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