mirror of
https://github.com/ivuorinen/dotfiles.git
synced 2026-02-01 22:47:48 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 731b96a021 | |||
| b0c647009d | |||
| 1f4f046bb1 | |||
| a0ae26bb21 | |||
| b1366dd982 | |||
|
|
5c59ed707e | ||
| 14ab12ceda | |||
|
|
a213dbf31f | ||
|
|
7ac2e2f1bf | ||
|
|
b6841a3ae0 | ||
| 1d0ea5ace4 | |||
| 76076fdaa4 | |||
| 5a832d1478 | |||
| 6155891fa4 | |||
|
|
066b38926a | ||
| 35e812baa2 | |||
| 0037067722 | |||
| 573fc9faf4 | |||
| 359ac4e2c0 | |||
| 255c8fdce7 |
@@ -28,7 +28,7 @@ indent_size = 1
|
|||||||
indent_size = 1
|
indent_size = 1
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
|
||||||
[{local/bin/*,**/*.sh,**/zshrc,config/*,scripts/*}]
|
[{local/bin/*,local/dfm/*,**/*.sh,**/zshrc,config/*,scripts/*}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
tab_width = 2
|
tab_width = 2
|
||||||
shell_variant = bash # --language-variant
|
shell_variant = bash # --language-variant
|
||||||
|
|||||||
8
.github/README.md
vendored
8
.github/README.md
vendored
@@ -16,9 +16,11 @@ see what interesting stuff you've done with it. Sharing is caring.
|
|||||||
### First time setup
|
### First time setup
|
||||||
|
|
||||||
1. Clone this repository to `$HOME/.dotfiles`
|
1. Clone this repository to `$HOME/.dotfiles`
|
||||||
2. `./install`
|
2. Install [shellcheck](https://github.com/koalaman/shellcheck) and [pre-commit](https://pre-commit.com/)
|
||||||
3. ???
|
3. Run `pre-commit install` to enable Git hooks
|
||||||
4. Profit
|
4. `./install`
|
||||||
|
5. ???
|
||||||
|
6. Profit
|
||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
|
|
||||||
|
|||||||
239
local/dfm/cmd/install.sh
Normal file
239
local/dfm/cmd/install.sh
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Source core dfm libraries
|
||||||
|
source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
||||||
|
source "$(dirname "${BASH_SOURCE[0]}")/../lib/utils.sh"
|
||||||
|
|
||||||
|
# Default paths can be overridden via environment variables
|
||||||
|
: "${DOTFILES:=$HOME/.dotfiles}"
|
||||||
|
: "${BREWFILE:=$DOTFILES/config/homebrew/Brewfile}"
|
||||||
|
: "${TEMP_DIR:=$(mktemp -d)}"
|
||||||
|
: "${DFM_MAX_RETRIES:=3}"
|
||||||
|
|
||||||
|
# Remove temp folder on exit
|
||||||
|
trap 'rm -rf "$TEMP_DIR"' EXIT
|
||||||
|
|
||||||
|
# Installation functions for dfm, the dotfile manager
|
||||||
|
#
|
||||||
|
# @author Ismo Vuorinen <https://github.com/ivuorinen>
|
||||||
|
# @license MIT
|
||||||
|
|
||||||
|
# Installs all required packages in the correct order.
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# Orchestrates the installation process for the dotfile manager by sequentially invoking
|
||||||
|
# the installation routines for fonts, Homebrew, and Rust (cargo). It logs the start of the
|
||||||
|
# overall installation process before calling each respective function.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# lib::log - Function used to log installation progress messages.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# None.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Logs an informational message indicating the start of the installation process.
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# None.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# all
|
||||||
|
#
|
||||||
|
# @description
|
||||||
|
# Parse command line options controlling installation steps.
|
||||||
|
parse_options()
|
||||||
|
{
|
||||||
|
NO_AUTOMATION=0
|
||||||
|
SKIP_FONTS=0
|
||||||
|
SKIP_BREW=0
|
||||||
|
SKIP_CARGO=0
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--no-automation)
|
||||||
|
NO_AUTOMATION=1
|
||||||
|
;;
|
||||||
|
--no-fonts)
|
||||||
|
SKIP_FONTS=1
|
||||||
|
;;
|
||||||
|
--no-brew)
|
||||||
|
SKIP_BREW=1
|
||||||
|
;;
|
||||||
|
--no-cargo)
|
||||||
|
SKIP_CARGO=1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
lib::error "Unknown option: $1"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# @description
|
||||||
|
# Install all configured components by calling each individual
|
||||||
|
# installation routine unless skipped via options.
|
||||||
|
install_all()
|
||||||
|
{
|
||||||
|
parse_options "$@"
|
||||||
|
|
||||||
|
lib::log "Installing all packages..."
|
||||||
|
|
||||||
|
if [[ $SKIP_FONTS -eq 0 ]]; then
|
||||||
|
install_fonts
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $SKIP_BREW -eq 0 ]]; then
|
||||||
|
install_brew
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $SKIP_CARGO -eq 0 ]]; then
|
||||||
|
install_cargo
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Installs fonts required by the dotfile manager.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# None.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# None.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Logs a message to STDOUT indicating that the font installation process has started.
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# None.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# install_fonts
|
||||||
|
#
|
||||||
|
# @description Install all configured fonts from helper script, prompting the user unless automation is disabled.
|
||||||
|
install_fonts()
|
||||||
|
{
|
||||||
|
|
||||||
|
: "${SKIP_FONTS:=0}"
|
||||||
|
: "${NO_AUTOMATION:=0}"
|
||||||
|
|
||||||
|
if [[ $SKIP_FONTS -eq 1 ]]; then
|
||||||
|
lib::log "Skipping fonts installation"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $NO_AUTOMATION -eq 0 ]]; then
|
||||||
|
utils::interactive::confirm "Install fonts?" || return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
lib::log "Installing fonts..."
|
||||||
|
local script="${DOTFILES}/scripts/install-fonts.sh"
|
||||||
|
|
||||||
|
if [[ ! -x "$script" ]]; then
|
||||||
|
lib::error "Font installation script not found: $script"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
bash "$script"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install Homebrew and set it up.
|
||||||
|
#
|
||||||
|
# Installs the Homebrew package manager on macOS.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# lib::log - Logging utility used to report installation progress.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Logs a message indicating the start of the Homebrew installation process.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# install_brew
|
||||||
|
#
|
||||||
|
# @description Install Homebrew and declared packages using the Brewfile.
|
||||||
|
install_brew()
|
||||||
|
{
|
||||||
|
|
||||||
|
: "${SKIP_BREW:=0}"
|
||||||
|
: "${NO_AUTOMATION:=0}"
|
||||||
|
|
||||||
|
|
||||||
|
if [[ $SKIP_BREW -eq 1 ]]; then
|
||||||
|
lib::log "Skipping Homebrew installation"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $NO_AUTOMATION -eq 0 ]]; then
|
||||||
|
utils::interactive::confirm "Install Homebrew packages?" || return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
lib::log "Installing Homebrew..."
|
||||||
|
if ! utils::is_installed brew; then
|
||||||
|
lib::log "Homebrew not found, installing..."
|
||||||
|
|
||||||
|
local installer="$TEMP_DIR/homebrew-install.sh"
|
||||||
|
utils::retry "$DFM_MAX_RETRIES" \
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh \
|
||||||
|
-o "$installer"
|
||||||
|
|
||||||
|
NONINTERACTIVE=1 bash "$installer"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if utils::is_installed brew; then
|
||||||
|
brew bundle install --file="$BREWFILE" --force --quiet
|
||||||
|
else
|
||||||
|
lib::error "Homebrew installation failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Installs Rust and cargo packages.
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# Logs the start of the installation process for Rust and cargo packages.
|
||||||
|
# The installation logic is intended to be implemented where indicated.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# Uses lib::log for logging the installation process.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# install_cargo
|
||||||
|
#
|
||||||
|
# @description Install Rust tooling and cargo packages using helper scripts.
|
||||||
|
install_cargo()
|
||||||
|
{
|
||||||
|
|
||||||
|
: "${SKIP_CARGO:=0}"
|
||||||
|
: "${NO_AUTOMATION:=0}"
|
||||||
|
|
||||||
|
|
||||||
|
if [[ $SKIP_CARGO -eq 1 ]]; then
|
||||||
|
lib::log "Skipping Rust and cargo installation"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $NO_AUTOMATION -eq 0 ]]; then
|
||||||
|
utils::interactive::confirm "Install Rust and cargo packages?" || return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
lib::log "Installing Rust and cargo packages..."
|
||||||
|
if ! utils::is_installed cargo; then
|
||||||
|
lib::log "Rust not found, installing rustup..."
|
||||||
|
|
||||||
|
local installer="$TEMP_DIR/rustup-init.sh"
|
||||||
|
utils::retry "$DFM_MAX_RETRIES" \
|
||||||
|
curl https://sh.rustup.rs -sSf -o "$installer"
|
||||||
|
sh "$installer" -y
|
||||||
|
source "$HOME/.cargo/env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local script="${DOTFILES}/scripts/install-cargo-packages.sh"
|
||||||
|
if [[ -x "$script" ]]; then
|
||||||
|
bash "$script"
|
||||||
|
else
|
||||||
|
lib::error "Cargo packages script not found: $script"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
104
local/dfm/dfm
Executable file
104
local/dfm/dfm
Executable file
@@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# dfm - dotfiles manager
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# allow overriding core directories
|
||||||
|
DFM_SCRIPT_DIR="${DFM_SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
|
||||||
|
readonly DFM_SCRIPT_DIR
|
||||||
|
export DFM_SCRIPT_DIR
|
||||||
|
DFM_CMD_DIR="${DFM_CMD_DIR:-${DFM_SCRIPT_DIR}/cmd}"
|
||||||
|
readonly DFM_CMD_DIR
|
||||||
|
export DFM_CMD_DIR
|
||||||
|
DFM_LIB_DIR="${DFM_LIB_DIR:-${DFM_SCRIPT_DIR}/lib}"
|
||||||
|
readonly DFM_LIB_DIR
|
||||||
|
export DFM_LIB_DIR
|
||||||
|
DFM_DEFAULT_CONFIG_PATH="${DFM_DEFAULT_CONFIG_PATH:-$HOME/.config}"
|
||||||
|
readonly DFM_DEFAULT_CONFIG_PATH
|
||||||
|
export DFM_DEFAULT_CONFIG_PATH
|
||||||
|
DFM_MAX_RETRIES="${DFM_MAX_RETRIES:-3}"
|
||||||
|
readonly DFM_MAX_RETRIES
|
||||||
|
export DFM_MAX_RETRIES
|
||||||
|
export DFM_DEFAULT_INSTALL_DIR="${DFM_DEFAULT_INSTALL_DIR:-$HOME/.local}"
|
||||||
|
export DFM_DEFAULT_VERBOSE="${DFM_DEFAULT_VERBOSE:-0}"
|
||||||
|
TEMP_DIR="${TEMP_DIR:-$(mktemp -d)}"
|
||||||
|
export TEMP_DIR
|
||||||
|
|
||||||
|
# Load the common and utility functions from the lib directory.
|
||||||
|
[[ -f "${DFM_LIB_DIR}/common.sh" ]] || {
|
||||||
|
echo "Error: Required file ${DFM_LIB_DIR}/common.sh not found"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[[ -f "${DFM_LIB_DIR}/utils.sh" ]] || {
|
||||||
|
echo "Error: Required file ${DFM_LIB_DIR}/utils.sh not found"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
source "${DFM_LIB_DIR}/common.sh"
|
||||||
|
source "${DFM_LIB_DIR}/utils.sh"
|
||||||
|
|
||||||
|
# Display help information
|
||||||
|
#
|
||||||
|
# @return None
|
||||||
|
main::show_help()
|
||||||
|
{
|
||||||
|
cat << EOF
|
||||||
|
Usage: dfm [command] [function] [arguments]
|
||||||
|
|
||||||
|
dotfiles manager utility for installing and configuring dotfiles.
|
||||||
|
|
||||||
|
If no arguments are provided, lists all available commands.
|
||||||
|
If only a command is provided, lists available functions for that command.
|
||||||
|
If a command and function are provided, executes the specified function.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dfm # List all available commands
|
||||||
|
dfm install # List available functions for the install command
|
||||||
|
dfm install all # Execute the 'all' function from the install command
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main function for the dfm script.
|
||||||
|
#
|
||||||
|
# The function checks if any arguments were provided. If no arguments are
|
||||||
|
# provided, it lists all available commands. If a command name is provided,
|
||||||
|
# it lists the available functions for that command. If a command and function
|
||||||
|
# name are provided, it executes the function with the provided arguments.
|
||||||
|
#
|
||||||
|
# @param args The command-line arguments.
|
||||||
|
# @return None
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
main::list_available_commands
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local cmd="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
if [[ "$cmd" == "-h" || "$cmd" == "--help" ]]; then
|
||||||
|
main::show_help
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
# Show the available functions for the command
|
||||||
|
local cmd_file="${DFM_CMD_DIR}/${cmd}.sh"
|
||||||
|
if [[ -f "$cmd_file" ]]; then
|
||||||
|
list::print_group "Available functions for '$cmd'"
|
||||||
|
list::loop_functions "$cmd_file"
|
||||||
|
else
|
||||||
|
lib::error "Command '$cmd' not found"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local func="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
main::execute_command "$cmd" "$func" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
368
local/dfm/lib/common.sh
Normal file
368
local/dfm/lib/common.sh
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# dfm common functions for logging and error handling, etc.
|
||||||
|
# Source this file to use the functions in your scripts.
|
||||||
|
#
|
||||||
|
# @author Ismo Vuorinen <https://github.com/ivuorinen>
|
||||||
|
# @license MIT
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
declare -A ERROR_CODES=(
|
||||||
|
[SUCCESS]=0
|
||||||
|
[INVALID_ARGUMENT]=1
|
||||||
|
[COMMAND_NOT_FOUND]=2
|
||||||
|
[FUNCTION_NOT_FOUND]=3
|
||||||
|
[EXECUTION_FAILED]=4
|
||||||
|
[FILE_NOT_FOUND]=5
|
||||||
|
)
|
||||||
|
|
||||||
|
declare -A LOG_LEVELS=(
|
||||||
|
[DEBUG]=0
|
||||||
|
[INFO]=1
|
||||||
|
[WARN]=2
|
||||||
|
[ERROR]=3
|
||||||
|
)
|
||||||
|
|
||||||
|
# Simple logging function
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# lib::log "Hello, world!"
|
||||||
|
#
|
||||||
|
# @description Log a message to the console
|
||||||
|
# @param $* Message to log
|
||||||
|
# Logs a message with a timestamp.
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# Outputs the provided message(s) to standard output, prepended with the current date and
|
||||||
|
# time in the format [YYYY-MM-DD HH:MM:SS]. This timestamp helps in tracking log events.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# One or more strings that form the log message.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the timestamped log message to standard output.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# lib::log "Server started" # Outputs: [2025-02-28 09:45:00] Server started
|
||||||
|
lib::log()
|
||||||
|
{
|
||||||
|
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Simple error logging function
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# lib::error "Something went wrong"
|
||||||
|
#
|
||||||
|
# @description Log an error message to the console
|
||||||
|
# @param $* Error message
|
||||||
|
# Logs an error message with a timestamp to standard error.
|
||||||
|
#
|
||||||
|
# This function formats the provided message(s) by prefixing it with the current date
|
||||||
|
# and time along with an "ERROR:" label, then outputs the result to STDERR.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# $* - The error message or messages to be logged.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the formatted error message to STDERR.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# lib::error "Failed to read the configuration file."
|
||||||
|
lib::error()
|
||||||
|
{
|
||||||
|
printf '[%s] ERROR: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_LEVEL="${LOG_LEVEL:-INFO}"
|
||||||
|
if [[ -z "${LOG_LEVELS[$LOG_LEVEL]+_}" ]]; then
|
||||||
|
lib::error "Invalid LOG_LEVEL: $LOG_LEVEL"
|
||||||
|
exit "${ERROR_CODES[INVALID_ARGUMENT]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle an error by logging an error message to the console
|
||||||
|
# and exiting with an error code based on the error type.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# lib::error::handle $LINENO $0
|
||||||
|
#
|
||||||
|
# @description Handle an error
|
||||||
|
# @param $1 Line number
|
||||||
|
# @param $2 Command
|
||||||
|
# Logs an error message based on the previous command's exit code and the provided context.
|
||||||
|
#
|
||||||
|
# This function captures the exit code from the last executed command and, using the provided
|
||||||
|
# line number and command string, determines the appropriate error message to log based on
|
||||||
|
# predefined error codes stored in the ERROR_CODES associative array.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# ERROR_CODES - An associative array mapping error code names to numeric values.
|
||||||
|
# lib::error - Logs error messages to STDERR.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# line_no - The line number in the script where the error occurred.
|
||||||
|
# command - The command that was executed when the error occurred.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes a descriptive error message to STDERR.
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# The exit code of the failed command.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# # If a command fails with an exit code corresponding to an invalid argument:
|
||||||
|
# lib::error::handle 42 "some_command"
|
||||||
|
# # This logs: "Invalid argument at line 42 in command 'some_command'" (if the exit code matches ERROR_CODES[INVALID_ARGUMENT])
|
||||||
|
lib::error::handle()
|
||||||
|
{
|
||||||
|
local exit_code=$?
|
||||||
|
local line_no=$1
|
||||||
|
local command=$2
|
||||||
|
|
||||||
|
case $exit_code in
|
||||||
|
"${ERROR_CODES[INVALID_ARGUMENT]}")
|
||||||
|
lib::error "Invalid argument at line $line_no in command '$command'"
|
||||||
|
;;
|
||||||
|
"${ERROR_CODES[COMMAND_NOT_FOUND]}")
|
||||||
|
lib::error "Command not found at line $line_no"
|
||||||
|
;;
|
||||||
|
"${ERROR_CODES[FUNCTION_NOT_FOUND]}")
|
||||||
|
lib::error "Function not found at line $line_no in command '$command'"
|
||||||
|
;;
|
||||||
|
"${ERROR_CODES[EXECUTION_FAILED]}")
|
||||||
|
lib::error "Execution failed at line $line_no in command '$command'"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
lib::error "Unknown error ($exit_code) at line $line_no in command '$command'"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return $exit_code
|
||||||
|
}
|
||||||
|
|
||||||
|
# Throw an error by logging an error message to the console and exiting
|
||||||
|
# with an error code based on the error type. The error code name is used
|
||||||
|
# to determine the error code from the ERROR_CODES associative array.
|
||||||
|
# The error message is passed as arguments to the function.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# lib::error::throw INVALID_ARGUMENT "Invalid argument"
|
||||||
|
# lib::error::throw COMMAND_NOT_FOUND "Command not found"
|
||||||
|
# lib::error::throw FUNCTION_NOT_FOUND "Function not found"
|
||||||
|
# lib::error::throw EXECUTION_FAILED "Execution failed"
|
||||||
|
#
|
||||||
|
# @description Throw an error
|
||||||
|
# @param $1 Error code name
|
||||||
|
# @param $* Error message
|
||||||
|
# Logs an error message and terminates the script by performing cleanup with a specified error code.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# ERROR_CODES - Associative array mapping error code names to numeric exit values.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# code_name - The key to retrieve the error code from the ERROR_CODES array.
|
||||||
|
# message - The error message to log, constructed from all subsequent arguments.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Logs the error message to standard error.
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# Exits the script via the cleanup function; does not return.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# lib::error::throw "FILE_NOT_FOUND" "Required file not found: /path/to/file"
|
||||||
|
lib::error::throw()
|
||||||
|
{
|
||||||
|
local code_name=$1
|
||||||
|
shift
|
||||||
|
local message=$*
|
||||||
|
|
||||||
|
if [[ -z "${ERROR_CODES[$code_name]+_}" ]]; then
|
||||||
|
lib::error "Unknown error code: $code_name"
|
||||||
|
cleanup "${ERROR_CODES[INVALID_ARGUMENT]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
lib::error "$message"
|
||||||
|
cleanup "${ERROR_CODES[$code_name]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Logs a message to the console if the current log level is set so that the
|
||||||
|
# message is displayed. The log level is compared to the log level of the
|
||||||
|
# message and if the message log level is greater than or equal to the current
|
||||||
|
# log level, the message is displayed.
|
||||||
|
# The log levels are defined in the LOG_LEVELS associative array.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# logger::log "INFO" "This is an info message"
|
||||||
|
# logger::log "DEBUG" "This is a debug message"
|
||||||
|
# logger::log "WARN" "This is a warning message"
|
||||||
|
# logger::log "ERROR" "This is an error message"
|
||||||
|
#
|
||||||
|
# @description Log a message to the console based on the log level setting.
|
||||||
|
# @param $1 Log level
|
||||||
|
# @param $2 Message
|
||||||
|
# Logs a message if its severity meets or exceeds the global log level.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# LOG_LEVELS - Associative array mapping log level names to severity values.
|
||||||
|
# LOG_LEVEL - The current log level threshold.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# level: A string representing the log severity (e.g., DEBUG, INFO, WARN, ERROR).
|
||||||
|
# msg: The message to log.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Prints a formatted log message with a timestamp to STDERR when the specified level qualifies.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# logger::log INFO "Initialization complete"
|
||||||
|
logger::log()
|
||||||
|
{
|
||||||
|
local level=$1
|
||||||
|
if [[ -z "${LOG_LEVELS[$level]:-}" ]]; then
|
||||||
|
lib::error "Invalid log level: $level"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
shift
|
||||||
|
local msg="$*"
|
||||||
|
|
||||||
|
if [[ ${LOG_LEVELS[$level]} -ge ${LOG_LEVELS[$LOG_LEVEL]} ]]; then
|
||||||
|
printf '[%s] [%s]: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$level" "$msg" >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Logs a debug message to the console, if the current log level is set to DEBUG or greater.
|
||||||
|
# The message is passed as arguments to the function.
|
||||||
|
# The function is defined above.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# logger::debug "This is a debug message"
|
||||||
|
#
|
||||||
|
# @description Log a debug message to the console
|
||||||
|
# @param $* Message
|
||||||
|
# Logs a debug-level message.
|
||||||
|
#
|
||||||
|
# This function logs a message at the DEBUG level by delegating to logger::log.
|
||||||
|
# It accepts one or more arguments that form the debug message, which are passed along
|
||||||
|
# to the underlying logger::log function.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# logger::debug "Debug info for variable x:" "$x"
|
||||||
|
logger::debug()
|
||||||
|
{
|
||||||
|
logger::log "DEBUG" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Logs an info message to the console, if the current log level is set to INFO or greater.
|
||||||
|
# The message is passed as arguments to the function.
|
||||||
|
# The function is defined above.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# logger::info "This is an info message"
|
||||||
|
#
|
||||||
|
# @description Log an info message to the console
|
||||||
|
# @param $* Message
|
||||||
|
# Logs an informational message to the console.
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# This function wraps the logger::log function to log messages at the "INFO" level. All provided arguments are
|
||||||
|
# forwarded to logger::log, where the message is formatted and output based on the current logging configuration.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# A message string followed by optional additional parameters used to format the message.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# The formatted informational message is written to STDOUT if the INFO log level is enabled.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# logger::info "Service started successfully on port" 8080
|
||||||
|
logger::info()
|
||||||
|
{
|
||||||
|
logger::log "INFO" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Logs a warning message to the console, if the current log level is set to WARN or greater.
|
||||||
|
# The message is passed as arguments to the function.
|
||||||
|
# The function is defined above.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# logger::warn "This is a warning message"
|
||||||
|
#
|
||||||
|
# @description Log a warning message to the console
|
||||||
|
# @param $* Message
|
||||||
|
# Logs a warning message.
|
||||||
|
#
|
||||||
|
# This function acts as a wrapper around `logger::log` by setting the log level to "WARN"
|
||||||
|
# for all provided message arguments. It forwards the given messages to the logger for output.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# A variable list of strings representing the warning message.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes a formatted warning message to the console.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# logger::warn "Low disk space" "Free up some space to avoid issues"
|
||||||
|
logger::warn()
|
||||||
|
{
|
||||||
|
logger::log "WARN" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Logs an error message to the console, if the current log level is set to ERROR or greater.
|
||||||
|
# The message is passed as arguments to the function.
|
||||||
|
# The function is defined above.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# logger::error "This is an error message"
|
||||||
|
#
|
||||||
|
# @description Log an error message to the console
|
||||||
|
# @param $* Message
|
||||||
|
# Logs an error message at the ERROR level.
|
||||||
|
#
|
||||||
|
# This function wraps the generic logging mechanism to record error messages by automatically
|
||||||
|
# specifying the ERROR severity level. It passes all provided arguments to the underlying logging function.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# Error message(s) – One or more strings that describe the error.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# logger::error "Unable to open file" "/path/to/file"
|
||||||
|
logger::error()
|
||||||
|
{
|
||||||
|
logger::log "ERROR" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup function to remove temporary files and directories
|
||||||
|
# when the script exits or is interrupted by a signal (e.g. Ctrl+C).
|
||||||
|
# The function is registered with the `EXIT` trap.
|
||||||
|
#
|
||||||
|
# @description Remove temporary files and directories
|
||||||
|
# Cleans up temporary resources before exiting.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# TEMP_DIR - Path to the temporary directory to be removed if it exists.
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# Exits the script with the original exit code.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# trap cleanup EXIT
|
||||||
|
cleanup() {
|
||||||
|
local exit_code=${1:-$?}
|
||||||
|
if [[ -n ${TEMP_DIR:-} && -d $TEMP_DIR ]]; then
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
fi
|
||||||
|
exit "$exit_code"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Register the cleanup function to run on EXIT signal.
|
||||||
|
# This ensures temporary files and directories are removed
|
||||||
|
# when the script exits or is interrupted.
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# Handle errors by logging an error message to the console.
|
||||||
|
# The `ERR` trap passes the line number and command to lib::error::handle.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# lib::error::handle ${LINENO} "$BASH_COMMAND"
|
||||||
|
trap 'lib::error::handle ${LINENO} "$BASH_COMMAND"' ERR
|
||||||
806
local/dfm/lib/utils.sh
Normal file
806
local/dfm/lib/utils.sh
Normal file
@@ -0,0 +1,806 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# dfm utility functions for common tasks
|
||||||
|
# Source this file to use the functions in your scripts.
|
||||||
|
#
|
||||||
|
# @author Ismo Vuorinen <https://github.com/ivuorinen>
|
||||||
|
# @license MIT
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ANSI escape codes
|
||||||
|
readonly RESET="\033[0m"
|
||||||
|
readonly BOLD="\033[1m"
|
||||||
|
readonly DIM="\033[2m"
|
||||||
|
readonly ITALIC="\033[3m"
|
||||||
|
readonly UNDERLINE="\033[4m"
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
readonly BLACK="\033[30m"
|
||||||
|
readonly RED="\033[31m"
|
||||||
|
readonly GREEN="\033[32m"
|
||||||
|
readonly YELLOW="\033[33m"
|
||||||
|
readonly BLUE="\033[34m"
|
||||||
|
readonly MAGENTA="\033[35m"
|
||||||
|
readonly CYAN="\033[36m"
|
||||||
|
readonly WHITE="\033[37m"
|
||||||
|
|
||||||
|
# Prints the provided text in black color using ANSI escape codes.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# BLACK - ANSI escape code for black text.
|
||||||
|
# RESET - ANSI escape code to reset terminal formatting.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# One or more strings that will be concatenated and printed.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the colored text to STDOUT (without a trailing newline).
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# clr::black "This text will appear in black."
|
||||||
|
clr::black()
|
||||||
|
{
|
||||||
|
printf "${BLACK}%s${RESET}" "$*"
|
||||||
|
}
|
||||||
|
# Prints the provided text in red using ANSI escape codes.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# RED - ANSI escape code for red.
|
||||||
|
# RESET - ANSI escape code to reset terminal formatting.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# One or more strings that will be printed in red.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the red formatted text to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# clr::red "Error: Invalid input"
|
||||||
|
clr::red()
|
||||||
|
{
|
||||||
|
printf "${RED}%s${RESET}" "$*"
|
||||||
|
}
|
||||||
|
# Prints the given text in green using ANSI escape codes.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# * One or more strings to output in green. Multiple arguments are concatenated.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the formatted green text to STDOUT without a trailing newline.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# clr::green "Operation successful"
|
||||||
|
clr::green()
|
||||||
|
{
|
||||||
|
printf "${GREEN}%s${RESET}" "$*"
|
||||||
|
}
|
||||||
|
# Prints the provided text in yellow color.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# YELLOW - ANSI escape code for yellow text.
|
||||||
|
# RESET - ANSI escape code to reset text formatting.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# Any text passed as parameters will be printed in yellow.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Colored text printed to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# clr::yellow "Hello, World!"
|
||||||
|
clr::yellow()
|
||||||
|
{
|
||||||
|
printf "${YELLOW}%s${RESET}" "$*"
|
||||||
|
}
|
||||||
|
# Prints the provided text in blue using ANSI escape codes.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# BLUE ANSI escape sequence for blue text.
|
||||||
|
# RESET ANSI escape sequence to reset text formatting.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# $@ One or more strings to be printed in blue.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Prints the input text in blue to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# clr::blue "Hello, World!"
|
||||||
|
clr::blue()
|
||||||
|
{
|
||||||
|
printf "${BLUE}%s${RESET}" "$*"
|
||||||
|
}
|
||||||
|
# Prints the provided text in magenta color.
|
||||||
|
#
|
||||||
|
# This function outputs one or more strings wrapped in ANSI escape sequences
|
||||||
|
# to display them in magenta. It uses the global variables MAGENTA for the color
|
||||||
|
# and RESET to revert to the default formatting.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# MAGENTA - ANSI escape sequence for magenta.
|
||||||
|
# RESET - ANSI escape code to reset formatting.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# One or more strings to print in magenta.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the formatted string directly to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# clr::magenta "Hello, World!"
|
||||||
|
clr::magenta()
|
||||||
|
{
|
||||||
|
printf "${MAGENTA}%s${RESET}" "$*"
|
||||||
|
}
|
||||||
|
# Prints the provided text in white color using ANSI escape codes.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# WHITE - ANSI escape code for white.
|
||||||
|
# RESET - ANSI escape code to reset text formatting.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# Any text passed as arguments will be concatenated and printed.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the formatted text to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# clr::white "Hello, World!"
|
||||||
|
clr::white()
|
||||||
|
{
|
||||||
|
printf "${WHITE}%s${RESET}" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Applies bold styling to the provided text and prints it to STDOUT.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# BOLD - ANSI escape code for enabling bold text.
|
||||||
|
# RESET - ANSI escape code for resetting text formatting.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# One or more strings to be printed in bold.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Bold-formatted text is printed to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# style::bold "This is bold text"
|
||||||
|
style::bold()
|
||||||
|
{
|
||||||
|
printf "${BOLD}%s${RESET}" "$*"
|
||||||
|
}
|
||||||
|
# Print the provided text in a dim style using ANSI escape codes.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# DIM - ANSI escape code for applying dim styling.
|
||||||
|
# RESET - ANSI escape code to reset text formatting.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# $* - The text to be printed in dim style.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the formatted dim text to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# style::dim "This text will appear dimmed"
|
||||||
|
style::dim()
|
||||||
|
{
|
||||||
|
printf "${DIM}%s${RESET}" "$*"
|
||||||
|
}
|
||||||
|
# Prints the provided text in italic style using ANSI escape sequences.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# ITALIC - ANSI escape sequence for italic text styling.
|
||||||
|
# RESET - ANSI escape sequence to reset text styling.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# All passed arguments are combined and printed in italic formatting.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# The styled text is printed to STDOUT without an automatic newline.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# style::italic "Hello, world!"
|
||||||
|
style::italic()
|
||||||
|
{
|
||||||
|
printf "${ITALIC}%s${RESET}" "$*"
|
||||||
|
}
|
||||||
|
# Underlines the provided text using ANSI escape codes.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# UNDERLINE - ANSI escape sequence to start underlining.
|
||||||
|
# RESET - ANSI escape sequence to reset text formatting.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# $* - The text to be underlined.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Prints the underlined text to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# style::underline "Underlined text"
|
||||||
|
style::underline()
|
||||||
|
{
|
||||||
|
printf "${UNDERLINE}%s${RESET}" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints a formatted line to STDOUT using the provided format string and arguments.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# RESET - ANSI escape code to reset text formatting.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# $1 - A format string that may include ANSI styling codes (do not include a conversion specifier for the text).
|
||||||
|
# $@ - The text to be formatted and printed.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the formatted text to STDOUT with an appended newline, ensuring that styling is reset afterward.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# list::print_formatted "${BOLD}" "Bold Text"
|
||||||
|
list::print_formatted()
|
||||||
|
{
|
||||||
|
local format=$1
|
||||||
|
shift
|
||||||
|
printf "${format}%s${RESET}\n" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints a formatted header with a decorative underline.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# BOLD - ANSI escape code for bold text.
|
||||||
|
# BLUE - ANSI escape code for blue text.
|
||||||
|
# RESET - ANSI escape code to reset text formatting.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# $1 - The header title to be displayed.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes a styled header to STDOUT, including the title in bold blue and a subsequent decorative line.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# list::print_header "Available Commands"
|
||||||
|
list::print_header()
|
||||||
|
{
|
||||||
|
printf "\n ${BOLD}${BLUE}%s${RESET}\n" "$1"
|
||||||
|
printf "%s\n" " $(printf '%.s─' {1..60})"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints a group header with bold yellow formatting.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# YELLOW - ANSI escape code for yellow color.
|
||||||
|
# BOLD - ANSI escape code for bold text.
|
||||||
|
# RESET - ANSI escape code to reset text formatting.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# group - The title text to display as the group header.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the formatted group header to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# list::print_group "My Group"
|
||||||
|
list::print_group()
|
||||||
|
{
|
||||||
|
local group=$1
|
||||||
|
printf "\n ${YELLOW}${BOLD}%s${RESET}\n\n" "$group"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints a formatted command with an optional description.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# BOLD - ANSI escape sequence for bold text.
|
||||||
|
# CYAN - ANSI escape sequence for cyan text.
|
||||||
|
# RESET - ANSI escape sequence to reset text formatting.
|
||||||
|
# DIM - ANSI escape sequence for dim text.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# cmd - The command name to display.
|
||||||
|
# desc - Optional description of the command (defaults to an empty string).
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the formatted command and description to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# list::print_command "ls" "List directory contents"
|
||||||
|
list::print_command()
|
||||||
|
{
|
||||||
|
local cmd=$1
|
||||||
|
local desc=${2:-""}
|
||||||
|
printf " ${BOLD}${CYAN}%-15s${RESET} ${DIM}%s${RESET}\n" "$cmd" "$desc"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints a subcommand in a formatted style.
|
||||||
|
#
|
||||||
|
# This function displays a subcommand name in green with a fixed width for neat alignment,
|
||||||
|
# followed by an optional description text. The ANSI escape codes for green text and reset
|
||||||
|
# styling are used to highlight the subcommand.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# GREEN - ANSI escape code applied to the subcommand name.
|
||||||
|
# RESET - ANSI escape code used to reset text formatting.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# cmd - The subcommand name to print.
|
||||||
|
# desc - (Optional) A description string for the subcommand. Defaults to empty if not provided.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Prints the formatted subcommand and optional description to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# list::print_subcommand "deploy" "Deploy the application to the production server"
|
||||||
|
list::print_subcommand()
|
||||||
|
{
|
||||||
|
local cmd=$1
|
||||||
|
local desc=${2:-""}
|
||||||
|
printf " ${GREEN}%-13s${RESET} ${desc}\n" "$cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Iterates over functions defined in a command file and prints each as a formatted subcommand.
|
||||||
|
#
|
||||||
|
# This function reads function names from the specified command file, retrieves their descriptions
|
||||||
|
# (removing any '@description' prefix), and prints each function name as a bullet point with its
|
||||||
|
# associated description if available.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# cmd_file - The path to the command file containing function definitions.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Prints formatted subcommand entries to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# list::loop_functions "/path/to/command_file.sh"
|
||||||
|
list::loop_functions()
|
||||||
|
{
|
||||||
|
local cmd_file="$1"
|
||||||
|
while IFS= read -r func; do
|
||||||
|
# Get the function description from the function definition in the
|
||||||
|
# command file. If no description is found, print only the function name.
|
||||||
|
# The description is printed without the @description prefix.
|
||||||
|
# If the function is not found, print only the function name.
|
||||||
|
# The function name is printed with a bullet point.
|
||||||
|
local doc
|
||||||
|
doc=$(main::get_function_description "$cmd_file" "$func")
|
||||||
|
if [[ -n "$doc" ]]; then
|
||||||
|
list::print_subcommand "$func:" "${doc#*@description}"
|
||||||
|
else
|
||||||
|
list::print_subcommand "$func" ""
|
||||||
|
fi
|
||||||
|
done < <(main::get_command_functions "$cmd_file")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extracts and prints the documentation associated with a specific function from a command file.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# None
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# cmd_file - The file containing function definitions and their associated documentation.
|
||||||
|
# func - The name of the function whose documentation should be extracted.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the extracted documentation tags and their content to STDOUT.
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# None
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# list::get_function_docs "commands.sh" "build_project"
|
||||||
|
list::get_function_docs()
|
||||||
|
{
|
||||||
|
local cmd_file="$1"
|
||||||
|
local func="$2"
|
||||||
|
|
||||||
|
awk -v func="$func" '
|
||||||
|
# Start collecting documentation when a function is found and the line contains @
|
||||||
|
/^[[:space:]]*#[[:space:]]*@/ {
|
||||||
|
tag = $2
|
||||||
|
sub(/^[[:space:]]*#[[:space:]]*@[[:space:]]*[a-zA-Z]+[[:space:]]*/, "")
|
||||||
|
docs[tag] = $0
|
||||||
|
last_tag = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collect multi-line documentation
|
||||||
|
/^[[:space:]]*#/ && last_tag && !/^[[:space:]]*#[[:space:]]*@/ {
|
||||||
|
sub(/^[[:space:]]*#[[:space:]]*/, "")
|
||||||
|
docs[last_tag] = docs[last_tag] " " $0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Empty line or comment line ends documentation
|
||||||
|
!/^[[:space:]]*#/ {
|
||||||
|
last_tag = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# When the function is found, print the documentation
|
||||||
|
$0 ~ "^[[:space:]]*(function[[:space:]]+)?" func "\\(\\)" {
|
||||||
|
for (tag in docs) {
|
||||||
|
printf "@%s %s\n", tag, docs[tag]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$cmd_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if a command exists in the current environment and return 0 if it does.
|
||||||
|
# Otherwise, return 1.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# if utils::is_installed curl; then
|
||||||
|
# echo "curl is installed"
|
||||||
|
# else
|
||||||
|
# echo "curl is not installed"
|
||||||
|
# fi
|
||||||
|
#
|
||||||
|
# @description Check if a command exists
|
||||||
|
# @param $1 Command to check
|
||||||
|
# Checks if a specified command is available in the system.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# $1 - Command name to check.
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# 0 if the command is found in the system's PATH, 1 otherwise.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# utils::is_installed "git" && echo "Git is installed" || echo "Git is not installed"
|
||||||
|
utils::is_installed()
|
||||||
|
{
|
||||||
|
command -v "$1" > /dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if an executable exists in one of the directories listed in PATH and
|
||||||
|
# return 0 if it does. Otherwise, return 1.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# if utils::in_path ls; then
|
||||||
|
# echo "ls is available in PATH"
|
||||||
|
# else
|
||||||
|
# echo "ls is not available in PATH"
|
||||||
|
# fi
|
||||||
|
#
|
||||||
|
# @description Check if an executable is in PATH
|
||||||
|
# @param $1 Command to check
|
||||||
|
# Checks if a specified executable is available in one of the directories in the PATH.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# PATH - The system's PATH environment variable listing directories to search.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# cmd: The name of the executable file to look for.
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# 0 if the executable is found in one of the PATH directories, 1 otherwise.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# utils::in_path ls && echo "ls is available in PATH"
|
||||||
|
utils::in_path()
|
||||||
|
{
|
||||||
|
local cmd=$1
|
||||||
|
local result=1
|
||||||
|
IFS=: read -ra path <<< "$PATH"
|
||||||
|
for p in "${path[@]}"; do
|
||||||
|
if [[ -x "$p/$cmd" ]]; then
|
||||||
|
result=0
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# Retry a command until it succeeds or the maximum number of retries is reached.
|
||||||
|
# Logs a warning message if the command fails and is retried after a short delay.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# if utils::retry 3 curl -sSL https://example.com; then
|
||||||
|
# echo "Success"
|
||||||
|
# else
|
||||||
|
# echo "Failed"
|
||||||
|
# fi
|
||||||
|
#
|
||||||
|
# @description Retry a command
|
||||||
|
# @param $1 Maximum number of retries
|
||||||
|
# @param $2.. Command to run
|
||||||
|
# @return 0 if the command succeeds, 1 otherwise
|
||||||
|
# Retries a command until it succeeds or the maximum number of attempts is reached.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# tries - Maximum number of attempts to execute the command.
|
||||||
|
# command and its args - The command to run and any arguments to pass.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# logger::warn - Logs a warning message for each failed attempt.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Warning messages are printed to STDERR for each retry.
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# 0 if the command eventually succeeds; 1 if all attempts fail.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# utils::retry 3 my_command --option value
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# logger::warn
|
||||||
|
utils::retry()
|
||||||
|
{
|
||||||
|
local tries=$1
|
||||||
|
shift
|
||||||
|
local count=1
|
||||||
|
|
||||||
|
until "$@"; do
|
||||||
|
[[ $count -gt $tries ]] && return 1
|
||||||
|
logger::warn "Failed, retry $count/$tries"
|
||||||
|
((count++))
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ask for confirmation before proceeding. The default value is used if the user
|
||||||
|
# presses Enter without providing an answer.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# if utils::interactive::confirm "Are you sure?"; then
|
||||||
|
# echo "Confirmed"
|
||||||
|
# else
|
||||||
|
# echo "Not confirmed"
|
||||||
|
# fi
|
||||||
|
#
|
||||||
|
# @description Confirm an action
|
||||||
|
# @param $1 Prompt message
|
||||||
|
# @param $2 Default value
|
||||||
|
# Prompts the user for confirmation with a yes/no question.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# prompt: The message displayed to the user when asking for confirmation.
|
||||||
|
# default: An optional default answer used if no input is provided (defaults to "Y").
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Repeatedly prompts the user until a valid yes or no answer is received.
|
||||||
|
# An error message is displayed for any invalid response.
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# 0 if the user confirms (answers yes), 1 if the user declines (answers no).
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# if utils::interactive::confirm "Do you want to proceed?"; then
|
||||||
|
# echo "Proceeding..."
|
||||||
|
# else
|
||||||
|
# echo "Operation cancelled."
|
||||||
|
# fi
|
||||||
|
utils::interactive::confirm()
|
||||||
|
{
|
||||||
|
local prompt=$1
|
||||||
|
local default=${2:-Y}
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -rp "$prompt [Y/n]: " response
|
||||||
|
case ${response:-$default} in
|
||||||
|
[Yy]*) return 0 ;;
|
||||||
|
[Nn]*) return 1 ;;
|
||||||
|
*) echo "Please answer yes or no" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find all command files in the cmd directory and return them
|
||||||
|
# as a space-separated string of filenames (e.g. "cmd1.sh cmd2.sh").
|
||||||
|
#
|
||||||
|
# The function uses a while loop to read the output of the find command
|
||||||
|
# line by line. The -print0 option is used to separate the filenames with
|
||||||
|
# a null character (\0) instead of a newline. This is necessary to handle
|
||||||
|
# filenames with spaces correctly.
|
||||||
|
#
|
||||||
|
# The read command reads the null-separated filenames and appends them to
|
||||||
|
# the cmd_files array. Finally, the function prints the array elements
|
||||||
|
# separated by a space.
|
||||||
|
#
|
||||||
|
# Finds all command script files (*.sh) in the directory specified by DFM_CMD_DIR.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# DFM_CMD_DIR - The directory to search for command files.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Echoes a space-separated list of command file paths.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# files=$(main::find_commands)
|
||||||
|
# echo "$files" # Displays the list of found command files.
|
||||||
|
main::find_commands()
|
||||||
|
{
|
||||||
|
local cmd_files=()
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
cmd_files+=("$file")
|
||||||
|
done < <(find "$DFM_CMD_DIR" -type f -name "*.sh" -print0)
|
||||||
|
echo "${cmd_files[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the function names from a command file.
|
||||||
|
#
|
||||||
|
# The function uses grep to find function definitions (function xxx() or xxx())
|
||||||
|
# and sed to extract the function names. The function names are printed one per
|
||||||
|
# line.
|
||||||
|
#
|
||||||
|
# @param cmd_file The command file to extract function names from.
|
||||||
|
# Extracts the names of functions defined in the specified command file.
|
||||||
|
#
|
||||||
|
# This function parses the provided file for Bash function definitions using
|
||||||
|
# regex patterns matching both "function name() {" and "name() {" styles.
|
||||||
|
# It outputs the names of the functions, one per line.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# None.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# cmd_file - Path to the file containing Bash function definitions.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the list of function names to STDOUT.
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# A list of function names extracted from the file.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# main::get_command_functions "/path/to/command_file.sh"
|
||||||
|
main::get_command_functions()
|
||||||
|
{
|
||||||
|
local cmd_file="$1"
|
||||||
|
# Find function definitions (function xxx() or xxx())
|
||||||
|
grep -E '^[[:space:]]*(function[[:space:]]+)?[a-zA-Z0-9_]+\(\)[[:space:]]*{' "$cmd_file" \
|
||||||
|
| sed -E 's/^[[:space:]]*(function[[:space:]]+)?([a-zA-Z0-9_]+).*/\2/'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the description of a function from a command file.
|
||||||
|
#
|
||||||
|
# The function uses grep to find the function definition and sed to extract
|
||||||
|
# the description. The description is printed without the @description prefix.
|
||||||
|
#
|
||||||
|
# @param cmd_file The command file to extract the function description from.
|
||||||
|
# @param func The function name.
|
||||||
|
# Retrieves the annotated description of a specified function from a command file.
|
||||||
|
#
|
||||||
|
# This function searches the provided command file for an "@description" comment
|
||||||
|
# preceding the definition of the designated function. It then extracts and prints
|
||||||
|
# the description text. If no description is found, nothing is printed.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# cmd - Command name or path to the file containing the function definitions.
|
||||||
|
# func - Name of the function whose description is to be extracted.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# The extracted description text is printed to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# desc=$(main::get_function_description "install" "my_function")
|
||||||
|
main::get_function_description()
|
||||||
|
{
|
||||||
|
local cmd_file="$1"
|
||||||
|
local func="$2"
|
||||||
|
|
||||||
|
if [[ ! -f "$cmd_file" ]]; then
|
||||||
|
[[ -n ${DFM_CMD_DIR:-} ]] || return 1
|
||||||
|
cmd_file="${DFM_CMD_DIR}/${cmd_file}"
|
||||||
|
[[ "$cmd_file" == *.sh ]] || cmd_file="${cmd_file}.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -f "$cmd_file" ]] || return 1
|
||||||
|
|
||||||
|
local escaped_func
|
||||||
|
escaped_func=$(printf '%s' "$func" | sed 's/[][\\.^$*+?(){}|]/\\&/g')
|
||||||
|
|
||||||
|
grep -B5 -E "^[[:space:]]*(function[[:space:]]*)?${escaped_func}[[:space:]]*\\(\\)[[:space:]]*(\\{)?[[:space:]]*$" "$cmd_file" \
|
||||||
|
| grep "@description" \
|
||||||
|
| sed -E 's/^[[:space:]]*#[[:space:]]*@description[[:space:]]*//'
|
||||||
|
}
|
||||||
|
|
||||||
|
# List all available commands and their functions.
|
||||||
|
#
|
||||||
|
# The function uses main::find_commands to get a list of command files.
|
||||||
|
# It then iterates over the files and prints the command name and
|
||||||
|
# its functions.
|
||||||
|
#
|
||||||
|
# Lists all available commands and their subcommands.
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# Uses main::find_commands to locate command files and prints a header followed by a group title.
|
||||||
|
# For each command file, extracts the command name (removing the '.sh' extension) and prints it,
|
||||||
|
# then calls list::loop_functions to display detailed subcommands.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# None.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# None.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Writes the formatted list of commands and associated subcommands to STDOUT.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# main::list_available_commands
|
||||||
|
main::list_available_commands()
|
||||||
|
{
|
||||||
|
local cmd_files
|
||||||
|
cmd_files=$(main::find_commands)
|
||||||
|
|
||||||
|
list::print_header "dfm - dotfiles manager"
|
||||||
|
list::print_group "Available commands"
|
||||||
|
|
||||||
|
for cmd_file in $cmd_files; do
|
||||||
|
local cmd_name
|
||||||
|
cmd_name=$(basename "$cmd_file" .sh)
|
||||||
|
list::print_command "$cmd_name"
|
||||||
|
|
||||||
|
list::loop_functions "$cmd_file"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute a command function.
|
||||||
|
#
|
||||||
|
# The function loads the command file and checks if the function exists.
|
||||||
|
# If the function exists, it executes the function with the provided arguments.
|
||||||
|
#
|
||||||
|
# @param cmd The command name.
|
||||||
|
# @param func The function name.
|
||||||
|
# @param args The function arguments.
|
||||||
|
# Executes a specified function from a command file.
|
||||||
|
#
|
||||||
|
# This function validates and runs a function defined within a command file. It checks that both
|
||||||
|
# the command and function names contain only allowed characters (alphanumeric, underscores, or dashes),
|
||||||
|
# verifies that the command file (located in DFM_CMD_DIR) exists, is readable, and is free of syntax errors,
|
||||||
|
# and then sources the file. If the specified function exists in the file, it is executed with any additional
|
||||||
|
# arguments provided.
|
||||||
|
#
|
||||||
|
# Globals:
|
||||||
|
# DFM_CMD_DIR - Directory containing command files.
|
||||||
|
#
|
||||||
|
# Arguments:
|
||||||
|
# command: The command file name (without .sh extension) to execute. Must match ^[a-zA-Z0-9_-]+$.
|
||||||
|
# function: The function name to be executed from the command file. Must match ^[a-zA-Z0-9_-]+$.
|
||||||
|
# [additional arguments]: Extra parameters to pass to the executed function.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# Any output generated by the executed function. Error messages are output via lib::error.
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# 0 if the function executes successfully; 1 if an error occurs (e.g., invalid names, missing or unreadable
|
||||||
|
# command file, syntax errors in the command file, or if the specified function is not found).
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# main::execute_command "deploy" "run_deploy" "arg1" "arg2"
|
||||||
|
main::execute_command()
|
||||||
|
{
|
||||||
|
local cmd="$1"
|
||||||
|
shift
|
||||||
|
local func="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
# Validate input
|
||||||
|
if [[ ! "$cmd" =~ ^[a-zA-Z0-9_-]+$ ]] || [[ ! "$func" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
||||||
|
lib::error "Invalid command or function name"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local cmd_file="${DFM_CMD_DIR}/${cmd}.sh"
|
||||||
|
if [[ ! -f "$cmd_file" ]] || [[ ! -r "$cmd_file" ]]; then
|
||||||
|
lib::error "Command '$cmd' not found"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate command file
|
||||||
|
if ! bash -n "$cmd_file"; then
|
||||||
|
lib::error "Command file '$cmd' contains syntax errors"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Source the command file
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$cmd_file"
|
||||||
|
|
||||||
|
# Check if the function exists
|
||||||
|
if ! declare -f "$func" > /dev/null; then
|
||||||
|
lib::error "Function '$func' not found in command '$cmd'"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run the function with the provided arguments
|
||||||
|
"$func" "$@"
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"fix:markdown": "npx markdownlint -df .",
|
"fix:markdown": "npx markdownlint -df .",
|
||||||
"lint:prettier": "npx prettier . --check",
|
"lint:prettier": "npx prettier . --check",
|
||||||
"fix:prettier": "npx prettier . --write",
|
"fix:prettier": "npx prettier . --write",
|
||||||
"test": "echo \"Error: no test specified\" && exit 0"
|
"test": "npx bats --recursive tests/"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -29,7 +29,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ivuorinen/base-configs": "^2.0.0",
|
"@ivuorinen/base-configs": "^2.0.0",
|
||||||
"@types/node": "^24.0.1",
|
"@types/node": "^24.0.1",
|
||||||
|
"bats": "^1.12.0",
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.22"
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||||
}
|
}
|
||||||
|
|||||||
16
tests/dfm/helpers.bash
Normal file
16
tests/dfm/helpers.bash
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
run_with_dfm() {
|
||||||
|
local cmd="$*"
|
||||||
|
run env \
|
||||||
|
PROJECT_ROOT="$PROJECT_ROOT" \
|
||||||
|
DFM_CMD_DIR="$PROJECT_ROOT/local/dfm/cmd" \
|
||||||
|
DFM_LIB_DIR="$PROJECT_ROOT/local/dfm/lib" \
|
||||||
|
DOTFILES="${DOTFILES:-$PROJECT_ROOT}" \
|
||||||
|
NO_AUTOMATION="${NO_AUTOMATION:-1}" \
|
||||||
|
TEMP_DIR="$TEMP_DIR" \
|
||||||
|
bash -c 'set -e
|
||||||
|
cmd="$1"
|
||||||
|
source "$PROJECT_ROOT/local/dfm/lib/common.sh"
|
||||||
|
source "$PROJECT_ROOT/local/dfm/lib/utils.sh"
|
||||||
|
set +e
|
||||||
|
eval "$cmd"' bash "$cmd"
|
||||||
|
}
|
||||||
56
tests/dfm/main.bats
Normal file
56
tests/dfm/main.bats
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
load "$BATS_TEST_DIRNAME/helpers.bash"
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
set -euo pipefail
|
||||||
|
PROJECT_ROOT="$BATS_TEST_DIRNAME/../.."
|
||||||
|
TEMP_DIR="$(mktemp -d)"
|
||||||
|
export TEMP_DIR
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown() {
|
||||||
|
[[ -n "${TEMP_DIR:-}" ]] && rm -rf "$TEMP_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "list_available_commands shows commands" {
|
||||||
|
run_with_dfm main::list_available_commands
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
echo "$output" | grep -q "Available commands"
|
||||||
|
echo "$output" | grep -q "install"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "interactive confirm returns 0 on yes" {
|
||||||
|
run_with_dfm 'utils::interactive::confirm "Proceed?" <<< "y"; echo $?'
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ "${lines[-1]}" = "0" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "interactive confirm returns 1 on no" {
|
||||||
|
run_with_dfm 'utils::interactive::confirm "Proceed?" <<< "n"; echo $?'
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ "${lines[-1]}" = "1" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "execute_command runs function" {
|
||||||
|
run_with_dfm "main::execute_command install fonts"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
echo "$output" | grep -q "Installing fonts"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "execute_command fails on missing function" {
|
||||||
|
run_with_dfm "main::execute_command install nofunc >/dev/null 2>&1"
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "install all respects skip options" {
|
||||||
|
run_with_dfm "main::execute_command install all --no-brew --no-cargo --no-automation"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
echo "$output" | grep -q "Installing fonts"
|
||||||
|
[[ "$output" != *"Installing Homebrew"* ]]
|
||||||
|
[[ "$output" != *"Rust and cargo packages"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "get_function_description returns description" {
|
||||||
|
run_with_dfm "main::get_function_description \"$PROJECT_ROOT/local/dfm/cmd/install.sh\" fonts"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
echo "$output" | grep -q "Install all configured fonts"
|
||||||
|
}
|
||||||
@@ -1252,6 +1252,11 @@ balanced-match@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-3.0.1.tgz#e854b098724b15076384266497392a271f4a26a0"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-3.0.1.tgz#e854b098724b15076384266497392a271f4a26a0"
|
||||||
integrity sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==
|
integrity sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==
|
||||||
|
|
||||||
|
bats@^1.12.0:
|
||||||
|
version "1.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bats/-/bats-1.12.0.tgz#3ed99170325141e5d6dd53a84c3e5a702d5ab5be"
|
||||||
|
integrity sha512-1HTv2n+fjn3bmY9SNDgmzS6bjoKtVlSK2pIHON5aSA2xaqGkZFoCCWP46/G6jm9zZ7MCi84mD+3Byw4t3KGwBg==
|
||||||
|
|
||||||
before-after-hook@^4.0.0:
|
before-after-hook@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-4.0.0.tgz#cf1447ab9160df6a40f3621da64d6ffc36050cb9"
|
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-4.0.0.tgz#cf1447ab9160df6a40f3621da64d6ffc36050cb9"
|
||||||
|
|||||||
Reference in New Issue
Block a user