1#!/usr/bin/env bash
2set -euo pipefail
3
4# Professional CLI Tool
5# Demonstrates a complete command-line interface with progress indicators,
6# colored output, and both short/long option parsing
7
8# ============================================================================
9# Script Metadata
10# ============================================================================
11
12readonly SCRIPT_NAME=$(basename "$0")
13readonly SCRIPT_VERSION="1.0.0"
14readonly SCRIPT_AUTHOR="Demo Author"
15
16# ============================================================================
17# Color definitions for output
18# ============================================================================
19
20readonly RED='\033[0;31m'
21readonly GREEN='\033[0;32m'
22readonly YELLOW='\033[1;33m'
23readonly BLUE='\033[0;34m'
24readonly CYAN='\033[0;36m'
25readonly MAGENTA='\033[0;35m'
26readonly BOLD='\033[1m'
27readonly DIM='\033[2m'
28readonly NC='\033[0m' # No Color
29
30# ============================================================================
31# Configuration Variables
32# ============================================================================
33
34VERBOSE=false
35QUIET=false
36OUTPUT_FILE=""
37INPUT_DIR="."
38DRY_RUN=false
39FORCE=false
40SHOW_PROGRESS=true
41
42# ============================================================================
43# Logging Functions
44# ============================================================================
45
46log_info() {
47 [[ "$QUIET" == true ]] && return
48 echo -e "${CYAN}ℹ${NC} $*"
49}
50
51log_success() {
52 [[ "$QUIET" == true ]] && return
53 echo -e "${GREEN}✓${NC} $*"
54}
55
56log_warning() {
57 echo -e "${YELLOW}⚠${NC} $*" >&2
58}
59
60log_error() {
61 echo -e "${RED}✗${NC} $*" >&2
62}
63
64log_verbose() {
65 [[ "$VERBOSE" == true ]] || return
66 echo -e "${DIM}[DEBUG]${NC} $*"
67}
68
69# ============================================================================
70# Progress Indicators
71# ============================================================================
72
73# Show a progress bar
74# Usage: show_progress <current> <total> <label>
75show_progress() {
76 local current=$1
77 local total=$2
78 local label="${3:-Processing}"
79
80 [[ "$SHOW_PROGRESS" == false ]] && return
81 [[ "$QUIET" == true ]] && return
82
83 local percent=$((current * 100 / total))
84 local filled=$((percent / 2))
85 local empty=$((50 - filled))
86
87 # Build progress bar
88 local bar=""
89 for ((i=0; i<filled; i++)); do bar+="█"; done
90 for ((i=0; i<empty; i++)); do bar+="░"; done
91
92 # Print progress (use \r to overwrite previous line)
93 printf "\r${CYAN}%s:${NC} [%s] %3d%% (%d/%d)" \
94 "$label" "$bar" "$percent" "$current" "$total"
95
96 # Add newline on completion
97 [[ $current -eq $total ]] && echo
98}
99
100# Show a spinner for long operations
101# Usage: run_with_spinner <command> [args...]
102run_with_spinner() {
103 [[ "$SHOW_PROGRESS" == false ]] && {
104 "$@"
105 return
106 }
107
108 [[ "$QUIET" == true ]] && {
109 "$@" 2>&1 | cat > /dev/null
110 return
111 }
112
113 local pid
114 local spin='-\|/'
115 local i=0
116
117 # Run command in background
118 "$@" &> /dev/null &
119 pid=$!
120
121 # Show spinner while command runs
122 while kill -0 "$pid" 2>/dev/null; do
123 i=$(((i+1) % 4))
124 printf "\r${CYAN}${spin:$i:1}${NC} Working..."
125 sleep 0.1
126 done
127
128 # Clear spinner
129 printf "\r"
130
131 # Check exit code
132 wait "$pid"
133 return $?
134}
135
136# ============================================================================
137# Usage and Help
138# ============================================================================
139
140usage() {
141 cat << EOF
142${BOLD}${BLUE}$SCRIPT_NAME${NC} - Professional CLI Tool Demo
143
144${BOLD}USAGE:${NC}
145 $SCRIPT_NAME [OPTIONS] [FILES...]
146
147${BOLD}DESCRIPTION:${NC}
148 A demonstration of a professional command-line tool with:
149 - Short and long option support
150 - Colored output and progress indicators
151 - Verbose/quiet modes
152 - Dry-run capability
153
154${BOLD}OPTIONS:${NC}
155 ${GREEN}-h, --help${NC} Show this help message
156 ${GREEN}-v, --verbose${NC} Enable verbose output
157 ${GREEN}-q, --quiet${NC} Suppress all output except errors
158 ${GREEN}-V, --version${NC} Show version information
159 ${GREEN}-o, --output FILE${NC} Write output to FILE
160 ${GREEN}-i, --input-dir DIR${NC} Set input directory (default: .)
161 ${GREEN}-n, --dry-run${NC} Show what would be done without doing it
162 ${GREEN}-f, --force${NC} Force operation without confirmation
163 ${GREEN}--no-progress${NC} Disable progress indicators
164
165${BOLD}EXAMPLES:${NC}
166 ${DIM}# Process files with verbose output${NC}
167 $SCRIPT_NAME -v file1.txt file2.txt
168
169 ${DIM}# Dry-run with output file${NC}
170 $SCRIPT_NAME --dry-run --output result.log *.txt
171
172 ${DIM}# Quiet mode with custom input directory${NC}
173 $SCRIPT_NAME -q -i /path/to/data
174
175 ${DIM}# Force operation without confirmation${NC}
176 $SCRIPT_NAME --force --output result.txt input/*
177
178${BOLD}EXIT CODES:${NC}
179 ${GREEN}0${NC} Success
180 ${RED}1${NC} General error
181 ${RED}2${NC} Invalid arguments
182
183EOF
184}
185
186show_version() {
187 cat << EOF
188${BOLD}$SCRIPT_NAME${NC} version ${GREEN}$SCRIPT_VERSION${NC}
189Written by $SCRIPT_AUTHOR
190EOF
191}
192
193# ============================================================================
194# Argument Parsing (Long and Short Options)
195# ============================================================================
196
197parse_arguments() {
198 local positional_args=()
199
200 while [[ $# -gt 0 ]]; do
201 case "$1" in
202 -h|--help)
203 usage
204 exit 0
205 ;;
206 -V|--version)
207 show_version
208 exit 0
209 ;;
210 -v|--verbose)
211 VERBOSE=true
212 QUIET=false
213 shift
214 ;;
215 -q|--quiet)
216 QUIET=true
217 VERBOSE=false
218 SHOW_PROGRESS=false
219 shift
220 ;;
221 -n|--dry-run)
222 DRY_RUN=true
223 shift
224 ;;
225 -f|--force)
226 FORCE=true
227 shift
228 ;;
229 --no-progress)
230 SHOW_PROGRESS=false
231 shift
232 ;;
233 -o|--output)
234 if [[ -z "${2:-}" ]]; then
235 log_error "Option $1 requires an argument"
236 exit 2
237 fi
238 OUTPUT_FILE="$2"
239 shift 2
240 ;;
241 -i|--input-dir)
242 if [[ -z "${2:-}" ]]; then
243 log_error "Option $1 requires an argument"
244 exit 2
245 fi
246 INPUT_DIR="$2"
247 shift 2
248 ;;
249 --)
250 shift
251 positional_args+=("$@")
252 break
253 ;;
254 -*)
255 log_error "Unknown option: $1"
256 echo "Use -h or --help for usage information"
257 exit 2
258 ;;
259 *)
260 positional_args+=("$1")
261 shift
262 ;;
263 esac
264 done
265
266 # Set global positional arguments
267 POSITIONAL_ARGS=("${positional_args[@]}")
268}
269
270# ============================================================================
271# Business Logic
272# ============================================================================
273
274validate_config() {
275 log_verbose "Validating configuration..."
276
277 # Check input directory
278 if [[ ! -d "$INPUT_DIR" ]]; then
279 log_error "Input directory does not exist: $INPUT_DIR"
280 return 1
281 fi
282
283 # Check output file parent directory
284 if [[ -n "$OUTPUT_FILE" ]]; then
285 local output_dir
286 output_dir=$(dirname "$OUTPUT_FILE")
287
288 if [[ ! -d "$output_dir" ]]; then
289 log_error "Output directory does not exist: $output_dir"
290 return 1
291 fi
292 fi
293
294 log_verbose "Configuration is valid"
295 return 0
296}
297
298display_config() {
299 log_verbose "=== Configuration ==="
300 log_verbose "Verbose: $VERBOSE"
301 log_verbose "Quiet: $QUIET"
302 log_verbose "Dry-run: $DRY_RUN"
303 log_verbose "Force: $FORCE"
304 log_verbose "Input dir: $INPUT_DIR"
305 log_verbose "Output file: ${OUTPUT_FILE:-<none>}"
306 log_verbose "Files: ${#POSITIONAL_ARGS[@]}"
307}
308
309process_files() {
310 local -a files=("${POSITIONAL_ARGS[@]}")
311
312 # If no files specified, use demo files
313 if [[ ${#files[@]} -eq 0 ]]; then
314 log_info "No files specified, using demo mode"
315 files=("file1.txt" "file2.txt" "file3.txt" "file4.txt" "file5.txt")
316 fi
317
318 local total=${#files[@]}
319 local current=0
320 local results=()
321
322 log_info "Processing $total file(s)..."
323
324 [[ "$DRY_RUN" == true ]] && log_warning "DRY-RUN MODE: No actual changes will be made"
325
326 for file in "${files[@]}"; do
327 ((current++))
328
329 log_verbose "Processing file $current/$total: $file"
330
331 # Show progress bar
332 show_progress "$current" "$total" "Progress"
333
334 # Simulate processing
335 if [[ "$DRY_RUN" == false ]]; then
336 sleep 0.2 # Simulate work
337 fi
338
339 results+=("Processed: $file")
340 done
341
342 echo # Newline after progress bar
343
344 # Save results if output file specified
345 if [[ -n "$OUTPUT_FILE" ]]; then
346 if [[ "$DRY_RUN" == true ]]; then
347 log_info "Would write results to: $OUTPUT_FILE"
348 else
349 log_info "Writing results to: $OUTPUT_FILE"
350
351 {
352 echo "# Processing Results"
353 echo "# Generated: $(date)"
354 echo "# Total files: $total"
355 echo
356 for result in "${results[@]}"; do
357 echo "$result"
358 done
359 } > "$OUTPUT_FILE"
360
361 log_success "Results saved to: $OUTPUT_FILE"
362 fi
363 fi
364
365 log_success "Processing complete! ($total file(s))"
366}
367
368# Simulate a long-running operation with spinner
369simulate_long_operation() {
370 log_info "Running long operation..."
371
372 if run_with_spinner sleep 2; then
373 log_success "Long operation completed"
374 else
375 log_error "Long operation failed"
376 return 1
377 fi
378}
379
380# ============================================================================
381# Main Execution
382# ============================================================================
383
384main() {
385 # Parse command-line arguments
386 declare -a POSITIONAL_ARGS=()
387 parse_arguments "$@"
388
389 # Show banner
390 if [[ "$QUIET" == false ]]; then
391 echo -e "${BOLD}${BLUE}╔════════════════════════════════════════════╗${NC}"
392 echo -e "${BOLD}${BLUE}║ Professional CLI Tool Demo ║${NC}"
393 echo -e "${BOLD}${BLUE}╚════════════════════════════════════════════╝${NC}"
394 echo
395 fi
396
397 # Display configuration
398 display_config
399
400 # Validate configuration
401 if ! validate_config; then
402 log_error "Configuration validation failed"
403 exit 1
404 fi
405
406 # Run long operation demo
407 simulate_long_operation
408
409 # Process files
410 process_files
411
412 echo
413 log_success "All operations completed successfully!"
414}
415
416main "$@"