Last active
September 4, 2025 08:47
-
-
Save peter-mghendi/da608f065e7288981c325d01e94106fd to your computer and use it in GitHub Desktop.
A shell script that interactively lets you explore code stats for any public GitHub repo. Depends on `curl`, `gum` and `jq`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| #--- helpers ---------------------------------------------------------------# | |
| need() { command -v "$1" >/dev/null 2>&1 || { echo "Missing dependency: $1" >&2; exit 1; }; } | |
| die() { gum style --foreground 1 --bold "Error:" -- "$*"; exit 1; } | |
| need awk | |
| need curl | |
| need gum | |
| need jq | |
| #--- input -----------------------------------------------------------------# | |
| repo="${1:-}" | |
| if [[ -z "${repo}" ]]; then | |
| repo="$(gum input --placeholder 'owner/repo' --prompt 'Repo › ')" || true | |
| fi | |
| [[ -z "${repo}" ]] && die "No repository provided." | |
| url="https://api.codetabs.com/v1/loc/?github=${repo}" | |
| #--- fetch -----------------------------------------------------------------# | |
| json="$( | |
| gum spin --spinner line --title "Fetching LOC for ${repo}…" -- \ | |
| bash -c 'curl -fsSL "'"$url"'"' | |
| )" || die "Failed to fetch data for ${repo}." | |
| # Validate JSON (expecting an array of language objects) | |
| if ! echo "$json" | jq -e 'type == "array" and length > 0' >/dev/null 2>&1; then | |
| # Some repos return a single object with an error message; surface it if present. | |
| msg="$(echo "$json" | jq -r '(.message // .error // empty)' 2>/dev/null || true)" | |
| [[ -n "$msg" ]] && die "$msg" | |
| die "Unexpected response format." | |
| fi | |
| #--- choose language -------------------------------------------------------# | |
| # Build a list of languages and add an "All languages" option. | |
| mapfile -t langs < <(echo "$json" | jq -r '.[].language' | sort -u) | |
| choices=("All languages" "${langs[@]}") | |
| lang_choice="$( | |
| printf '%s\n' "${choices[@]}" \ | |
| | gum filter --placeholder "Select a language (or filter)…" --limit 1 | |
| )" || die "Selection cancelled." | |
| #--- view mode -------------------------------------------------------------# | |
| # Let user choose how to view the stats. | |
| view_choice="$( | |
| printf '%s\n' "Pretty table" "Raw JSON" "Compact line" \ | |
| | gum choose --cursor.foreground="212" --selected.foreground="212" --header "Display as:" | |
| )" || die "Selection cancelled." | |
| #--- render ----------------------------------------------------------------# | |
| render_table() { | |
| # Markdown table via gum format | |
| table_md() { | |
| if [[ "$lang_choice" == "All languages" ]]; then | |
| echo "$json" \ | |
| | jq -r ' | |
| sort_by(-.lines) | | |
| (["Language","Files","Lines","Blanks","Comments","LOC"] | @tsv), | |
| (.[] | [ .language, .files, .lines, .blanks, .comments, .linesOfCode ] | @tsv) | |
| ' \ | |
| | awk 'BEGIN{FS="\t"} | |
| NR==1{ | |
| printf "| %s | %s | %s | %s | %s | %s |\n",$1,$2,$3,$4,$5,$6 | |
| print "|---|---:|---:|---:|---:|---:|" | |
| next | |
| } | |
| { | |
| printf "| %s | %s | %s | %s | %s | %s |\n",$1,$2,$3,$4,$5,$6 | |
| }' | |
| else | |
| echo "$json" \ | |
| | jq -r --arg lang "$lang_choice" ' | |
| map(select(.language == $lang)) | | |
| (["Language","Files","Lines","Blanks","Comments","LOC"] | @tsv), | |
| (.[] | [ .language, .files, .lines, .blanks, .comments, .linesOfCode ] | @tsv) | |
| ' \ | |
| | awk 'BEGIN{FS="\t"} | |
| NR==1{ | |
| printf "| %s | %s | %s | %s | %s | %s |\n",$1,$2,$3,$4,$5,$6 | |
| print "|---|---:|---:|---:|---:|---:|" | |
| next | |
| } | |
| { | |
| printf "| %s | %s | %s | %s | %s | %s |\n",$1,$2,$3,$4,$5,$6 | |
| }' | |
| fi | |
| } | |
| gum format \ | |
| "# LOC for \`$repo\` | |
| $( [[ "$lang_choice" == "All languages" ]] && echo "" || echo "**Language:** \`$lang_choice\`" ) | |
| $(table_md) | |
| " | |
| } | |
| render_raw_json() { | |
| if [[ "$lang_choice" == "All languages" ]]; then | |
| echo "$json" | jq . | |
| else | |
| echo "$json" | jq --arg lang "$lang_choice" 'map(select(.language == $lang))' | |
| fi | |
| } | |
| render_compact() { | |
| if [[ "$lang_choice" == "All languages" ]]; then | |
| echo "$json" \ | |
| | jq -r ' | |
| sort_by(-.lines) | |
| | .[] | |
| | "\(.language): files=\(.files) lines=\(.lines) blanks=\(.blanks) comments=\(.comments) loc=\(.linesOfCode)"' | |
| else | |
| echo "$json" \ | |
| | jq -r --arg lang "$lang_choice" ' | |
| map(select(.language == $lang))[] | |
| | "\(.language): files=\(.files) lines=\(.lines) blanks=\(.blanks) comments=\(.comments) loc=\(.linesOfCode)"' | |
| fi \ | |
| | gum style --border normal --margin "1 0" --padding "1 2" --border-foreground 63 | |
| } | |
| case "$view_choice" in | |
| "Pretty table") render_table ;; | |
| "Raw JSON") render_raw_json ;; | |
| "Compact line") render_compact ;; | |
| esac |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment