diff --git a/install.sh b/install.sh index 179e1cf..6de345d 100755 --- a/install.sh +++ b/install.sh @@ -1,99 +1,34 @@ #!/usr/bin/env bash -# shellcheck disable=SC2034 - # Install the product into the system +# # Copyright 2024 林博仁(Buo-ren Lin) # SPDX-License-Identifier: WTFPL +# +# shellcheck disable=SC2034 -## Makes debuggers' life easier - Unofficial Bash Strict Mode -## BASHDOC: Shell Builtin Commands - Modifying Shell Behavior - The Set Builtin -set -o errexit -set -o errtrace -set -o nounset -set -o pipefail - -## Runtime Dependencies Checking -declare\ - runtime_dependency_checking_result=still-pass\ - required_software - -for required_command in \ - basename\ - dirname\ - install\ - realpath\ - rm; do - if ! command -v "${required_command}" &>/dev/null; then - runtime_dependency_checking_result=fail - - case "${required_command}" in - basename\ - |dirname\ - |install\ - |realpath\ - |rm) - required_software='GNU Coreutils' - ;; - *) - required_software="${required_command}" - ;; - esac - - printf --\ - 'Error: This program requires "%s" to be installed and its executables in the executable searching paths.\n' \ - "${required_software}" 1>&2 - unset required_software - fi -done; unset required_command required_software - -if [ "${runtime_dependency_checking_result}" = fail ]; then - printf --\ - 'Error: Runtime dependency checking fail, the progrom cannot continue.\n' 1>&2 - exit 1 -fi; unset runtime_dependency_checking_result - -## Non-overridable Primitive Variables -## BASHDOC: Shell Variables » Bash Variables -## BASHDOC: Basic Shell Features » Shell Parameters » Special Parameters -if [ -v 'BASH_SOURCE[0]' ]; then - RUNTIME_EXECUTABLE_PATH="$(realpath --strip "${BASH_SOURCE[0]}")" - RUNTIME_EXECUTABLE_FILENAME="$(basename "${RUNTIME_EXECUTABLE_PATH}")" - RUNTIME_EXECUTABLE_NAME="${RUNTIME_EXECUTABLE_FILENAME%.*}" - RUNTIME_EXECUTABLE_DIRECTORY="$(dirname "${RUNTIME_EXECUTABLE_PATH}")" - RUNTIME_COMMANDLINE_BASECOMMAND="${0}" - declare -r\ - RUNTIME_EXECUTABLE_FILENAME\ - RUNTIME_EXECUTABLE_DIRECTORY\ - RUNTIME_EXECUTABLE_PATHABSOLUTE\ - RUNTIME_COMMANDLINE_BASECOMMAND -fi -declare -ar RUNTIME_COMMANDLINE_ARGUMENTS=("${@}") - -## init function: entrypoint of main program -## This function is called near the end of the file, -## with the script's command-line parameters as arguments init(){ local flag_uninstall=false local install_directory_xdg - if ! process_commandline_arguments\ - flag_uninstall; then - printf -- \ + if ! process_commandline_arguments \ + flag_uninstall \ + "${script_args[@]}"; then + printf \ 'Error: %s: Invalid command-line parameters.\n' \ "${FUNCNAME[0]}" \ 1>&2 - print_help + print_help "${script_basecommand}" exit 1 fi - if ! determine_install_directory\ + if ! determine_install_directory \ install_directory_xdg; then printf -- \ 'Error: Unable to determine install directory, installer cannot continue.\n' \ 1>&2 exit 1 else - printf -- \ + printf \ 'Will be installed to: %s\n' \ "${install_directory_xdg}" printf '\n' @@ -101,30 +36,27 @@ init(){ remove_old_installation\ "${install_directory_xdg}" - if [ "${flag_uninstall}" = true ]; then - printf -- \ - 'Software uninstalled successfully.\n' + if test "${flag_uninstall}" = true; then + printf 'Software uninstalled successfully.\n' exit 0 fi - printf -- \ - 'Installing template files...\n' + printf 'Installing template files...\n' mkdir \ --parents \ "${XDG_TEMPLATES_DIR}" install \ --verbose \ --mode=u=rw,go=r \ - "${RUNTIME_EXECUTABLE_DIRECTORY}/.editorconfig" \ + "${script_dir}/.editorconfig" \ "${install_directory_xdg}/EditorConfig Template.editorconfig" printf '\n' # Seperate output from different operations while true; do - printf -- \ - 'Do you want to install files to enable KDE support(y/N)?' + printf 'Do you want to install files to enable KDE support(y/N)?' read -r answer - if [ -z "${answer}" ]; then + if test -z "${answer}"; then break else # lowercasewize @@ -135,10 +67,10 @@ init(){ | tr '[:upper:]' '[:lower:]' )" - if [ "${answer}" != n ] && [ "${answer}" != y ]; then + if test "${answer}" != n && test "${answer}" != y; then # wrong format, re-ask continue - elif [ "${answer}" == n ]; then + elif test "${answer}" == n; then break else printf 'Configuring templates for KDE...\n' @@ -148,26 +80,28 @@ init(){ install \ --verbose \ --mode=u=rw,go=r \ - "${RUNTIME_EXECUTABLE_DIRECTORY}/.editorconfig" \ + "${script_dir}/.editorconfig" \ "${HOME}/.local/share/templates/EditorConfig Template.editorconfig" install \ --verbose \ --mode=u=rw,go=r \ - "${RUNTIME_EXECUTABLE_DIRECTORY}/Template Setup for KDE"/*.desktop \ + "${script_dir}/Template Setup for KDE/"*.desktop \ "${HOME}/.local/share/templates" break fi fi - done; unset answer - - printf 'Installation completed.\n' + done + printf \ + 'Info: Operation completed without errors.\n' exit 0 -}; declare -fr init +} print_help(){ - printf '# %s #\n' "${RUNTIME_EXECUTABLE_NAME}" + local script_basecommand="${1}"; shift + + printf '# %s #\n' "${script_basecommand}" printf 'This program installs the templates into the system to make it accessible.\n\n' printf '## Command-line Options ##\n' @@ -181,23 +115,23 @@ print_help(){ printf 'Enable debug mode\n\n' return 0 -}; declare -fr print_help; +} process_commandline_arguments() { - local -n flag_uninstall_ref="${1}" + local -n flag_uninstall_ref="${1}"; shift - if [ "${#RUNTIME_COMMANDLINE_ARGUMENTS[@]}" -eq 0 ]; then + if test "${#script_args[@]}" -eq 0; then return 0 fi # modifyable parameters for parsing by consuming - local -a parameters=("${RUNTIME_COMMANDLINE_ARGUMENTS[@]}") + local -a parameters=("${@}"); set -- - # Normally we won't want debug traces to appear during parameter parsing, so we add this flag and defer it activation till returning(Y: Do debug) - local enable_debug=N + # Normally we won't want debug traces to appear during parameter parsing, so we add this flag and defer it activation till returning + local enable_debug=false while true; do - if [ "${#parameters[@]}" -eq 0 ]; then + if test "${#parameters[@]}" -eq 0; then break else case "${parameters[0]}" in @@ -212,7 +146,7 @@ process_commandline_arguments() { ;; --debug\ |-d) - enable_debug=Y + enable_debug=true ;; *) printf 'ERROR: Unknown command-line argument "%s"\n' "${parameters[0]}" >&2 @@ -221,27 +155,26 @@ process_commandline_arguments() { esac # shift array by 1 = unset 1st then repack unset 'parameters[0]' - if [ "${#parameters[@]}" -ne 0 ]; then + if test "${#parameters[@]}" -ne 0; then parameters=("${parameters[@]}") fi fi done - if [ "${enable_debug}" = Y ]; then - trap 'trap_return "${FUNCNAME[0]}"' RETURN + if test "${enable_debug}" = true; then set -o xtrace fi return 0 -}; declare -fr process_commandline_arguments +} determine_install_directory(){ local -n install_directory_xdg_ref="${1}"; shift # For $XDG_TEMPLATES_DIR - if [ -f "${HOME}"/.config/user-dirs.dirs ];then + if test -f "${HOME}/.config/user-dirs.dirs";then # external file, disable check - #shellcheck disable=SC1090 - source "${HOME}"/.config/user-dirs.dirs + # shellcheck source=/dev/null + source "${HOME}/.config/user-dirs.dirs" if [ -v XDG_TEMPLATES_DIR ]; then install_directory_xdg_ref="${XDG_TEMPLATES_DIR}" @@ -254,70 +187,137 @@ determine_install_directory(){ "${FUNCNAME[0]}" \ 1>&2 - if [ ! -d "${HOME}"/Templates ]; then + if test ! -d "${HOME}/Templates"; then return 1 else - install_directory_xdg_ref="${HOME}"/Templates + install_directory_xdg_ref="${HOME}/Templates" fi -}; declare -fr determine_install_directory +} ## Attempt to remove old installation files remove_old_installation(){ local install_directory_xdg="${1}"; shift 1 printf 'Removing previously installed templates(if available)...\n' - rm\ - --verbose\ - --force\ + rm \ + --verbose \ + --force \ "${install_directory_xdg}/EditorConfig Template.editorconfig" - rm\ - --verbose\ - --force\ - "${HOME}/.local/share/templates/EditorConfig Template.editorconfig"\ + rm \ + --verbose \ + --force \ + "${HOME}/.local/share/templates/EditorConfig Template.editorconfig" \ "${HOME}/.local/share/templates/EditorConfig Template.desktop" printf 'Finished.\n' printf '\n' # Additional blank line for separating output return 0 -}; declare -fr remove_old_installation - -## Traps: Functions that are triggered when certain condition occurred -## Shell Builtin Commands » Bourne Shell Builtins » trap +} + +printf \ + 'Info: Configuring the defensive interpreter behaviors...\n' +set_opts=( + # Terminate script execution when an unhandled error occurs + -o errexit + -o errtrace + + # Terminate script execution when an unset parameter variable is + # referenced + -o nounset +) +if ! set "${set_opts[@]}"; then + printf \ + 'Error: Unable to configure the defensive interpreter behaviors.\n' \ + 1>&2 + exit 1 +fi -# Traps shouldn't be reachable in normal circumstances -# shellcheck disable=SC2317 -trap_errexit(){ - printf 'An error occurred and the script is prematurely aborted\n' 1>&2 - return 0 -}; declare -fr trap_errexit; trap trap_errexit ERR +runtime_dependency_check_failed=false +required_commands=( + basename + dirname + install + realpath + rm +) +for command in "${required_commands[@]}"; do + if ! command -v "${command}" &>/dev/null; then + runtime_dependency_check_failed=true + + case "${command}" in + basename\ + |dirname\ + |install\ + |realpath\ + |rm) + required_software='GNU Coreutils' + ;; + *) + required_software="${command}" + ;; + esac -# Traps shouldn't be reachable in normal circumstances -# shellcheck disable=SC2317 -trap_exit(){ - return 0 -}; declare -fr trap_exit; trap trap_exit EXIT + printf \ + 'Error: This program requires "%s" to be installed and its executables in the executable searching paths.\n' \ + "${required_software}" \ + 1>&2 + fi +done -# Traps shouldn't be reachable in normal circumstances -# shellcheck disable=SC2317 -trap_return(){ - local returning_function="${1}" +if test "${runtime_dependency_check_failed}" == true; then + printf \ + 'Error: Runtime dependency checking failed, the progrom cannot continue.\n' \ + 1>&2 + exit 1 +fi - printf 'DEBUG: %s: returning from %s\n' "${FUNCNAME[0]}" "${returning_function}" 1>&2 -}; declare -fr trap_return -# Traps shouldn't be reachable in normal circumstances +printf \ + 'Info: Configuring the convenience variables...\n' +if test -v BASH_SOURCE; then + # Convenience variables may not need to be referenced + # shellcheck disable=SC2034 + { + printf \ + 'Info: Determining the absolute path of the program...\n' + if ! script="$( + realpath \ + --strip \ + "${BASH_SOURCE[0]}" + )"; then + printf \ + 'Error: Unable to determine the absolute path of the program.\n' \ + 1>&2 + exit 1 + fi + script_dir="${script%/*}" + script_filename="${script##*/}" + script_name="${script_filename%%.*}" + } +fi +# Convenience variables may not need to be referenced +# shellcheck disable=SC2034 +{ + script_basecommand="${0}" + script_args=("${@}") +} + +printf \ + 'Info: Setting the ERR trap...\n' +# trap commands are not called by default # shellcheck disable=SC2317 -trap_interrupt(){ - printf '\n' # Separate previous output - printf 'Recieved SIGINT, script is interrupted.' 1>&2 - return 1 -}; declare -fr trap_interrupt; trap trap_interrupt INT +trap_err(){ + printf \ + 'Error: The program prematurely terminated due to an unhandled error.\n' \ + 1>&2 + exit 99 +} +if ! trap trap_err ERR; then + printf \ + 'Error: Unable to set the ERR trap.\n' \ + 1>&2 + exit 1 +fi init "${@}" - -## This script is based on the GNU Bash Shell Script Template project -## https://github.com/Lin-Buo-Ren/GNU-Bash-Shell-Script-Template -## and is based on the following version: -## GNU_BASH_SHELL_SCRIPT_TEMPLATE_VERSION="v3.0.0-4-g5de1348" -## You may rebase your script to incorporate new features and fixes from the template