Files
bin/doc_bash_generate.sh
Sebastian Krüger a71eef9006 Add utility scripts collection with auto-generated documentation
This commit introduces a comprehensive collection of utility scripts for shell
automation, color manipulation, and documentation generation:

Core Scripts:
- artifact_github_download.sh: Download GitHub Action artifacts via CLI
- css_color_filter.sh: Generate CSS filter values using SPSA algorithm
- css_color_palette.sh: Generate comprehensive color palettes (monochromatic, triadic, etc.)
- css_json_convert.sh: Convert CSS variables to JSON/YAML formats
- doc_bash_generate.sh: Auto-generate README.md with animated GIF demos
- doc_rust_generate.sh: Generate Rust project documentation
- jinja_template_render.sh: Render Jinja2 templates from CLI
- mime_mp4_gif.sh: Convert MP4 videos to GIF format

Documentation Features:
- Comprehensive README.md with table of contents
- 8 animated GIF demos showing real command examples
- Sandboxed demo execution in temporary directories
- 15-second timeout protection for intensive computations
- Automatic example extraction from --help output

Technical Implementation:
- Pure bash color utilities using only bc for arithmetic
- tput-based color codes for portability
- IFS-safe string parsing using parameter expansion
- Stdout/stderr isolation to prevent contamination
- Base64 encoding for multi-line text preservation

All scripts include detailed --help documentation with usage examples.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 03:10:19 +01:00

723 lines
18 KiB
Bash
Executable File

#!/usr/bin/env bash
#############################################################################
# Bash Documentation Generator with Animated GIFs
#
# Generate sexy, comprehensive README.md files with embedded asciinema GIFs
# for any POSIX-compliant executables (those with --help)
#
# Usage:
# doc_bash_generate.sh [OPTIONS] <executable> [executable...]
# doc_bash_generate.sh [OPTIONS] "*.sh"
#
# Arguments:
# executable One or more executables or glob patterns
#
# Options:
# -o, --output FILE Output README.md path (default: ./README.md)
# -t, --title TITLE Documentation title (default: auto-generated)
# --no-gif Skip GIF generation (faster, text only)
# --gif-only Only generate GIFs, don't update README
# -h, --help Show this help message
#
# Examples:
# doc_bash_generate.sh css_*.sh
# doc_bash_generate.sh -o docs/README.md *.sh
# doc_bash_generate.sh --title "My Awesome Tools" script1.sh script2.sh
#
# Dependencies:
# asciinema For terminal recording
# agg For converting asciinema to GIF (cargo install agg)
#
#############################################################################
set -euo pipefail
# ============================================================================
# Color Definitions
# ============================================================================
RED="" GREEN="" YELLOW="" BLUE="" MAGENTA="" CYAN="" BOLD="" DIM="" RESET=""
COLORS=0
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1; then
COLORS=$(tput colors 2>/dev/null || echo 0)
if [[ ${COLORS:-0} -ge 8 ]]; then
RED=$(tput setaf 1 2>/dev/null || echo "")
GREEN=$(tput setaf 2 2>/dev/null || echo "")
YELLOW=$(tput setaf 3 2>/dev/null || echo "")
BLUE=$(tput setaf 4 2>/dev/null || echo "")
MAGENTA=$(tput setaf 5 2>/dev/null || echo "")
CYAN=$(tput setaf 6 2>/dev/null || echo "")
BOLD=$(tput bold 2>/dev/null || echo "")
DIM=$(tput dim 2>/dev/null || echo "")
RESET=$(tput sgr0 2>/dev/null || echo "")
fi
fi
# ============================================================================
# Configuration
# ============================================================================
OUTPUT_FILE="$PWD/README.md"
DOC_TITLE=""
SKIP_GIF=false
GIF_ONLY=false
declare -a EXECUTABLES=()
TEMP_DIR=""
# ============================================================================
# Helper Functions
# ============================================================================
print_usage() {
cat << EOF
${BOLD}Bash Documentation Generator with Animated GIFs${RESET}
Generate sexy, comprehensive README.md files with embedded asciinema GIFs.
${BOLD}USAGE:${RESET}
$(basename "$0") [OPTIONS] <executable> [executable...]
${BOLD}ARGUMENTS:${RESET}
executable One or more executables or glob patterns
${BOLD}OPTIONS:${RESET}
-o, --output FILE Output README.md path (default: ./README.md)
-t, --title TITLE Documentation title (default: auto-generated)
--no-gif Skip GIF generation (faster, text only)
--gif-only Only generate GIFs, don't update README
-h, --help Show this help message
${BOLD}EXAMPLES:${RESET}
$(basename "$0") css_*.sh
$(basename "$0") -o docs/README.md *.sh
$(basename "$0") --title "My Awesome Tools" script1.sh script2.sh
${BOLD}DEPENDENCIES:${RESET}
asciinema Terminal session recorder
agg Asciinema to GIF converter (cargo install agg)
${BOLD}NOTES:${RESET}
Demos are automatically generated by running ${DIM}--help${RESET} on each command.
GIF recordings are created in a temporary directory and cleaned up after.
EOF
}
error() {
echo "${RED}${BOLD}Error:${RESET} $1" >&2
exit 1
}
info() {
echo "${BLUE}${BOLD}==>${RESET} $1" >&2
}
success() {
echo "${GREEN}${BOLD}[OK]${RESET} $1" >&2
}
warning() {
echo "${YELLOW}${BOLD}[WARN]${RESET} $1" >&2
}
banner() {
echo "" >&2
echo "${BOLD}${MAGENTA}================================================================${RESET}" >&2
echo "${BOLD}${MAGENTA} $1${RESET}" >&2
echo "${BOLD}${MAGENTA}================================================================${RESET}" >&2
echo "" >&2
}
cleanup() {
if [[ -n "${TEMP_DIR:-}" ]] && [[ -d "$TEMP_DIR" ]]; then
rm -rf "$TEMP_DIR"
fi
}
trap cleanup EXIT
check_dependencies() {
local missing=()
if ! command -v asciinema >/dev/null 2>&1; then
missing+=("asciinema")
fi
if ! command -v agg >/dev/null 2>&1 && [[ "$SKIP_GIF" == "false" ]]; then
warning "agg not found. Install with: cargo install agg"
warning "Continuing without GIF generation..."
SKIP_GIF=true
fi
if [[ ${#missing[@]} -gt 0 ]]; then
error "Missing required dependencies: ${missing[*]}"
fi
}
# ============================================================================
# Help Parsing Functions
# ============================================================================
get_command_help() {
local cmd="$1"
if [[ ! -x "$cmd" ]]; then
warning "Command not executable: $cmd"
return 1
fi
# Try --help, then -h, then fail
local help_output
if help_output=$("$cmd" --help 2>&1); then
echo "$help_output"
return 0
elif help_output=$("$cmd" -h 2>&1); then
echo "$help_output"
return 0
else
warning "Command doesn't support --help: $cmd"
return 1
fi
}
extract_description() {
local help_text="$1"
# Try to find first non-empty line after header/title
echo "$help_text" | grep -v "^#" | grep -v "^-" | grep -v "^=" | \
grep -E "^[A-Z]" | head -n 1 | sed 's/^[[:space:]]*//'
}
extract_usage() {
local help_text="$1"
# Find USAGE section
echo "$help_text" | sed -n '/USAGE:/,/^$/p' | grep -v "USAGE:" | \
sed 's/^[[:space:]]*//'
}
extract_examples() {
local help_text="$1"
# Find EXAMPLES section
echo "$help_text" | sed -n '/EXAMPLES:/,/^[A-Z]/p' | grep -v "EXAMPLES:" | \
grep -v "^[A-Z]" | sed 's/^[[:space:]]*//'
}
# ============================================================================
# Asciinema Recording Functions
# ============================================================================
extract_example_commands() {
local help_text="$1"
local cmd_name="$2"
# Extract example commands from EXAMPLES section
# Look for lines that start with the command name or common patterns
echo "$help_text" | sed -n '/EXAMPLES:/,/^[A-Z]/p' | \
grep -E "^\s*($cmd_name|#|$)" | \
grep -v "^#" | \
grep -v "EXAMPLES:" | \
grep -v "^[A-Z]" | \
sed 's/^[[:space:]]*//' | \
grep -v "^$" | \
head -n 2
}
create_demo() {
local cmd="$1"
local cmd_name=$(basename "$cmd")
# Create demo file in temp directory
local demo_file="$TEMP_DIR/${cmd_name%.sh}.demo"
# Get help text to extract examples
local help_text=$("$cmd" --help 2>&1 || true)
# Extract example commands from EXAMPLES section
local examples=$(echo "$help_text" | sed -n '/EXAMPLES:/,/^[A-Z]/p' | \
grep -E "^\s*$cmd_name" | head -n 2 | sed 's/^[[:space:]]*//')
cat > "$demo_file" << 'DEMO_HEADER'
#!/usr/bin/env bash
# Auto-generated demo
echo ""
echo "=== Demo: $CMD_NAME ==="
echo ""
DEMO_HEADER
# If we have examples, run them
if [[ -n "$examples" ]]; then
echo '# Show examples' >> "$demo_file"
echo 'echo ""' >> "$demo_file"
while IFS= read -r example; do
if [[ -n "$example" ]]; then
# Show the command being run
echo "echo '$ $example'" >> "$demo_file"
echo 'echo ""' >> "$demo_file"
# Run the command with timeout (15 seconds max)
# Replace the script name with full path
local full_example="${example/$cmd_name/$cmd}"
cat >> "$demo_file" << EOF
timeout 15s $full_example 2>&1 || {
exit_code=\$?
if [ \$exit_code -eq 124 ]; then
echo "[Example timed out after 15s - computation too intensive for demo]"
else
echo "[Command requires additional setup or arguments]"
fi
}
EOF
echo 'echo ""' >> "$demo_file"
fi
done <<< "$examples"
else
# Fallback to showing help if no examples found
echo 'echo "$ $CMD_NAME --help | head -n 20"' >> "$demo_file"
echo 'echo ""' >> "$demo_file"
echo "$cmd --help 2>&1 | head -n 20" >> "$demo_file"
fi
echo 'echo ""' >> "$demo_file"
# Replace placeholder with actual command name
sed -i "s|\$CMD_NAME|$cmd_name|g" "$demo_file"
chmod +x "$demo_file"
echo "$demo_file"
}
record_demo() {
local cmd="$1"
local output_cast="$2"
local demo_script="$3"
info "Recording demo for $(basename "$cmd")..."
# Create a sandbox directory for demo execution
local sandbox_dir="$TEMP_DIR/sandbox"
mkdir -p "$sandbox_dir"
# Create a wrapper script that runs the demo in sandbox
local wrapper="$TEMP_DIR/demo_wrapper.sh"
cat > "$wrapper" << 'WRAPPER_EOF'
#!/usr/bin/env bash
set -e
# Set terminal size for consistent output
export COLUMNS=100
export LINES=30
# Change to sandbox directory for safe execution
cd "$3"
# Add some style
echo -e "\033[1;36m"
echo "========================================"
echo " Demo: $(basename "$1")"
echo "========================================"
echo -e "\033[0m"
echo ""
sleep 1
# Source the demo script
source "$2"
echo ""
sleep 2
WRAPPER_EOF
chmod +x "$wrapper"
# Record with asciinema (redirect all output to avoid contamination)
asciinema rec \
--command "$wrapper '$cmd' '$demo_script' '$sandbox_dir'" \
--overwrite \
--cols 100 \
--rows 30 \
"$output_cast" >/dev/null 2>&1
if [[ $? -eq 0 ]]; then
success "Recorded: $output_cast"
return 0
else
warning "Failed to record demo for $(basename "$cmd")"
return 1
fi
}
convert_to_gif() {
local cast_file="$1"
local gif_file="$2"
info "Converting to GIF: $(basename "$gif_file")..."
if command -v agg >/dev/null 2>&1; then
agg \
--font-size 14 \
--speed 1.5 \
--theme monokai \
"$cast_file" \
"$gif_file" >/dev/null 2>&1
if [[ $? -eq 0 ]]; then
success "Created GIF: $(basename "$gif_file")"
return 0
fi
fi
warning "Failed to convert to GIF: $(basename "$cast_file")"
return 1
}
# ============================================================================
# README Generation Functions
# ============================================================================
generate_readme_header() {
local title="$1"
local output_dir=$(dirname "$OUTPUT_FILE")
cat << EOF
# $title
> Comprehensive documentation with usage examples and demos
This documentation was auto-generated using [\`doc_bash_generate.sh\`](https://github.com/yourusername/yourrepo).
## Table of Contents
EOF
}
generate_toc_entry() {
local cmd="$1"
local cmd_name=$(basename "$cmd")
local slug=$(echo "$cmd_name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g')
echo "- [\`$cmd_name\`](#$slug)"
}
generate_command_section() {
local cmd="$1"
local gif_path="$2"
local help_text="$3"
local cmd_name=$(basename "$cmd")
cat << EOF
---
## \`$cmd_name\`
EOF
# Add description
local description=$(extract_description "$help_text")
if [[ -n "$description" ]]; then
echo "$description"
echo ""
fi
# Add GIF if exists
if [[ -f "$gif_path" ]]; then
local gif_rel_path="docs/img/$(basename "$gif_path")"
cat << EOF
### Demo
![Demo of $cmd_name]($gif_rel_path)
EOF
fi
# Add usage section
cat << EOF
### Usage
\`\`\`bash
$cmd_name [OPTIONS] [ARGUMENTS]
\`\`\`
EOF
# Add full help output
cat << EOF
### Options
<details>
<summary>Click to expand full help output</summary>
\`\`\`
$help_text
\`\`\`
</details>
EOF
# Add examples if available
local examples=$(extract_examples "$help_text")
if [[ -n "$examples" ]]; then
cat << EOF
### Examples
\`\`\`bash
$examples
\`\`\`
EOF
fi
}
generate_readme_footer() {
cat << EOF
---
## Installation
All scripts are available in this repository. Make sure they are executable:
\`\`\`bash
chmod +x *.sh
\`\`\`
Add them to your PATH for easy access:
\`\`\`bash
export PATH="\$PATH:\$(pwd)"
\`\`\`
## Dependencies
Common dependencies for these scripts:
- \`bash\` (4.0+)
- \`bc\` - For floating-point arithmetic
Specific dependencies are listed in each command's help output.
## Contributing
Contributions are welcome! Please ensure:
- Scripts follow POSIX conventions
- Include comprehensive \`--help\` output with usage examples
## License
MIT License - See LICENSE file for details.
---
*Documentation generated on $(date '+%Y-%m-%d %H:%M:%S %Z') by [\`doc_bash_generate.sh\`](https://github.com/yourusername/yourrepo)*
EOF
}
# ============================================================================
# Main Processing Functions
# ============================================================================
process_command() {
local cmd="$1"
local cmd_name=$(basename "$cmd")
local output_dir=$(dirname "$OUTPUT_FILE")
local img_dir="$output_dir/docs/img"
info "Processing: ${BOLD}$cmd_name${RESET}"
# Create image directory
mkdir -p "$img_dir"
# Get help text
local help_text
if ! help_text=$(get_command_help "$cmd" 2>&1); then
warning "Skipping $cmd_name - no help available"
return 1
fi
# Setup paths
local cast_file="$TEMP_DIR/${cmd_name%.sh}.cast"
local gif_file="$img_dir/${cmd_name%.sh}.gif"
# Handle GIF generation
if [[ "$SKIP_GIF" == "false" ]]; then
# Create demo script in temp directory
local demo_script=$(create_demo "$cmd")
# Record and convert
if record_demo "$cmd" "$cast_file" "$demo_script"; then
convert_to_gif "$cast_file" "$gif_file" || true
fi
fi
# Return data for README generation (base64 encode help text to preserve newlines)
local encoded_help=$(echo "$help_text" | base64 -w 0)
echo "$cmd|$gif_file|$encoded_help"
}
build_readme() {
local -a command_data=("$@")
info "Building README.md..."
local title="${DOC_TITLE:-"Script Documentation"}"
local readme_content=""
# Generate header
readme_content+=$(generate_readme_header "$title")
readme_content+=$'\n'
# Generate TOC
for data in "${command_data[@]}"; do
# Split data using bash parameter expansion to avoid IFS issues
local cmd="${data%%|*}"
local rest="${data#*|}"
local gif_path="${rest%%|*}"
local encoded_help="${rest#*|}"
readme_content+=$(generate_toc_entry "$cmd")
readme_content+=$'\n'
done
readme_content+=$'\n'
# Generate command sections
for data in "${command_data[@]}"; do
# Split data using bash parameter expansion to avoid IFS issues
local cmd="${data%%|*}"
local rest="${data#*|}"
local gif_path="${rest%%|*}"
local encoded_help="${rest#*|}"
local help_text=$(echo "$encoded_help" | base64 -d 2>/dev/null || echo "")
readme_content+=$(generate_command_section "$cmd" "$gif_path" "$help_text")
readme_content+=$'\n'
done
# Generate footer
readme_content+=$(generate_readme_footer)
# Write to file
echo "$readme_content" > "$OUTPUT_FILE"
success "README generated: $OUTPUT_FILE"
}
# ============================================================================
# Argument Parsing
# ============================================================================
parse_arguments() {
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
print_usage
exit 0
;;
-o|--output)
OUTPUT_FILE="$2"
shift 2
;;
-t|--title)
DOC_TITLE="$2"
shift 2
;;
--no-gif)
SKIP_GIF=true
shift
;;
--gif-only)
GIF_ONLY=true
shift
;;
-*)
error "Unknown option: $1"
;;
*)
# Handle glob patterns
if [[ "$1" == *"*"* ]] || [[ "$1" == *"?"* ]]; then
# Expand glob
for file in $1; do
if [[ -f "$file" ]]; then
EXECUTABLES+=("$file")
fi
done
else
if [[ -f "$1" ]]; then
EXECUTABLES+=("$1")
else
warning "File not found: $1"
fi
fi
shift
;;
esac
done
if [[ ${#EXECUTABLES[@]} -eq 0 ]]; then
error "No executables specified. Use --help for usage information."
fi
}
# ============================================================================
# Main Script Logic
# ============================================================================
main() {
banner "Bash Documentation Generator"
parse_arguments "$@"
check_dependencies
# Create temp directory
TEMP_DIR=$(mktemp -d)
info "Output: $OUTPUT_FILE"
info "Commands: ${#EXECUTABLES[@]}"
echo ""
# Process all commands
local -a command_data=()
for cmd in "${EXECUTABLES[@]}"; do
local result
if result=$(process_command "$cmd"); then
command_data+=("$result")
fi
echo ""
done
if [[ ${#command_data[@]} -eq 0 ]]; then
error "No commands were successfully processed"
fi
# Build README unless gif-only mode
if [[ "$GIF_ONLY" == "false" ]]; then
build_readme "${command_data[@]}"
fi
echo ""
banner "Documentation Complete!"
echo "${BOLD}Generated Files:${RESET}"
if [[ "$GIF_ONLY" == "false" ]]; then
echo " ${GREEN}*${RESET} $OUTPUT_FILE"
fi
if [[ "$SKIP_GIF" == "false" ]]; then
local img_dir="$(dirname "$OUTPUT_FILE")/docs/img"
if [[ -d "$img_dir" ]]; then
local gif_count=$(find "$img_dir" -name "*.gif" 2>/dev/null | wc -l)
echo " ${GREEN}*${RESET} $gif_count GIF(s) in $img_dir"
fi
fi
echo ""
success "All done! Check out your sexy new documentation!"
}
main "$@"