linux3.bash

#!/usr/bin/env bash

##
# Split a string into an array of arguments 
# @example str_split_args "One two three" args_list # $args_list will contain three elements, 'One','two', and 'three'
# @arg $1 the string to split
# @arg $2 variable name to store array in 
#
function str_split_args(){
    local -n ARGS_ARRAY="$2"
    local arg_list

    IFS=' ' read -r -a ARGS_ARRAY <<< "$1"
}

## 
# Move the file to a dated trash dir
# @arg $1 relative path to the file, within the data directory
#
function trash_file(){
    echo "$trash_dir"
    local src_file="$data_dir/$1"
    local target_file="$trash_dir/$1"
    local target_dir="$(dirname "$target_file")"

    mkdir --parents "$target_dir"
    mv "$src_file" "$target_file"
}

##
# Prompt to choose items
# 
# @example select_items packages_to_remove "Choose packages to dnf remove" "${packages[@]}"
# @arg $1 name of variable to store items in
# @arg $2 header for the prompt
# @arg $3 array of items
#
function select_items(){
    local -n SELECTED_ITEMS="$1"
    local promptstr
    local header="$2"

    local items=()

    local index=0
    for arg in "$@";do
        if [[ $index -lt 2 ]];then
            index=$((index + 1))
            continue;
        fi

        items+=("$arg")
    done
    # echo "First:${items[0]}"
    # echo "All:${items[@]}"
    # exit;


    promptstr=""
    promptstr=()
    for item in "${items[@]}";do
        promptstr+=("$item")
        promptstr+=("$item")
        promptstr+=("")
    done


    prompt_choose_multi SELECTED_ITEMS "# $header" "${promptstr[@]}" 
}

##
# split a file by string and filter out blank lines and lines starting with a `#` or space
#
# @example get_file_items "$file" packages
# @arg $1 the absolute path to the file to load
# @arg $2 name of variable to store the items array in
#
function get_file_items(){
    local file="$1"
    local -n ITEMS="$2"
    content=$(cat "$file")
    lines=()
    str_split_line "$content" lines
    
    ITEMS=""
    ITEMS=()
    for line in "${lines[@]}";do
        if [[ "${line:0:1}" == "#" || "${line:0:1}" == " " || -z "$line" ]];then
            continue;
        fi
        ITEMS+=("$line")
    done
}

## 
# Source each file in a directory. Do not descend into subdirectories
# @arg $1 the relative directory inside the setup dir
#
function src_each(){
    local dir;
    dir="$setup_dir/$1"

    prompt_yes_or_no "Source files in $dir?" || return

    echo "Dir:$dir"
    for f in "$dir"/*;do
        if [[ -f "$f" ]];then
            source "$f"
        fi;
    done
}

##
# remove/uninstall each package listed in the given file
# @arg $1 the relative path to the file containing a list of packages, one on each line
#
function remove_each(){
    local file="$setup_dir/$1"
    local packages
    local packages_to_remove
    local package_list

    get_file_items "$file" packages
    select_items packages_to_remove "Choose packages to dnf remove" "${packages[@]}"

    if [[ -z "$packages_to_remove" ]];then
        msg_notice "No packages selected to remove"
    else
        package_list="${packages_to_remove[@]}"
        msg_instruct "[sudo dnf remove $package_list]"
        sudo dnf remove ${packages_to_remove[@]}
    fi
}



##
# install each package listed in the given file
# @arg $1 the relative path to the file containing a list of packages, one on each line
#
function install_each(){
    local file="$setup_dir/$1"
    local packages
    local packages_to_install
    local package_list

    get_file_items "$file" packages
    select_items packages_to_install "Choose packages to dnf install" "${packages[@]}"

    if [[ -z "$packages_to_install" ]];then
        msg_notice "No packages selected to install"
    else
        package_list="${packages_to_install[@]}"
        msg_instruct "[sudo dnf install $package_list]"
        sudo dnf install ${packages_to_install[@]}
    fi
}

##
# decompress each file in the list.
# Each file in the list must be relative to the data directory.
# Each file must be a path WITHOUT `.tar.gz` 
# the "$file.tar.gz" will be decompressed to "$file" 
# "$file.tar.gz" will be removed after decompression
# 
# @arg $1 the relative path to a file containing a list of files, on eon each line. 
#
function decompress_each(){
    local file="$setup_dir/$1"
    local rel_files
    local files_to_decompress
    local path

    get_file_items "$file" rel_files
    select_items files_to_decompress "Choose Files to Decompress" "${rel_files[@]}"

    if [[ -z "$files_to_decompress" ]];then
        msg_notice "No files to decompress"
        return;
    fi

    for file in "${files_to_decompress[@]}";do
        dir_path="$data_dir/$file"
        tar_path="${dir_path}.tar.gz"

        echo "TAR:$tar_path"
        echo "DIR:$dir_path"
        # exit

        if [[ ! -f "$tar_path" ]];then
            echo "why continue?"
            continue;
        fi


        tar -xf "$tar_path" -C "$(dirname $tar_path)"
        trash_file "$file.tar.gz"
    done
}

## 
# Source/run each file in a directory. Do not descend into subdirectories
# @arg $1 the relative directory inside the setup dir
# @alias of src_each
function run_each(){
    local dir;
    dir="$setup_dir/$1"

    prompt_yes_or_no "Source files in $dir?" || return

    echo "Dir:$dir"
    for f in "$dir"/*;do
        if [[ -f "$f" ]];then
            source "$f"
        fi;
    done
}

##
# Create a symlink for each line in the file
#
# @arg $1 the file, relative to the setup dir, which should be scanned for links
#
function link_each(){
    local file="$setup_dir/$1"
    local symlink

    get_file_items "$file" symlink_rows


    # echo "File: $file"
    # echo "SYMLINK ROWS: ${symlink_rows[0]}"

    # exit

    select_items symlinks_to_make "Choose which symlinks to make" "${symlink_rows[@]}"

    if [[ -z "$symlinks_to_make" ]];then
        msg_notice "No symlinks selected to run"
    else
        for symlink in "${symlinks_to_make[@]}";do
            # echo "LINK: $symlink"
            str_split_args "$symlink" symlink_args
            # echo "Args: ${symlink_args[@]}"
            # echo "Arg1: ${symlink_args[0]}"
            execute_symlink "${symlink_args[@]}"
        done
    fi
}

##
# Create a symlink in the home dir pointing to a file in the data dir
# 
# @example execute_symlink config/bashrc .bashrc  # create $home_dir/.bashrc pointing to $data_dir/config/bashrc
# @example config/.config -r  # for every file in $data_dir/config/.config, create a same-pathed symlink in $home_dir/.config pointing to that file
#
# @arg $1 the file in the data directory that should be symlinked to
# @arg $2 (optional) The location of the symlink itself, inside the home directory
#
# @option -r recurse into source directory and only symlink to files
# @option -d delete target directory if it exists and replace with a symlink
#
function execute_symlink(){
    local data_file
    local home_file
    local recurse="false"
    local delete="false"
    local arg_list

    arg_list=()
    for arg in "${@}";do
        if [[ "$arg" == "-r" ]];then
            recurse="true"
        elif [[ "$arg" == "-d" ]];then
            delete="true"
        else
            arg_list+=("$arg")
        fi
    done

    data_file="${arg_list[0]}"
    home_file="${arg_list[1]}"
    if [[ -z "$home_file" ]];then
        home_file="$(basename "$data_file")"
    fi

    echo "  Data:$data_file"
    echo "  home:$home_file"
    echo "  Recurse:$recurse"
    echo "  Delete:$delete"

    data_path="$data_dir/$data_file"

    echo "Data Path: $data_path"
    if [[ "$recurse" != "true" ]];then
        echo "NOT RECURSE"
        if [[ -d "$data_path" ]];then
            echo "CREATE DIR LINK"
            home_path="$home_dir/$home_file"
            
            offer_trash_home_path "$home_path" "$data_path"
            ln -s "$data_path" "$home_path"
        elif [[ -f "$data_path" ]];then
            echo "CREATE FILE LINK"
            home_path="$home_dir/$home_file"
            offer_trash_home_path "$home_path" "$data_path"
            ln -s "$data_path" "$home_path"
          elif [[ "${data_path:(-2)}" == "/*" ]];then 
            if [[ "${home_file:(-1)}" == "*" ]];then
                home_file="${home_file::-1}"
            fi
            echo "CREATE MULTIPLE LINKS IN DIR"
            data_dir_path="${data_path::-2}"
            for file in "$data_dir_path"/*;do
              echo "Create link to: $file"
              basename="$(basename $file)"
              home_path="$home_dir/$home_file/$basename"
              offer_trash_home_path "$home_path" "$file"
              ln -s "$file" "$home_path"
            done
        fi
      elif [[ "$recurse" == "true" ]];then
        echo "RECURSE"

        local rel_path="$1"
        local input_dir_path="${root_dir}/$rel_path"
        # echo "$root_dir"
        # echo "$input_dir_path"
        ### find every file
        local cur_dir="$(pwd)"
        # cd "$home_dir"

        msg_instruct "homelink_deep $rel_path"

        local file_list=()

        while IFS= read -r -d $'\0' filePath; do
            if [[ -d "$filePath" ]];then continue; fi
            file_list+=("$filePath")
        done < <(find "$data_path"/ -type f -print0)

        for filePath in "${file_list[@]}";do
            # echo "$filePath"
            rel_file_path="${filePath##${data_path}/}"
            target_home_path="${home_dir}/${rel_file_path}"
            offer_trash_home_path "$target_home_path" "$filePath"
            msg_status "mkdir \"$(dirname ${target_home_path})\""
            mkdir --parents "$(dirname ${target_home_path})"
            msg_status "  then: ln -s ${filePath} ${target_home_path}"
            ln -s "${filePath}" "${target_home_path}"
        done

        # cd "$cur_dir"
    fi

    exit
    msg_warn "This method is not implemented yet"

}

##
# Offer to move a target file to trash in order to create a symlink
# @arg trash_path the absolute path to the file that will be replaced by a symlink
# @arg symlink_target the absolute path the symlink will point to
#
# @usage offer_trash_home_path "$path_to_trashable_file" "$path_to_symlink_target"
#
function offer_trash_home_path(){
    trash_path="$1"
    symlink_target="$2"

    if [[ ! -e "$trash_path" ]];then
      return
    fi

    prompt_yes_or_no "Trash '$trash_path' and replace with symlink to '$symlink_target'" allow_trash
    if [[ $allow_trash ]];then
        local src_file="$trash_path"
        local trash_file_target="$trash_dir/${src_file##$home_dir}"
        local target_dir="$(dirname "$trash_file_target")"

        echo "Move to trash: $trash_file_target"

        mkdir --parents "$target_dir"
        mv "$src_file" "$trash_file_target"
    fi

}