testsAndMisc/linux_configuration/scripts/single_use/utils/install_exercism.sh
Krzysztof Rudnicki b83ce5d3c7
Some checks failed
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
billsplit CI / quality (push) Has been cancelled
reverse-survivors CI / check (push) Has been cancelled
Fail the build on any shellcheck finding; fix the 222 the old gate hid
The shellcheck hook ran with --severity=warning, so info- and style-level
findings never failed a commit. 222 had accumulated across 94 of 244 shell
files. They were not all style noise -- six were bugs:

  - Five scripts in single_use/misc/testsAndMisc-bash/ sourced
    $SCRIPT_DIR/../../lib/common.sh, one directory too shallow. Nothing exists
    at that path, so all five failed at runtime.
  - setup_personal_website.sh and setup_kcd2_dice_solver.sh called usage()
    (which ends in `exit 0`) and then `exit 1` for an unknown command. The
    exit 1 was unreachable, so a typo'd subcommand reported SUCCESS.
  - Six i3blocks scripts had a `# shellcheck source=` directive missing the
    scripts/periodic_background/ path segment. The stale path had silently
    disabled source-following for all of them.
  - diagnose_arch_performance.sh word-split a newline-joined file list; when
    the find came back empty, awk got no file operands, fell through to
    reading stdin, and the diagnostic hung.
  - lib/common.sh's handle_arg_help_or_unknown ended in an unreachable
    `return 0` while documenting "Returns: 0 if handled" -- a contract no code
    path could satisfy.
  - deploy.sh's SCRIPT_DIR is reassigned by config.sh, which it sources at top
    level, and config.sh can repoint it at the on-device path
    /data/local/tmp/focus_mode. Renamed to DEPLOY_DIR.

The SC1091 bulk (81 findings) was resolved rather than silenced: .shellcheckrc
sets external-sources + source-path=SCRIPTDIR so sourced files are actually
followed and analysed. That is what surfaced the i3blocks and deploy.sh bugs.

17 `# shellcheck disable=` directives remain, each with a written reason, and
only where the finding is provably wrong: injection-test payloads that must not
expand, literal source text being grepped for, POSIX sh scripts on the phone
where arrays do not exist, and source paths that only exist at runtime.
Everything else was fixed in code.

Verified in a CLEAN worktree, not just my own tree. config.sh sources the
gitignored config_secrets.sh, which exists locally but not in a fresh checkout;
a working-tree-only check passed while ci_mirror.sh -- which lints HEAD in a
clean worktree, exactly as CI does -- failed on it.

The gate is verified to bite: a file whose only finding is SC2016 (info) fails
the hook, while the old --severity=warning gate exits 0 on it.

third_party/ is excluded -- vendored upstream, matching the prettier hook.

Not fixed, reported: check_polling_antipatterns.sh carries a pre-existing
SC2034 disable that hides a real bug -- its `patterns` array is never used, so
the checker detects nothing. Confirmed unchanged by this commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 19:49:11 +02:00

331 lines
9.1 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:"
# Printing a line for the USER to paste into their shell rc: $HOME and
# $PATH must appear literally, not be resolved to this machine's values.
# shellcheck disable=SC2016
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..."
if pip3 install --user pytest 2>/dev/null; then
success "Python: pytest installed"
else
warn "Python: install pytest manually"
fi
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:"
# Example commands printed for the user to copy; $(...) and $ex must stay
# literal rather than execute here.
# shellcheck disable=SC2016
echo ' for ex in $(exercism download --track=python 2>&1 | head -20); do'
# shellcheck disable=SC2016
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 "$@"