Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 85 additions & 2 deletions .github/workflows/profile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,16 @@ on:
description: "What he says when you don't."
type: string
default: "...i'll fix that, promise"
stats_banner:
description: "Path for the standalone stats banner (no character). Empty
= don't build it. Set it and the workflow fills your all-time numbers
and draws it — you add nothing to awan.json."
type: string
default: ""

env:
# The release this copy of the file was cut with. Bump it when you tag.
AWAN_VERSION: v0.0.5
AWAN_VERSION: v0.0.6

jobs:
awan:
Expand Down Expand Up @@ -135,20 +141,97 @@ jobs:
' "$CONFIG" > awan.tmp && mv awan.tmp "$CONFIG"
rm -f cal.json wall.json

# The stats banner wants numbers the one-year calendar can't give — an
# all-time commit count from your first commit, and your longest streak
# ever — so this walks the contribution calendar year by year, from the
# year you joined to now, and writes stat_boxes into your config. Only runs
# when you asked for the banner, so nobody else pays for the extra calls.
- name: Fill the stats banner numbers
if: inputs.stats_banner != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
USER: ${{ inputs.user || github.repository_owner }}
CONFIG: ${{ inputs.config }}
run: |
set -euo pipefail
FROM=$(gh api "users/$USER" --jq '.created_at' | cut -c1-4)
TO=$(date -u +%Y)
COMMITS=0
echo '[]' > days.json
for Y in $(seq "$FROM" "$TO"); do
gh api graphql -f query='
{ user(login: "'"$USER"'") { contributionsCollection(
from: "'"$Y"'-01-01T00:00:00Z", to: "'"$Y"'-12-31T23:59:59Z") {
totalCommitContributions
contributionCalendar { weeks { contributionDays { date contributionCount } } }
} } }' > y.json || continue
C=$(jq '.data.user.contributionsCollection.totalCommitContributions // 0' y.json)
COMMITS=$((COMMITS + C))
jq -s '.[0] + (.[1].data.user.contributionsCollection.contributionCalendar.weeks
| map(.contributionDays[]?))' days.json y.json > d.tmp && mv d.tmp days.json
done

# sort the days, then in one pass: the earliest active day (first
# commit), and the longest run of consecutive active days with its ends.
jq 'sort_by(.date) | unique_by(.date)
| (map(select(.contributionCount > 0)) | .[0].date) as $first
| reduce .[] as $d ({run: [], best: []};
if $d.contributionCount > 0
then (.run + [$d.date]) as $r
| {run: $r, best: (if ($r|length) > (.best|length) then $r else .best end)}
else {run: [], best: .best} end) as $s
| {first: $first, len: ($s.best|length),
from: ($s.best[0] // ""), to: ($s.best[-1] // "")}' \
days.json > life.json

fmt() { date -u -d "$1" +"%-d %b %Y" 2>/dev/null || echo "$1"; }
fmts() { date -u -d "$1" +"%-d %b" 2>/dev/null || echo "$1"; }

STREAK=$(jq -r '.streak // 0' "$CONFIG")
if [ "$STREAK" -gt 0 ]; then
SSTART="since $(date -u -d "-$((STREAK-1)) days" +"%-d %b")"
else
SSTART="not today"
fi
FIRST=$(jq -r '.first // ""' life.json)
LFROM=$(jq -r '.from // ""' life.json); LTO=$(jq -r '.to // ""' life.json)
LLEN=$(jq -r '.len // 0' life.json)

jq --argjson n "$COMMITS" --arg fc "since $(fmt "$FIRST")" \
--arg cs "$STREAK days" --arg css "$SSTART" \
--arg ls "$LLEN days" --arg lss "$(fmts "$LFROM") - $(fmt "$LTO")" '
def commas: tostring | . as $s | ($s | length) as $L
| if $L <= 3 then $s else ($s[:$L-3] | commas) + "," + $s[$L-3:] end;
.stat_boxes = [
{value: ($n | commas), label: "ALL COMMITS", note: $fc},
{value: $cs, label: "CURRENT STREAK", note: $css},
{value: $ls, label: "LONGEST STREAK", note: $lss}
]' "$CONFIG" > awan.tmp && mv awan.tmp "$CONFIG"
rm -f y.json days.json life.json

- name: Draw it
run: awan-profile whoami --config "${{ inputs.config }}"

- name: Shrink the GIF
- name: Draw the stats banner
if: inputs.stats_banner != ''
run: awan-profile stats --config "${{ inputs.config }}" --out "${{ inputs.stats_banner }}"

- name: Shrink the GIFs
run: |
sudo apt-get update && sudo apt-get install -y gifsicle
OUT=$(jq -r '.output // "assets/awan.gif"' "${{ inputs.config }}")
gifsicle -O3 --colors 64 "$OUT" -o "$OUT"
BANNER="${{ inputs.stats_banner }}"
if [ -n "$BANNER" ] && [ "${BANNER##*.}" = "gif" ]; then
gifsicle -O3 --colors 64 "$BANNER" -o "$BANNER"
fi

- name: Commit the result
run: |
OUT=$(jq -r '.output // "assets/awan.gif"' "${{ inputs.config }}")
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add "$OUT" "${{ inputs.config }}"
[ -n "${{ inputs.stats_banner }}" ] && git add "${{ inputs.stats_banner }}" || true
git commit -m "chore: refresh awan profile" || echo "no changes"
git push
64 changes: 64 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ cp -r profile/sample/. my-profile/ # awan.json + a GitHub Action + a profile R
cargo run -p awan-profile -- whoami --config my-profile/awan.json
```

Want the numbers without the character too? A one-line workflow input also draws
a **stats banner** — all-time commits, current and longest streak, with dates —
as its own image. See [`profile/`](profile#the-stats-banner).

Full walkthrough and the `awan.json` format: **[`profile/`](profile)**. Built as
a separate, opt-in crate, so the core `awan` stays untouched.

Expand Down Expand Up @@ -218,6 +222,7 @@ library — no Rust, no rebuild of the engine.
The banner is the product; everything else is how it's made.

- **Shipping** — profile banners with live numbers, the contribution year, a
standalone stats banner (all-time commits and streaks, no character), a
browser editor with a character studio, and a reusable workflow you call
rather than copy
- **Next** — more beats worth watching, and somewhere to show the characters
Expand Down
Binary file added assets/stats-banner.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/stats-banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions docs/PROFILE.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,26 @@ the `stats` readout — live in `awan.json`; the CI job fetches the real values
(`gh api` → `jq`) and writes them in before rendering, so the binary stays
deterministic and offline.

## The stats banner

A separate output (`awan-profile stats`), and the only one with no character:
three metrics — all-time commits, current streak, longest streak — in a row,
split by vertical rules, each with the dates it covers. It answers a different
question ("the numbers, at a glance") and wants the full width, so it isn't a
scene in the reel.

The split mirrors the rest: the engine knows nothing about it. The renderer lays
out `stat_boxes` (three `{value, label, note}` strings) in the 8×8 pixel font, so
it matches the character banner beside it; CI formats the strings. A `.gif` drifts
soft clouds behind the numbers and loops (two parallax clouds, each crossing a
whole number of spans per loop, so `frame(0) == frame(N)`); a `.png` is the still.

The numbers it wants reach past the one-year calendar the walking banner uses —
an all-time count from the first commit, the longest streak *ever* — so the
workflow walks the contribution calendar year by year from the year the account
was created, and only when the `stats_banner` input is set, so the extra API
calls are opt-in.

## Rendering to GIF

The generator lives in the `publish = false` `profile/` crate, so the `image` /
Expand Down
2 changes: 1 addition & 1 deletion profile/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ path = "src/main.rs"

[dependencies]
awan-core = { workspace = true }
image = { version = "0.25", default-features = false, features = ["gif"] }
image = { version = "0.25", default-features = false, features = ["gif", "png"] }
font8x8 = "0.3"
serde = { workspace = true }
serde_json = "1"
49 changes: 48 additions & 1 deletion profile/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,42 @@ Same acts, same config shape — only the content differs. Pick a sample and edi
repo's numbers.* Drop `{ "act": "stats" }` into **either** config — it reads the
same `stats` array.

## The stats banner

A third output, and the only one with **no character**: three numbers in a row,
split by vertical rules — your all-time commits, your current streak, your
longest streak — each with the dates it covers. It wants the whole width to
itself, so it's a separate image from the walking banner.

<p align="center">
<img src="../assets/stats-banner.gif" alt="all commits, current streak, longest streak — with dates" width="700">
</p>

```sh
awan-profile stats --config awan.json --out assets/stats-banner.gif # drifting clouds, loops
awan-profile stats --config awan.json --out assets/stats-banner.png # a still
```

The `.gif` drifts soft clouds behind the numbers and loops; the `.png` is a
still. Same command — the extension picks.

The three boxes come from `stat_boxes` in your config, which **CI fills** (like
`stats` and the streak) — you don't write them by hand. Each box is a
pre-formatted `value`, a `label`, and a `note` for the dates:

```jsonc
"stat_boxes": [
{ "value": "1,247", "label": "ALL COMMITS", "note": "since 12 Mar 2021" },
{ "value": "12 days", "label": "CURRENT STREAK", "note": "since 8 Jul" },
{ "value": "38 days", "label": "LONGEST STREAK", "note": "3 Feb - 12 Mar 2024" }
]
```

To get it drawn and refreshed nightly, set the `stats_banner` input on the
workflow (see [Auto-update](#auto-update-on-github)). The all-time figures need
the calendar walked year by year from the year you joined, so the workflow only
does that when you ask for the banner.

## Get started

The [`sample/`](sample) folder is a ready-to-copy setup:
Expand Down Expand Up @@ -82,6 +118,9 @@ its own — no Ctrl+C.
// so run the workflow once and look
"contrib_year": 2060, // ↳ both filled in by the workflow too
"contrib_recent": 183,
"stat_boxes": [], // the stats banner's 3 boxes (no
// character). CI fills these too — see
// "The stats banner". Leave it empty.
"output": "assets/awan.gif", // where the GIF is written
"scenes": [ // ← reorder / add / remove freely
{ "act": "wave", "say": "hi there! i'm {name}" },
Expand Down Expand Up @@ -175,9 +214,16 @@ jobs:
brag_over: 100 # your bar for "a good month"
brag_say: "i'm so excited!"
cope_say: "...i'll fix that, promise"
stats_banner: assets/stats-banner.gif # optional; see "The stats banner"
```

Then reference the outputs in your profile `README.md`:

```markdown
![awan](assets/awan.gif)
![my github, at a glance](assets/stats-banner.gif)
```

Then reference the GIF in your profile `README.md`: `![awan](assets/awan.gif)`.
No secrets to set up: the stock `GITHUB_TOKEN` reads everything this needs.

| input | default | what it does |
Expand All @@ -186,6 +232,7 @@ No secrets to set up: the stock `GITHUB_TOKEN` reads everything this needs.
| `config` | `awan.json` | where your config lives |
| `brag_over` | `100` | last-30 contributions that count as a good month |
| `brag_say` / `cope_say` | see above | what he says either side of that bar |
| `stats_banner` | *(off)* | path for the stats banner; set it to draw one |

### Which tag to point at

Expand Down
4 changes: 4 additions & 0 deletions profile/sample/.github/workflows/awan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ jobs:
brag_over: 100 # your bar for "a good month"
brag_say: "i'm so excited!"
cope_say: "...i'll fix that, promise"
# Also draw the standalone stats banner (all-time commits, current and
# longest streak, with dates). Delete this line to skip it — you add
# nothing to awan.json either way, the workflow fills the numbers.
stats_banner: assets/stats-banner.gif
2 changes: 2 additions & 0 deletions profile/sample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

![awan](assets/awan.gif)

![my github, at a glance](assets/stats-banner.gif)

# hi, i'm Muhammad Ridwan 👋

fullstack engineer · Indonesia · building with Rust, Go & TypeScript
Expand Down
1 change: 1 addition & 0 deletions profile/sample/awan.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"contributions": "",
"contrib_year": 0,
"contrib_recent": 0,
"stat_boxes": [],
"output": "assets/awan.gif",
"scenes": [
{
Expand Down
Loading
Loading