main
ec1f561 · 3 months ago 21 commits
  1#!/bin/bash
  2
  3# ============================================================================
  4# Common Functions Library
  5# ============================================================================
  6# Usage: source common_functions.sh
  7# This file contains reusable functions for bash scripts
  8
  9# Colors for better visual feedback
 10export RED='\033[0;31m'
 11export GREEN='\033[0;32m'
 12export BLUE='\033[0;34m'
 13export YELLOW='\033[1;33m'
 14export CYAN='\033[0;36m'
 15export MAGENTA='\033[0;35m'
 16export WHITE='\033[1;37m'
 17export NC='\033[0m'
 18
 19# Animation characters - different styles available
 20export SPINNER_DOTS="⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
 21export SPINNER_CIRCLE="◐◓◑◒"
 22export SPINNER_BLOCKS="⣾⣽⣻⢿⡿⣟⣯⣷"
 23
 24# Default spinner style
 25SPINNER="$SPINNER_DOTS"
 26
 27# Logging functions
 28timestamp() {
 29    date "+%Y-%m-%d %H:%M:%S"
 30}
 31
 32log(){
 33    local show="$1"
 34    if [ "$show" = true ]; then
 35        echo -e "$2"
 36    fi
 37    echo -e "[$(timestamp)] $2" >> ${LOGFILE:-"script.log"}
 38}
 39
 40log_info() {
 41    echo -e "${BLUE}[ INFO ] $1${NC} "
 42    echo -e "${BLUE}[$(timestamp)] [ INFO ]${NC} $1" >> ${LOGFILE:-"script.log"}
 43}
 44
 45log_success() {
 46    echo -e "${GREEN}[ SUCC ] $1${NC} "
 47    echo -e "${GREEN}[$(timestamp)] [ SUCC ]${NC} $1" >> ${LOGFILE:-"script.log"}
 48}
 49
 50log_warning() {
 51    echo -e "${YELLOW}[ WARN ] $1${NC} "
 52    echo -e "${YELLOW}[$(timestamp)] [ WARN ]${NC} $1" >> ${LOGFILE:-"script.log"}
 53}
 54
 55log_error() {
 56    echo -e "${RED}[ ERRR ] $1${NC} "
 57    echo -e "${RED}[$(timestamp)] [ ERRR ]${NC} $1" >> ${LOGFILE:-"script.log"}
 58}
 59
 60log_debug() {
 61    if [ "${DEBUG:-}" = "true" ]; then
 62        echo -e "${CYAN}[$(timestamp)] [ DEBG ]${NC} $1"
 63        echo -e "${CYAN}[$(timestamp)] [ DEBG ]${NC} $1" >> ${LOGFILE:-"script.log"}
 64    fi
 65}
 66
 67set_spinner_style() {
 68    case "$1" in
 69        "dots") SPINNER="$SPINNER_DOTS" ;;
 70        "circle") SPINNER="$SPINNER_CIRCLE" ;;
 71        "blocks") SPINNER="$SPINNER_BLOCKS" ;;
 72        *) SPINNER="$SPINNER_DOTS" ;;
 73    esac
 74}
 75
 76show_spinner() {
 77    local pid=$1
 78    local message="$2"
 79    local i=0
 80    
 81    printf "${BLUE}%s${NC} " "$message"
 82    
 83    while kill -0 $pid 2>/dev/null; do
 84        printf "\r${BLUE}%s${NC} %s" "$message" "${SPINNER:$i:1}"
 85        i=$(( (i + 1) % ${#SPINNER} ))
 86        sleep 0.1
 87    done
 88    
 89    wait $pid
 90    local exit_code=$?
 91    
 92    if [ $exit_code -eq 0 ]; then
 93        printf "\r${BLUE}%s${NC} ${GREEN}${NC}\n" "$message"
 94        return 0
 95    else
 96        printf "\r${BLUE}%s${NC} ${RED}${NC}\n" "$message"
 97        return 1
 98    fi
 99}
100
101# jus execute the cmd given with spinner
102execute_step() {
103    local command="$1"
104    local message="$2"
105    local allow_failure="${3:-false}"
106
107    log false "$message : Executing command: $command"
108    
109    # command in background, suppress all output
110    eval "$command" >/dev/null 2>&1 &
111    local pid=$!
112    
113    if show_spinner $pid "$message"; then
114        return 0
115    else
116        if [ "$allow_failure" = "true" ]; then
117            log_warning "$message failed (continuing anyway)"
118            return 1
119        else
120            log_error "$message failed"
121            exit 1
122        fi
123    fi
124}
125
126# same as above but finally spits the output
127execute_with_output() {
128    local command="$1"
129    local message="$2"
130    local temp_file
131    temp_file=$(mktemp)
132    
133    eval "$command" > "$temp_file" 2>&1 &
134    local pid=$!
135    
136    if show_spinner $pid "$message"; then
137        cat "$temp_file"
138        rm -f "$temp_file"
139        return 0
140    else
141        echo -e "${RED}Command output:${NC}"
142        cat "$temp_file"
143        rm -f "$temp_file"
144        return 1
145    fi
146}
147
148# display header - success - message 
149show_box() {
150    local title="$1"
151    local width="${2:-40}"
152    local color="${3:-YELLOW}"
153    local details="$4"
154
155    local color_code="${!color:-$YELLOW}"
156
157    echo
158    printf "${color_code}"
159    printf "═%.0s" $(seq 1 $((width - 2)))
160    printf "${NC}\n"
161
162    local padding=$(( (width - ${#title} - 2) / 2 ))
163    printf "${color_code}"
164    printf " %.0s" $(seq 1 $padding)
165    printf "%s" "$title"
166    printf " %.0s" $(seq 1 $((width - ${#title} - padding - 2)))
167    printf "${NC}\n"
168
169    printf "${color_code}"
170    printf "═%.0s" $(seq 1 $((width - 2)))
171    printf "${NC}\n"
172
173    if [ -n "$details" ]; then
174        echo
175        echo "$details"
176    fi
177
178    echo
179}
180
181command_exists() {
182    command -v "$1" >/dev/null 2>&1
183}
184
185ensure_directory() {
186    local dir="$1"
187    local message="${2:-Creating directory: $dir}"
188    
189    if [ ! -d "$dir" ]; then
190        printf "${BLUE}%s${NC} " "$message"
191        if mkdir -p "$dir" 2>/dev/null; then
192            printf "${GREEN}${NC}\n"
193        else
194            printf "${RED}${NC}\n"
195            log_error "Failed to create directory: $dir"
196            return 1
197        fi
198    fi
199}
200
201backup_file() {
202    local file="$1"
203    local backup_suffix="${2:-.bak}"
204    
205    if [ -f "$file" ]; then
206        local backup_file="${file}${backup_suffix}"
207        printf "${BLUE}Backing up %s${NC} " "$(basename "$file")"
208        if cp "$file" "$backup_file" 2>/dev/null; then
209            printf "${GREEN}${NC}\n"
210        else
211            printf "${RED}${NC}\n"
212            return 1
213        fi
214    fi
215}
216
217show_progress() {
218    local current="$1"
219    local total="$2" 
220    local message="${3:-Progress}"
221    local width=24
222    
223    local percentage=$((current * 100 / total))
224    local filled=$((current * width / total))
225    local empty=$((width - filled))
226    
227    printf "\r${BLUE}%s${NC} [" "$message"
228    printf "${GREEN}█%.0s${NC}" $(seq 1 $filled)
229    printf "░%.0s" $(seq 1 $empty)
230    printf "] %d%%" "$percentage"
231    
232    if [ "$current" -eq "$total" ]; then
233        printf " ${GREEN}${NC}\n"
234    fi
235}
236
237ask_yes_no() {
238    local question="$1"
239    local default="${2:-n}"
240    local response
241    
242    while true; do
243        if [ "$default" = "y" ]; then
244            printf "${YELLOW}%s [Y/n]: ${NC}" "$question"
245        else
246            printf "${YELLOW}%s [y/N]: ${NC}" "$question"
247        fi
248        
249        read -r response
250        response=${response:-$default}
251        
252        case "$response" in
253            [Yy]|[Yy][Ee][Ss]) return 0 ;;
254            [Nn]|[Nn][Oo]) return 1 ;;
255            *) echo -e "${RED}Please answer yes or no.${NC}" ;;
256        esac
257    done
258}
259
260setup_cleanup_trap() {
261    local cleanup_function="$1"
262
263    if ! command_exists "$cleanup_function"; then
264        log_error "Cleanup function '$cleanup_function' does not exist"
265        return 1
266    fi
267    
268    cleanup_handler() {
269        echo
270        log_warning "Script ended. Running cleanup..."
271        "$cleanup_function"
272        exit 1
273    }
274    
275    trap cleanup_handler INT TERM 
276}
277
278validate_url() {
279    local url="$1"
280    if [[ $url =~ ^https?:// ]]; then
281        return 0
282    else
283        return 1
284    fi
285}
286
287check_disk_space() {
288    local required_mb="$1"
289    local path="${2:-.}"
290    
291    if command_exists df; then
292        local available_kb
293        available_kb=$(df "$path" | awk 'NR==2 {print $4}')
294        local available_mb=$((available_kb / 1024))
295        
296        if [ "$available_mb" -lt "$required_mb" ]; then
297            log_warning "Low disk space: ${available_mb}MB available, ${required_mb}MB required"
298            return 1
299        fi
300    fi
301    return 0
302}
303
304prompt_input() {
305  local PROMPT="$1"
306  local VAR_NAME="$2"
307
308  echo -ne "${YELLOW}${PROMPT}${NC}: "
309  read "$VAR_NAME"
310}
311
312prompt_masked_password() {
313    local prompt_text="${1:-Password: }"
314    local password=""
315    
316    echo -en "${YELLOW}$prompt_text${NC}: "
317    
318    stty -echo
319    
320    # Read password character by character
321    while IFS= read -r -n1 -s char; do
322        # Check for Enter key (newline)
323        if [[ -z $char ]] || [[ $char == $'\n' ]] || [[ $char == $'\r' ]]; then
324            break
325        fi
326        
327        # Check for Backspace
328        if [[ $char == $'\177' ]] || [[ $char == $'\b' ]]; then
329            if [[ ${#password} -gt 0 ]]; then
330                password="${password%?}"  # Remove last character
331                echo -ne '\b \b'          # Move cursor back, print space, move back again
332            fi
333        else
334            password+="$char"
335            echo -n "*"
336        fi
337    done
338    
339    stty echo
340    echo  
341
342    if [[ -n "$2" ]]; then
343        eval "$2=\"$password\""
344    else
345        echo $password
346    fi
347}
348
349
350# ========== Cursor Positioning Functions ==========
351
352get_cursor_line() {
353    exec < /dev/tty
354    local oldstty=$(stty -g)
355    stty raw -echo min 0 time 5
356    printf '\033[6n' > /dev/tty
357
358    IFS=';' read -r -d R -a pos
359    stty "$oldstty"
360
361    echo "${pos[0]#*[}"
362}
363
364
365move_cursor_to_line() {
366    local target_line="$1"
367    tput cup $((target_line - 1)) 0
368}
369
370
371clear_from_line_to_bottom() {
372    local start_line="$1"
373    move_cursor_to_line "$start_line"
374    tput ed  # Clear from cursor to bottom of screen
375}
376
377
378clear_screen_from_given_line() {
379    local line="$1"
380
381    if ! [[ "$line" =~ ^[0-9]+$ ]]; then
382        echo "Invalid line number: $line"
383        return 1
384    fi
385
386    clear_from_line_to_bottom "$line"
387}
388
389# ===================================================
390
391# ========= THIS FUNCTION IS FOR SPECIFIC PURPOSE OF SHOWING PROGRESS BAR AT TOP==========
392initialize_progress_bar(){
393    printf "\033[${progress_line};1H"
394    show_progress "$current_step" "$total_steps"
395    echo -e "\n\n"
396}
397
398run_step() {
399    local cmd="$1"
400    local msg="$2"
401    execute_step "$cmd" "$msg"
402    ((current_step++))
403    printf "\033[s"                  # Save cursor
404    printf "\033[${progress_line};1H"
405    show_progress "$current_step" "$total_steps"
406    printf "\033[u"                  # Restore cursor
407}
408# ================================================
409
410
411init_common_functions() {
412    set_spinner_style "dots"
413    export -f show_spinner show_box show_progress
414    export -f log_info log_success log_warning log_error log_debug
415    export -f execute_step execute_with_output command_exists 
416    export -f ensure_directory backup_file ask_yes_no validate_url
417    export -f check_disk_space
418}
419
420init_common_functions