Skip to content

Instantly share code, notes, and snippets.

@peter-mghendi
Last active September 4, 2025 08:47
Show Gist options
  • Select an option

  • Save peter-mghendi/da608f065e7288981c325d01e94106fd to your computer and use it in GitHub Desktop.

Select an option

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`.
#!/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