testsAndMisc/scripts/utils/install_exercism.sh
Copilot faff8ba349 Fix shell script formatting and add PR workflow validation (#3)
* Initial plan

* fix: format shell scripts with shfmt (convert tabs to 2 spaces)

Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com>

* feat: enhance shell-check workflow for PR pre-merge validation

- Add pull_request_target trigger to check PRs from forks
- Add explicit failure message with instructions
- Create BRANCH_PROTECTION.md with setup guide
- Ensure workflow runs on all PRs targeting main/master

Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com>

* refactor: improve workflow security and remove redundant exit code

- Remove pull_request_target to avoid executing untrusted fork code
- Remove redundant exit 1 from failure step
- Update documentation to reflect changes
- Standard pull_request trigger handles forks securely

Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com>
2026-01-07 22:52:20 +01:00

320 lines
9.0 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Install Exercism CLI - Offline Coding Challenges
#
# Exercism is a free, open source platform with:
# - 65+ programming languages
# - Built-in test suites for each exercise
# - Works offline after downloading exercises
#
# Website: https://exercism.org
# License: AGPL-3.0
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
success() { echo -e "${GREEN}$1${NC}"; }
warn() { echo -e "${YELLOW}$1${NC}"; }
error() { echo -e "${RED}$1${NC}"; }
info() { echo -e "${BLUE} $1${NC}"; }
EXERCISM_DIR="${HOME}/exercism"
echo "=============================================="
echo " Exercism - Offline Coding Challenges"
echo " Free & Open Source with Built-in Tests"
echo "=============================================="
echo ""
# Install Exercism CLI
install_exercism_cli() {
if command -v exercism &> /dev/null; then
local version
version=$(exercism version 2> /dev/null | head -1)
success "Exercism CLI already installed: $version"
return 0
fi
echo "Installing Exercism CLI..."
# Try package managers first
if command -v pacman &> /dev/null; then
# Check AUR
if command -v yay &> /dev/null; then
yay -S --noconfirm exercism-bin
success "Exercism CLI installed via AUR"
return 0
elif command -v paru &> /dev/null; then
paru -S --noconfirm exercism-bin
success "Exercism CLI installed via AUR"
return 0
fi
elif command -v brew &> /dev/null; then
brew install exercism
success "Exercism CLI installed via Homebrew"
return 0
fi
# Manual installation from GitHub releases
info "Installing from GitHub releases..."
local arch
case "$(uname -m)" in
x86_64) arch="x86_64" ;;
aarch64 | arm64) arch="arm64" ;;
armv7l) arch="armv7" ;;
i686) arch="i386" ;;
*)
error "Unsupported architecture: $(uname -m)"
return 1
;;
esac
local os="linux"
[[ "$(uname -s)" == "Darwin" ]] && os="darwin"
# Get latest release
local latest_url="https://api.github.com/repos/exercism/cli/releases/latest"
local download_url
download_url=$(curl -fsSL "$latest_url" | grep "browser_download_url.*${os}-${arch}" | head -1 | cut -d '"' -f 4)
if [[ -z $download_url ]]; then
error "Could not find download URL for your system"
echo "Please install manually from: https://exercism.org/docs/using/solving-exercises/working-locally"
return 1
fi
echo "Downloading from: $download_url"
local temp_dir
temp_dir=$(mktemp -d)
curl -fL --progress-bar "$download_url" -o "$temp_dir/exercism.tar.gz"
tar -xzf "$temp_dir/exercism.tar.gz" -C "$temp_dir"
# Install to ~/.local/bin
mkdir -p "$HOME/.local/bin"
mv "$temp_dir/exercism" "$HOME/.local/bin/"
chmod +x "$HOME/.local/bin/exercism"
rm -rf "$temp_dir"
success "Exercism CLI installed to ~/.local/bin/exercism"
# Check PATH
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
warn "Add ~/.local/bin to your PATH:"
echo ' export PATH="$HOME/.local/bin:$PATH"'
fi
}
# Configure exercism workspace
configure_exercism() {
echo ""
echo "=== Configuring Exercism ==="
mkdir -p "$EXERCISM_DIR"
# Check if already configured
if exercism configure 2>&1 | grep -q "workspace"; then
success "Exercism already configured"
else
# Set workspace directory
exercism configure --workspace="$EXERCISM_DIR"
success "Workspace set to: $EXERCISM_DIR"
fi
echo ""
info "To fully configure Exercism with your account:"
echo " 1. Create free account at https://exercism.org"
echo " 2. Go to https://exercism.org/settings/api_cli"
echo " 3. Copy your API token"
echo " 4. Run: exercism configure --token=YOUR_TOKEN"
echo ""
}
# Install test runners for languages
install_test_runners() {
echo ""
echo "=== Installing Test Runners ==="
echo ""
# Python - pytest
if command -v python3 &> /dev/null; then
if python3 -c "import pytest" 2> /dev/null; then
success "Python: pytest already installed"
else
info "Installing pytest for Python exercises..."
pip3 install --user pytest 2> /dev/null && success "Python: pytest installed" || warn "Python: install pytest manually"
fi
fi
# JavaScript/TypeScript - Node.js + npm
if command -v node &> /dev/null; then
success "JavaScript/TypeScript: Node.js available ($(node --version))"
info " Tests run with: npm test (or jest)"
else
warn "JavaScript/TypeScript: Install Node.js for JS/TS exercises"
fi
# C - gcc + criterion/cmocka
if command -v gcc &> /dev/null; then
success "C: gcc available"
info " Some C exercises use Unity test framework (included in exercise)"
else
warn "C: Install gcc for C exercises"
fi
# C++ - g++ + Catch2/doctest
if command -v g++ &> /dev/null; then
success "C++: g++ available"
info " C++ exercises use Catch2 (header-only, included in exercise)"
else
warn "C++: Install g++ for C++ exercises"
fi
# Rust
if command -v cargo &> /dev/null; then
success "Rust: cargo available (tests with: cargo test)"
fi
# Go
if command -v go &> /dev/null; then
success "Go: go available (tests with: go test)"
fi
}
# Download exercises for a track (language)
download_track() {
local track="$1"
local count="${2:-10}"
echo ""
info "Downloading $count exercises for $track track..."
# Get list of exercises
local exercises
exercises=$(curl -fsSL "https://exercism.org/api/v2/tracks/${track}/exercises" 2> /dev/null |
grep -oP '"slug":"\K[^"]+' | head -n "$count")
if [[ -z $exercises ]]; then
warn "Could not fetch exercise list for $track"
return 1
fi
local downloaded=0
for exercise in $exercises; do
local exercise_dir="$EXERCISM_DIR/$track/$exercise"
if [[ -d $exercise_dir ]]; then
echo " [exists] $exercise"
else
if exercism download --track="$track" --exercise="$exercise" 2> /dev/null; then
echo " [downloaded] $exercise"
((downloaded++))
else
echo " [failed] $exercise (may require auth)"
fi
fi
done
success "Downloaded $downloaded new exercises for $track"
}
# Show available tracks and usage
show_usage() {
echo ""
echo "=============================================="
echo " Exercism Usage Guide"
echo "=============================================="
echo ""
echo -e "${CYAN}Download exercises:${NC}"
echo " exercism download --track=python --exercise=hello-world"
echo " exercism download --track=javascript --exercise=two-fer"
echo " exercism download --track=c --exercise=isogram"
echo ""
echo -e "${CYAN}Run tests locally:${NC}"
echo " Python: cd ~/exercism/python/hello-world && pytest"
echo " JavaScript: cd ~/exercism/javascript/hello-world && npm test"
echo " TypeScript: cd ~/exercism/typescript/hello-world && npm test"
echo " C: cd ~/exercism/c/hello-world && make test"
echo " C++: cd ~/exercism/cpp/hello-world && make"
echo " Rust: cd ~/exercism/rust/hello-world && cargo test"
echo " Go: cd ~/exercism/go/hello-world && go test"
echo ""
echo -e "${CYAN}Submit solution (when online):${NC}"
echo " exercism submit solution.py"
echo ""
echo -e "${CYAN}Popular tracks:${NC}"
echo " python, javascript, typescript, c, cpp, rust, go, java, ruby"
echo " bash, elixir, haskell, kotlin, swift, csharp, php, sql"
echo ""
echo -e "${CYAN}Batch download (requires API token):${NC}"
echo " # Download first 20 Python exercises:"
echo ' for ex in $(exercism download --track=python 2>&1 | head -20); do'
echo ' exercism download --track=python --exercise=$ex'
echo " done"
echo ""
echo "Exercises are in: $EXERCISM_DIR"
echo ""
echo "=============================================="
}
# Main
main() {
# Step 1: Install CLI
echo ""
echo "=== Step 1: Installing Exercism CLI ==="
install_exercism_cli
# Step 2: Configure
configure_exercism
# Step 3: Install test runners
install_test_runners
# Step 4: Download sample exercises
echo ""
echo "=== Step 4: Downloading Sample Exercises ==="
echo ""
echo "Downloading a few starter exercises for common languages..."
echo "(Full download requires API token from exercism.org)"
echo ""
# Try to download hello-world for each track
local tracks=("python" "javascript" "typescript" "c" "cpp")
for track in "${tracks[@]}"; do
local exercise_dir="$EXERCISM_DIR/$track/hello-world"
if [[ -d $exercise_dir ]]; then
echo " [$track] hello-world already exists"
else
if exercism download --track="$track" --exercise="hello-world" 2> /dev/null; then
success "[$track] hello-world downloaded"
else
warn "[$track] hello-world requires authentication"
fi
fi
done
# Show usage
show_usage
echo ""
success "Installation complete!"
echo ""
echo "Next steps:"
echo " 1. Sign up at https://exercism.org (free)"
echo " 2. Get your token from https://exercism.org/settings/api_cli"
echo " 3. Run: exercism configure --token=YOUR_TOKEN"
echo " 4. Download exercises and code offline!"
echo ""
}
main "$@"