cli_tool.sh

Download
bash 417 lines 11.2 KB
  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 "$@"