cache-apt-pkgs-action/scripts/dev/lib.sh
2026-01-05 16:36:23 -08:00

371 lines
8.9 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'
function 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
#==============================================================================
function log_info() {
if ! ${QUIET}; then
echo -e "${BLUE}[INFO]${NC} $1"
fi
}
function log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1" >&2
}
function log_error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
function log_success() {
if ! ${QUIET}; then
echo -e "${GREEN}[SUCCESS]${NC} $1"
fi
}
function log_debug() {
if ${VERBOSE}; then
echo -e "${DIM}[DEBUG]${NC} $1" >&2
fi
}
# Print formatted headers
function print_header() {
if ! ${QUIET}; then
echo -en "\n${BOLD}${BLUE}$1${NC}\n"
fi
}
function print_section() {
if ! ${QUIET}; then
echo -en "\n${CYAN}${BOLD}$1${NC}\n\n"
fi
}
function print_option() {
if ! ${QUIET}; then
echo -en "${YELLOW}$1)${CYAN} $2${NC}\n"
fi
}
function print_status() {
if ! ${QUIET}; then
echo -en "${GREEN}==>${NC} $1\n"
fi
}
function print_success() {
if ! ${QUIET}; then
echo -en "${GREEN}${BOLD}$1${NC}\n"
fi
}
#==============================================================================
# Error Handling
#==============================================================================
function 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
function 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}"
}
function setup_cleanup() {
trap 'cleanup_on_exit' EXIT
}
#==============================================================================
# Command and Dependency Checking
#==============================================================================
function command_exists() {
command -v "$1" >/dev/null 2>&1
}
function 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}"
}
function 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}"
}
function npm_package_installed() {
npm list -g "$1" >/dev/null 2>&1
}
function go_tool_installed() {
go list -m "$1" >/dev/null 2>&1 || command_exists "$(basename "$1")"
}
#==============================================================================
# File and Directory Operations
#==============================================================================
function file_exists() {
[[ -f $1 ]]
}
function dir_exists() {
[[ -d $1 ]]
}
function ensure_dir() {
[[ ! -d $1 ]] && mkdir -p "$1"
log_debug "Ensured directory exists: $1"
}
function create_temp_dir() {
TEMP_DIR=$(mktemp -d)
log_debug "Created temporary directory: ${TEMP_DIR}"
echo "${TEMP_DIR}"
}
function safe_remove() {
local path="$1"
if [[ -e ${path} ]]; then
rm -rf "${path}"
log_debug "Removed: ${path}"
fi
}
#==============================================================================
# Project Structure
#==============================================================================
function 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
#==============================================================================
function install_trunk() {
if command_exists trunk; then
log_debug "trunk already installed"
return 0
fi
log_info "Installing trunk..."
# trunk-ignore(semgrep/bash.curl.security.curl-pipe-bash.curl-pipe-bash)
curl -fsSL https://get.trunk.io | bash
log_success "trunk installed successfully"
}
function 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"
}
function 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
#==============================================================================
function 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"
}
function 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 Operations
#==============================================================================
function 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
}
function update_go_modules() {
run_with_status "Updating Go modules" "go mod tidy && go mod verify"
}
function run_tests() {
run_with_status "Running tests" "go test -v ./..."
}
function run_build() {
run_with_status "Building project" "go build -v ./..."
}
function run_lint() {
require_command trunk "Please install trunk first"
run_with_status "Running linting" "trunk check"
}
#==============================================================================
# Initialization
#==============================================================================
# Set up cleanup trap when library is sourced
setup_cleanup
function 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.