mirror of
https://github.com/awalsh128/cache-apt-pkgs-action.git
synced 2025-12-26 13:21:33 +00:00
462 lines
11 KiB
Bash
Executable file
462 lines
11 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
#==============================================================================
|
|
# lib.sh
|
|
#==============================================================================
|
|
#
|
|
# DESCRIPTION:
|
|
# Enhanced common shell script library for project utilities and helpers.
|
|
# Provides functions for logging, error handling, argument parsing, file operations,
|
|
# command validation, and development workflow tasks.
|
|
#
|
|
# USAGE:
|
|
# source "$(cd "$(dirname "$0")" && pwd)/lib.sh"
|
|
#
|
|
# FEATURES:
|
|
# - Consistent logging and output formatting
|
|
# - Command existence and dependency checking
|
|
# - File and directory operations
|
|
# - Project structure helpers
|
|
# - Development tool installation helpers
|
|
# - Error handling and validation
|
|
#
|
|
#==============================================================================
|
|
|
|
# Exit on error by default for sourced scripts
|
|
set -eE -o functrace
|
|
|
|
# Detect debugging flag (bash -x) and also print line numbers
|
|
[[ $- == *"x"* ]] && PS4='+$(basename ${BASH_SOURCE[0]}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
|
|
|
|
# Global variables
|
|
export VERBOSE=${VERBOSE:-false}
|
|
export QUIET=${QUIET:-false}
|
|
export SCRIPT_DIRNAME="scripts"
|
|
|
|
#==============================================================================
|
|
# Logging Functions
|
|
#==============================================================================
|
|
|
|
export GREEN='\033[0;32m'
|
|
export RED='\033[0;31m'
|
|
export YELLOW='\033[0;33m'
|
|
export BLUE='\033[0;34m'
|
|
export CYAN='\033[0;36m'
|
|
export MAGENTA='\033[0;35m'
|
|
export NC='\033[0m' # No Color
|
|
export BOLD='\033[1m'
|
|
export DIM='\033[2m'
|
|
export BLINK='\033[5m'
|
|
|
|
echo_color() {
|
|
local echo_flags=()
|
|
# Collect echo flags (start with -)
|
|
while [[ $1 == -* ]]; do
|
|
if [[ $1 == "-e" || $1 == "-n" ]]; then
|
|
echo_flags+=("$1")
|
|
fi
|
|
shift
|
|
done
|
|
local color="$1"
|
|
local color_var
|
|
color_var=$(echo "${color}" | tr '[:lower:]' '[:upper:]')
|
|
shift
|
|
echo -e "${echo_flags[@]}" "${!color_var}$*${NC}"
|
|
}
|
|
|
|
#==============================================================================
|
|
# Logging Functions
|
|
#==============================================================================
|
|
|
|
log_info() {
|
|
if ! ${QUIET}; then
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
fi
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1" >&2
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
|
}
|
|
|
|
log_success() {
|
|
if ! ${QUIET}; then
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
fi
|
|
}
|
|
|
|
log_debug() {
|
|
if ${VERBOSE}; then
|
|
echo -e "${DIM}[DEBUG]${NC} $1" >&2
|
|
fi
|
|
}
|
|
|
|
# Print formatted headers
|
|
print_header() {
|
|
if ! ${QUIET}; then
|
|
echo -en "\n${BOLD}${BLUE}$1${NC}\n"
|
|
fi
|
|
}
|
|
|
|
print_section() {
|
|
if ! ${QUIET}; then
|
|
echo -en "\n${CYAN}${BOLD}$1${NC}\n\n"
|
|
fi
|
|
}
|
|
|
|
print_option() {
|
|
if ! ${QUIET}; then
|
|
echo -en "${YELLOW}$1)${CYAN} $2${NC}\n"
|
|
fi
|
|
}
|
|
|
|
print_status() {
|
|
if ! ${QUIET}; then
|
|
echo -en "${GREEN}==>${NC} $1\n"
|
|
fi
|
|
}
|
|
|
|
print_success() {
|
|
if ! ${QUIET}; then
|
|
echo -en "${GREEN}${BOLD}$1${NC}\n"
|
|
fi
|
|
}
|
|
|
|
#==============================================================================
|
|
# Error Handling
|
|
#==============================================================================
|
|
|
|
fail() {
|
|
# Usage: fail [message] [exit_code]
|
|
local msg="${1-}"
|
|
local exit_code="${2:-1}"
|
|
if [[ -n ${msg} ]]; then
|
|
log_error "${msg}"
|
|
fi
|
|
exit "${exit_code}"
|
|
}
|
|
|
|
# Trap handler for cleanup
|
|
cleanup_on_exit() {
|
|
local exit_code=$?
|
|
[[ -n ${TEMP_DIR} && -d ${TEMP_DIR} ]] && rm -rf "${TEMP_DIR}"
|
|
[[ ${exit_code} -eq 0 ]] && exit 0
|
|
local i
|
|
for ((i = ${#FUNCNAME[@]} - 1; i; i--)); do
|
|
echo "${BASH_SOURCE[i]}:${BASH_LINENO[i]}: ${FUNCNAME[i]}"
|
|
done
|
|
exit "${exit_code}"
|
|
}
|
|
|
|
setup_cleanup() {
|
|
trap 'cleanup_on_exit' EXIT
|
|
}
|
|
|
|
#==============================================================================
|
|
# Command and Dependency Checking
|
|
#==============================================================================
|
|
|
|
command_exists() {
|
|
command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
require_command() {
|
|
local cmd="$1"
|
|
local install_msg="${2:-Please install ${cmd}}"
|
|
|
|
if ! command_exists "${cmd}"; then
|
|
fail "${cmd} is required. ${install_msg}"
|
|
fi
|
|
log_debug "Found required command: ${cmd}"
|
|
}
|
|
|
|
require_script() {
|
|
local script="$1"
|
|
if [[ ! -x ${script} ]]; then
|
|
fail "${script} is required and must be executable. This script has a bug."
|
|
fi
|
|
log_debug "Found required script: ${script}"
|
|
}
|
|
|
|
npm_package_installed() {
|
|
npm list -g "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
go_tool_installed() {
|
|
go list -m "$1" >/dev/null 2>&1 || command_exists "$(basename "$1")"
|
|
}
|
|
|
|
#==============================================================================
|
|
# File and Directory Operations
|
|
#==============================================================================
|
|
|
|
file_exists() {
|
|
[[ -f $1 ]]
|
|
}
|
|
|
|
dir_exists() {
|
|
[[ -d $1 ]]
|
|
}
|
|
|
|
ensure_dir() {
|
|
[[ ! -d $1 ]] && mkdir -p "$1"
|
|
log_debug "Ensured directory exists: $1"
|
|
}
|
|
|
|
create_temp_dir() {
|
|
TEMP_DIR=$(mktemp -d)
|
|
log_debug "Created temporary directory: ${TEMP_DIR}"
|
|
echo "${TEMP_DIR}"
|
|
}
|
|
|
|
safe_remove() {
|
|
local path="$1"
|
|
if [[ -e ${path} ]]; then
|
|
rm -rf "${path}"
|
|
log_debug "Removed: ${path}"
|
|
fi
|
|
}
|
|
|
|
#==============================================================================
|
|
# Project Structure Helpers
|
|
#==============================================================================
|
|
|
|
get_project_root() {
|
|
local root
|
|
if command_exists git; then
|
|
root=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
|
fi
|
|
if [[ -n ${root} ]]; then
|
|
echo "${root}"
|
|
else
|
|
# Fallback to current working directory
|
|
pwd
|
|
fi
|
|
}
|
|
PROJECT_ROOT="$(get_project_root)"
|
|
export PROJECT_ROOT
|
|
|
|
#==============================================================================
|
|
# Development Tool Helpers
|
|
#==============================================================================
|
|
|
|
install_trunk() {
|
|
if command_exists trunk; then
|
|
log_debug "trunk already installed"
|
|
return 0
|
|
fi
|
|
|
|
log_info "Installing trunk..."
|
|
curl -fsSL https://get.trunk.io | bash
|
|
log_success "trunk installed successfully"
|
|
}
|
|
|
|
install_doctoc() {
|
|
require_command npm "Please install Node.js and npm first"
|
|
|
|
if npm_package_installed doctoc; then
|
|
log_debug "doctoc already installed"
|
|
return 0
|
|
fi
|
|
|
|
log_info "Installing doctoc..."
|
|
npm install -g doctoc
|
|
log_success "doctoc installed successfully"
|
|
}
|
|
|
|
install_go_tools() {
|
|
local tools=(
|
|
"golang.org/x/tools/cmd/goimports@latest"
|
|
"github.com/segmentio/golines@latest"
|
|
"github.com/golangci/golangci-lint/cmd/golangci-lint@latest"
|
|
)
|
|
|
|
log_info "Installing Go development tools..."
|
|
for tool in "${tools[@]}"; do
|
|
log_info "Installing $(basename "${tool}")..."
|
|
go install "${tool}"
|
|
done
|
|
log_success "Go tools installed successfully"
|
|
}
|
|
|
|
#==============================================================================
|
|
# Validation Helpers
|
|
#==============================================================================
|
|
|
|
validate_go_project() {
|
|
require_command go "Please install Go first"
|
|
|
|
local project_root
|
|
project_root=$(get_project_root)
|
|
|
|
if [[ ! -f "${project_root}/go.mod" ]]; then
|
|
fail "Not a Go project (no go.mod found)"
|
|
fi
|
|
|
|
log_debug "Validated Go project structure"
|
|
}
|
|
|
|
validate_git_repo() {
|
|
require_command git "Please install git first"
|
|
|
|
local project_root
|
|
project_root=$(get_project_root)
|
|
|
|
if [[ ! -d "${project_root}/.git" ]]; then
|
|
fail "Not a git repository"
|
|
fi
|
|
|
|
log_debug "Validated git repository"
|
|
}
|
|
|
|
#==============================================================================
|
|
# Common Argument Parsing
|
|
#==============================================================================
|
|
|
|
parse_common_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-h | --help)
|
|
[[ $(type -t show_help) == function ]] && show_help
|
|
exit 0
|
|
;;
|
|
-v | --verbose)
|
|
if [[ ${VERBOSE} == false ]]; then
|
|
export VERBOSE=true
|
|
log_debug "Verbose mode enabled"
|
|
fi
|
|
shift
|
|
;;
|
|
-q | --quiet)
|
|
export QUIET=true
|
|
shift
|
|
;;
|
|
*)
|
|
# Return unhandled arguments
|
|
break
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Return remaining arguments
|
|
# Echo any remaining unhandled arguments for callers to capture
|
|
if [[ $# -gt 0 ]]; then
|
|
echo "$@"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
#==============================================================================
|
|
# Common Operations
|
|
#==============================================================================
|
|
|
|
run_with_status() {
|
|
local description="$1"
|
|
shift
|
|
local cmd="$*"
|
|
|
|
print_status "${description}"
|
|
log_debug "Running: ${cmd}"
|
|
|
|
if eval "${cmd}"; then
|
|
log_success "${description} completed"
|
|
return 0
|
|
else
|
|
local exit_code=$?
|
|
log_error "${description} failed (exit code: ${exit_code})"
|
|
return "${exit_code}"
|
|
fi
|
|
}
|
|
|
|
update_go_modules() {
|
|
run_with_status "Updating Go modules" "go mod tidy && go mod verify"
|
|
}
|
|
|
|
run_tests() {
|
|
run_with_status "Running tests" "go test -v ./..."
|
|
}
|
|
|
|
run_build() {
|
|
run_with_status "Building project" "go build -v ./..."
|
|
}
|
|
|
|
run_lint() {
|
|
require_command trunk "Please install trunk first"
|
|
run_with_status "Running linting" "trunk check"
|
|
}
|
|
|
|
#==============================================================================
|
|
# Default Help Function
|
|
#==============================================================================
|
|
|
|
show_help() {
|
|
# Extract header comment block and format it
|
|
local script_file="${BASH_SOURCE[1]}"
|
|
|
|
if [[ ! -f ${script_file} ]]; then
|
|
echo "Help information not available"
|
|
return
|
|
fi
|
|
|
|
local lines=$'\n'
|
|
local inside_header=false
|
|
while IFS= read -r line; do
|
|
if [[ ${inside_header} == true ]]; then
|
|
[[ ${line} =~ ^#\=+ ]] && continue
|
|
if [[ ${line} =~ ^# ]]; then
|
|
lines+="${line#\#}"$'\n'
|
|
else
|
|
break
|
|
fi
|
|
fi
|
|
[[ ${line} =~ ^#\=+ ]] && inside_header=true
|
|
done <"${script_file}"
|
|
printf "%s" "${lines}"
|
|
}
|
|
|
|
#==============================================================================
|
|
# Utility Functions
|
|
#==============================================================================
|
|
|
|
pause() {
|
|
[[ ${QUIET} == true ]] && return
|
|
echo
|
|
read -n 1 -s -r -p "Press any key to continue..."
|
|
echo
|
|
}
|
|
|
|
confirm() {
|
|
local prompt="${1:-Are you sure?}"
|
|
local response
|
|
|
|
while true; do
|
|
read -rp "${prompt} (y/n): " response
|
|
case ${response} in
|
|
[Yy] | [Yy][Ee][Ss]) return 0 ;;
|
|
[Nn] | [Nn][Oo]) return 1 ;;
|
|
*) echo "Please answer yes or no." ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
#==============================================================================
|
|
# Initialization
|
|
#==============================================================================
|
|
|
|
# Set up cleanup trap when library is sourced
|
|
setup_cleanup
|
|
|
|
init() {
|
|
parse_common_args "$@"
|
|
if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
|
|
echo "This script should be sourced, not executed directly."
|
|
# shellcheck disable=SC2016
|
|
echo 'Usage: source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"/lib.sh'
|
|
exit 1
|
|
fi
|
|
}
|
|
# Do not auto-run init when this file is sourced; allow callers to invoke init() explicitly if needed.
|