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>
478 lines
14 KiB
Bash
Executable File
478 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
|
||
#############################################################################
|
||
# GitHub Artifact Downloader
|
||
#
|
||
# Download and extract GitHub Actions artifacts with style
|
||
#
|
||
# Usage:
|
||
# artifact_github_download.sh <REPO> [OPTIONS]
|
||
#
|
||
# Arguments:
|
||
# REPO GitHub repository (owner/repo)
|
||
#
|
||
# Options:
|
||
# -n, --name NAME Artifact name to download (preselect)
|
||
# -o, --output DIR Output directory (default: current directory)
|
||
# -h, --help Show this help message
|
||
#
|
||
# Examples:
|
||
# artifact_github_download.sh valknarness/awesome
|
||
# artifact_github_download.sh valknarness/awesome -n awesome-database-latest
|
||
# artifact_github_download.sh valknarness/awesome -o ~/downloads
|
||
#############################################################################
|
||
|
||
set -euo pipefail
|
||
|
||
# ============================================================================
|
||
# Color Definitions
|
||
# ============================================================================
|
||
|
||
# Check if terminal supports colors
|
||
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1; then
|
||
COLORS=$(tput colors 2>/dev/null || echo 0)
|
||
if [[ $COLORS -ge 8 ]]; then
|
||
# Standard colors
|
||
RED=$(tput setaf 1)
|
||
GREEN=$(tput setaf 2)
|
||
YELLOW=$(tput setaf 3)
|
||
BLUE=$(tput setaf 4)
|
||
MAGENTA=$(tput setaf 5)
|
||
CYAN=$(tput setaf 6)
|
||
WHITE=$(tput setaf 7)
|
||
|
||
# Bright colors
|
||
BRIGHT_GREEN=$(tput setaf 10 2>/dev/null || tput setaf 2)
|
||
BRIGHT_YELLOW=$(tput setaf 11 2>/dev/null || tput setaf 3)
|
||
BRIGHT_BLUE=$(tput setaf 12 2>/dev/null || tput setaf 4)
|
||
BRIGHT_MAGENTA=$(tput setaf 13 2>/dev/null || tput setaf 5)
|
||
BRIGHT_CYAN=$(tput setaf 14 2>/dev/null || tput setaf 6)
|
||
|
||
# Text formatting
|
||
BOLD=$(tput bold)
|
||
DIM=$(tput dim 2>/dev/null || echo "")
|
||
RESET=$(tput sgr0)
|
||
else
|
||
RED="" GREEN="" YELLOW="" BLUE="" MAGENTA="" CYAN="" WHITE=""
|
||
BRIGHT_GREEN="" BRIGHT_YELLOW="" BRIGHT_BLUE="" BRIGHT_MAGENTA="" BRIGHT_CYAN=""
|
||
BOLD="" DIM="" RESET=""
|
||
fi
|
||
else
|
||
RED="" GREEN="" YELLOW="" BLUE="" MAGENTA="" CYAN="" WHITE=""
|
||
BRIGHT_GREEN="" BRIGHT_YELLOW="" BRIGHT_BLUE="" BRIGHT_MAGENTA="" BRIGHT_CYAN=""
|
||
BOLD="" DIM="" RESET=""
|
||
fi
|
||
|
||
# ============================================================================
|
||
# Logging Functions
|
||
# ============================================================================
|
||
|
||
log_info() {
|
||
echo -e "${BRIGHT_BLUE}${BOLD}ℹ${RESET} ${CYAN}$*${RESET}" >&2
|
||
}
|
||
|
||
log_success() {
|
||
echo -e "${BRIGHT_GREEN}${BOLD}✓${RESET} ${GREEN}$*${RESET}" >&2
|
||
}
|
||
|
||
log_warning() {
|
||
echo -e "${BRIGHT_YELLOW}${BOLD}⚠${RESET} ${YELLOW}$*${RESET}" >&2
|
||
}
|
||
|
||
log_error() {
|
||
echo -e "${RED}${BOLD}✗${RESET} ${RED}$*${RESET}" >&2
|
||
}
|
||
|
||
log_step() {
|
||
echo -e "${BRIGHT_MAGENTA}${BOLD}▸${RESET} ${MAGENTA}$*${RESET}" >&2
|
||
}
|
||
|
||
log_header() {
|
||
local text="$*"
|
||
local length=${#text}
|
||
local line=$(printf '═%.0s' $(seq 1 $length))
|
||
echo "" >&2
|
||
echo -e "${BRIGHT_CYAN}${BOLD}╔${line}╗${RESET}" >&2
|
||
echo -e "${BRIGHT_CYAN}${BOLD}║${RESET}${BOLD}${WHITE}${text}${RESET}${BRIGHT_CYAN}${BOLD}║${RESET}" >&2
|
||
echo -e "${BRIGHT_CYAN}${BOLD}╚${line}╝${RESET}" >&2
|
||
echo "" >&2
|
||
}
|
||
|
||
log_data() {
|
||
local label="$1"
|
||
local value="$2"
|
||
echo -e " ${DIM}${label}:${RESET} ${BOLD}${value}${RESET}" >&2
|
||
}
|
||
|
||
# ============================================================================
|
||
# Helper Functions
|
||
# ============================================================================
|
||
|
||
check_dependencies() {
|
||
local missing=()
|
||
|
||
if ! command -v gh &> /dev/null; then
|
||
missing+=("gh (GitHub CLI)")
|
||
fi
|
||
|
||
if ! command -v jq &> /dev/null; then
|
||
missing+=("jq")
|
||
fi
|
||
|
||
if ! command -v unzip &> /dev/null; then
|
||
missing+=("unzip")
|
||
fi
|
||
|
||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||
log_error "Missing required dependencies:"
|
||
for dep in "${missing[@]}"; do
|
||
echo -e " ${RED}•${RESET} ${dep}"
|
||
done
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
check_gh_auth() {
|
||
if ! gh auth status &> /dev/null; then
|
||
log_error "Not authenticated with GitHub CLI"
|
||
log_info "Run: ${BOLD}gh auth login${RESET}"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
show_help() {
|
||
cat << EOF
|
||
${BOLD}${BRIGHT_CYAN}GitHub Artifact Downloader${RESET}
|
||
|
||
${BOLD}USAGE:${RESET}
|
||
$(basename "$0") ${CYAN}<REPO>${RESET} [${YELLOW}OPTIONS${RESET}]
|
||
|
||
${BOLD}ARGUMENTS:${RESET}
|
||
${CYAN}REPO${RESET} GitHub repository (${DIM}owner/repo${RESET})
|
||
|
||
${BOLD}OPTIONS:${RESET}
|
||
${YELLOW}-n, --name NAME${RESET} Artifact name to download (preselect)
|
||
${YELLOW}-o, --output DIR${RESET} Output directory (default: current directory)
|
||
${YELLOW}-h, --help${RESET} Show this help message
|
||
|
||
${BOLD}EXAMPLES:${RESET}
|
||
${DIM}# Interactive mode - list and select artifacts${RESET}
|
||
$(basename "$0") valknarness/awesome
|
||
|
||
${DIM}# Preselect artifact by name${RESET}
|
||
$(basename "$0") valknarness/awesome -n awesome-database-latest
|
||
|
||
${DIM}# Download to specific directory${RESET}
|
||
$(basename "$0") valknarness/awesome -o ~/downloads
|
||
|
||
${DIM}# Combine options${RESET}
|
||
$(basename "$0") valknarness/awesome -n awesome-database-latest -o ~/downloads
|
||
|
||
EOF
|
||
}
|
||
|
||
format_size() {
|
||
local bytes=$1
|
||
if (( bytes < 1024 )); then
|
||
echo "${bytes}B"
|
||
elif (( bytes < 1048576 )); then
|
||
awk "BEGIN {printf \"%.1fKB\", $bytes/1024}"
|
||
elif (( bytes < 1073741824 )); then
|
||
awk "BEGIN {printf \"%.1fMB\", $bytes/1048576}"
|
||
else
|
||
awk "BEGIN {printf \"%.2fGB\", $bytes/1073741824}"
|
||
fi
|
||
}
|
||
|
||
format_date() {
|
||
local iso_date="$1"
|
||
if command -v date &> /dev/null; then
|
||
if date --version &> /dev/null 2>&1; then
|
||
# GNU date
|
||
date -d "$iso_date" "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "$iso_date"
|
||
else
|
||
# BSD date (macOS)
|
||
date -j -f "%Y-%m-%dT%H:%M:%SZ" "$iso_date" "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "$iso_date"
|
||
fi
|
||
else
|
||
echo "$iso_date"
|
||
fi
|
||
}
|
||
|
||
# ============================================================================
|
||
# Main Functions
|
||
# ============================================================================
|
||
|
||
list_artifacts() {
|
||
local repo="$1"
|
||
|
||
log_step "Fetching artifacts from ${BOLD}${repo}${RESET}..."
|
||
|
||
# First check if there are any artifacts using gh's built-in jq
|
||
local count
|
||
count=$(gh api \
|
||
-H "Accept: application/vnd.github+json" \
|
||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||
--jq '.artifacts | length' \
|
||
"/repos/${repo}/actions/artifacts?per_page=100" 2>/dev/null)
|
||
|
||
if [[ -z "$count" ]]; then
|
||
log_error "Failed to fetch artifacts from repository"
|
||
log_info "Please check that:"
|
||
echo " • The repository ${BOLD}${repo}${RESET} exists and you have access"
|
||
echo " • GitHub Actions is enabled for this repository"
|
||
exit 1
|
||
fi
|
||
|
||
if [[ "$count" -eq 0 ]]; then
|
||
log_warning "No artifacts found in repository ${BOLD}${repo}${RESET}"
|
||
log_info "This repository may not have any workflow runs that produced artifacts"
|
||
exit 0
|
||
fi
|
||
|
||
# Now fetch the full JSON response
|
||
local artifacts_json
|
||
artifacts_json=$(gh api \
|
||
-H "Accept: application/vnd.github+json" \
|
||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||
"/repos/${repo}/actions/artifacts?per_page=100" 2>/dev/null)
|
||
|
||
echo "$artifacts_json"
|
||
}
|
||
|
||
select_artifact() {
|
||
local artifacts_json="$1"
|
||
local preselect_name="$2"
|
||
|
||
# Parse artifacts
|
||
local artifacts
|
||
artifacts=$(echo "$artifacts_json" | jq -r '.artifacts[] |
|
||
"\(.id)|\(.name)|\(.size_in_bytes)|\(.created_at)|\(.workflow_run.id)"')
|
||
|
||
# If preselect name is provided, find matching artifact
|
||
if [[ -n "$preselect_name" ]]; then
|
||
local selected
|
||
selected=$(echo "$artifacts" | grep -F "|${preselect_name}|" | head -1)
|
||
|
||
if [[ -z "$selected" ]]; then
|
||
log_error "Artifact '${BOLD}${preselect_name}${RESET}' not found"
|
||
log_info "Available artifacts:"
|
||
echo "$artifacts" | while IFS='|' read -r id name size created workflow; do
|
||
echo " ${CYAN}•${RESET} ${name}"
|
||
done
|
||
exit 1
|
||
fi
|
||
|
||
echo "$selected"
|
||
return 0
|
||
fi
|
||
|
||
# Interactive selection
|
||
log_info "Available artifacts:"
|
||
echo ""
|
||
|
||
local i=1
|
||
local -a artifact_array
|
||
|
||
while IFS='|' read -r id name size created workflow; do
|
||
artifact_array+=("$id|$name|$size|$created|$workflow")
|
||
local formatted_size=$(format_size "$size")
|
||
local formatted_date=$(format_date "$created")
|
||
|
||
printf " ${BOLD}${YELLOW}[%2d]${RESET} ${BRIGHT_CYAN}%s${RESET}\n" "$i" "$name"
|
||
printf " ${DIM}Size: ${RESET}%s ${DIM}Created: ${RESET}%s\n" "$formatted_size" "$formatted_date"
|
||
echo ""
|
||
|
||
((i++))
|
||
done <<< "$artifacts"
|
||
|
||
# Prompt for selection
|
||
local selection
|
||
while true; do
|
||
echo -n -e "${BRIGHT_MAGENTA}${BOLD}<EFBFBD>${RESET} ${MAGENTA}Select artifact [1-$((i-1))]:${RESET} "
|
||
read -r selection
|
||
|
||
if [[ "$selection" =~ ^[0-9]+$ ]] && [[ "$selection" -ge 1 ]] && [[ "$selection" -lt "$i" ]]; then
|
||
break
|
||
else
|
||
log_warning "Invalid selection. Please enter a number between 1 and $((i-1))"
|
||
fi
|
||
done
|
||
|
||
echo "${artifact_array[$((selection-1))]}"
|
||
}
|
||
|
||
download_artifact() {
|
||
local repo="$1"
|
||
local artifact_id="$2"
|
||
local artifact_name="$3"
|
||
local output_dir="$4"
|
||
|
||
log_step "Downloading artifact ${BOLD}${artifact_name}${RESET}..."
|
||
|
||
# Create output directory if it doesn't exist
|
||
mkdir -p "$output_dir"
|
||
|
||
# Download artifact using gh
|
||
local zip_file="${output_dir}/${artifact_name}.zip"
|
||
|
||
if gh api \
|
||
-H "Accept: application/vnd.github+json" \
|
||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||
"/repos/${repo}/actions/artifacts/${artifact_id}/zip" \
|
||
> "$zip_file" 2>/dev/null; then
|
||
|
||
log_success "Downloaded to ${BOLD}${zip_file}${RESET}"
|
||
echo "$zip_file"
|
||
else
|
||
log_error "Failed to download artifact"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
extract_artifact() {
|
||
local zip_file="$1"
|
||
local output_dir="$2"
|
||
|
||
log_step "Extracting archive..."
|
||
|
||
# Create extraction directory
|
||
local extract_dir="${output_dir}/$(basename "$zip_file" .zip)"
|
||
mkdir -p "$extract_dir"
|
||
|
||
if unzip -q "$zip_file" -d "$extract_dir"; then
|
||
log_success "Extracted to ${BOLD}${extract_dir}${RESET}"
|
||
|
||
# Show extracted files
|
||
log_info "Extracted files:"
|
||
find "$extract_dir" -type f -exec basename {} \; | while read -r file; do
|
||
echo " ${GREEN}•${RESET} ${file}"
|
||
done
|
||
|
||
# Remove zip file
|
||
rm "$zip_file"
|
||
log_info "Cleaned up zip file"
|
||
|
||
echo "$extract_dir"
|
||
else
|
||
log_error "Failed to extract archive"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# ============================================================================
|
||
# Main Script
|
||
# ============================================================================
|
||
|
||
main() {
|
||
local repo=""
|
||
local artifact_name=""
|
||
local output_dir="."
|
||
|
||
# Parse arguments
|
||
while [[ $# -gt 0 ]]; do
|
||
case $1 in
|
||
-h|--help)
|
||
show_help
|
||
exit 0
|
||
;;
|
||
-n|--name)
|
||
artifact_name="$2"
|
||
shift 2
|
||
;;
|
||
-o|--output)
|
||
output_dir="$2"
|
||
shift 2
|
||
;;
|
||
-*)
|
||
log_error "Unknown option: $1"
|
||
echo ""
|
||
show_help
|
||
exit 1
|
||
;;
|
||
*)
|
||
if [[ -z "$repo" ]]; then
|
||
repo="$1"
|
||
else
|
||
log_error "Unexpected argument: $1"
|
||
echo ""
|
||
show_help
|
||
exit 1
|
||
fi
|
||
shift
|
||
;;
|
||
esac
|
||
done
|
||
|
||
# Validate required arguments
|
||
if [[ -z "$repo" ]]; then
|
||
log_error "Repository argument is required"
|
||
echo ""
|
||
show_help
|
||
exit 1
|
||
fi
|
||
|
||
# Validate repository format
|
||
if [[ ! "$repo" =~ ^[^/]+/[^/]+$ ]]; then
|
||
log_error "Invalid repository format. Expected: ${BOLD}owner/repo${RESET}"
|
||
exit 1
|
||
fi
|
||
|
||
# Show header
|
||
log_header "GitHub Artifact Downloader"
|
||
|
||
# Check dependencies
|
||
log_step "Checking dependencies..."
|
||
check_dependencies
|
||
log_success "All dependencies found"
|
||
|
||
# Check GitHub authentication
|
||
log_step "Checking GitHub authentication..."
|
||
check_gh_auth
|
||
log_success "Authenticated with GitHub"
|
||
|
||
echo ""
|
||
log_data "Repository" "${BRIGHT_CYAN}${repo}${RESET}"
|
||
if [[ -n "$artifact_name" ]]; then
|
||
log_data "Artifact" "${BRIGHT_YELLOW}${artifact_name}${RESET}"
|
||
fi
|
||
log_data "Output" "${BRIGHT_GREEN}${output_dir}${RESET}"
|
||
echo ""
|
||
|
||
# List artifacts
|
||
local artifacts_json
|
||
artifacts_json=$(list_artifacts "$repo")
|
||
|
||
# Select artifact
|
||
local selected
|
||
selected=$(select_artifact "$artifacts_json" "$artifact_name")
|
||
|
||
IFS='|' read -r artifact_id name size created workflow <<< "$selected"
|
||
|
||
echo ""
|
||
log_info "Selected artifact:"
|
||
log_data " Name" "${BRIGHT_CYAN}${name}${RESET}"
|
||
log_data " Size" "$(format_size "$size")"
|
||
log_data " Created" "$(format_date "$created")"
|
||
echo ""
|
||
|
||
# Download artifact
|
||
local zip_file
|
||
zip_file=$(download_artifact "$repo" "$artifact_id" "$name" "$output_dir")
|
||
|
||
# Extract artifact
|
||
local extract_dir
|
||
extract_dir=$(extract_artifact "$zip_file" "$output_dir")
|
||
|
||
# Success summary
|
||
echo ""
|
||
log_header "Download Complete!"
|
||
log_data "Location" "${BOLD}${extract_dir}${RESET}"
|
||
echo ""
|
||
|
||
log_success "All done! 🎉"
|
||
}
|
||
|
||
# Run main function
|
||
main "$@"
|