mirror of
https://github.com/tiennm99/ghstats.git
synced 2026-05-14 12:58:48 +00:00
734ac21be9
Several recent code changes hadn't propagated to the docs:
- design-guidelines
* Card frame title row: document the 11–15 px auto-shrink (not a flat
15 px anymore).
* Donut Top-N: already 7 (updated earlier).
* Bar-chart section renamed to cover weekday + by-year too; document
the peak-vs-dim highlight convention and the niceTicks yMax ≥ max
invariant.
* Add Heatmap / Stat-column (streak) / List (top-starred) card
sections — they were missing entirely.
* Rewrite "Text overflow" from "we don't truncate" to the current
truth (truncate helper, formatTick abbreviations).
* Replace dangling `truncateName` reference with `truncate`.
- code-standards
* SVG output standards: call out truncate, formatTick abbreviation,
header auto-fit so the card-review gate reflects what the renderers
actually do.
- codebase-summary
* Layout tree: svg.go / axis.go comments list the helpers they now
contain; productive.go notes the weekday histogram.
* demo/ tree shows the index vs per-theme split.
* Data-flow diagram includes Weekday in the productive pass.
* Test coverage row lists TestCardsFitFrame / TestFitTitleFontSize /
TestNiceTicksCoversMax — the new invariant guards.
- system-architecture
* Shared primitives list adds renderWeekday and renderHeatmap; donut
blurb updated to "top 7".
* New "Chart-geometry invariants" block documents niceTicks ceiling,
formatTick abbreviation, header auto-fit.
- project-roadmap
* Phase 7.5 bullet updated to describe the index + per-theme demo
split (was single-README TOC).
7.5 KiB
7.5 KiB
Codebase Summary
Layout
ghstats/
├── main.go # CLI entry point; wires flags → fetchers → renderers
├── action.yml # GitHub Action metadata
├── entrypoint.sh # Action runtime; maps INPUT_* env → CLI flags
├── Dockerfile # Multi-stage build for the Action image
├── go.mod # Module declaration; no external deps
├── internal/
│ ├── github/ # GraphQL client + fetchers + models
│ │ ├── client.go # HTTP POST to /graphql, error decoding
│ │ ├── queries.go # profileQuery, commitHistoryQuery, contributionYearQuery
│ │ ├── model.go # Profile, RepoInfo, LangStat, LangEdge, DailyContribution
│ │ ├── profile.go # FetchProfile — user + owned repos + stats + calendar
│ │ ├── productive.go # FetchProductive — commit history → hour + weekday histograms + lang buckets
│ │ ├── contributions_all_time.go # FetchContributionsAllTime — per-year loop → seed list + daily series
│ │ └── profile_test.go # sortLangStats tiebreak
│ ├── card/ # SVG renderers; one file per card
│ │ ├── card.go # Card interface, RenderAll, allCards slice
│ │ ├── svg.go # escapeXML, formatInt, truncate, header (auto-fit title), footer
│ │ ├── axis.go # niceTicks (d3-style, last tick ≥ max), formatTick (1500→"1.5k")
│ │ ├── icons.go # Octicon path strings
│ │ ├── profile.go # profile-details
│ │ ├── repos_per_language.go # repos-per-language
│ │ ├── most_commit_language.go # most-commit-language
│ │ ├── most_commit_language_all_time.go # most-commit-language-all-time
│ │ ├── stats.go # stats
│ │ ├── productive.go # productive-time (+ all-time)
│ │ ├── productive_weekday.go # productive-weekday (+ all-time)
│ │ ├── contributions.go # contributions (+ all-time)
│ │ ├── contributions_heatmap.go # contributions-heatmap (7×53 calendar grid)
│ │ ├── contributions_by_year.go # contributions-by-year bar chart
│ │ ├── streak.go # streak (current/longest/active days)
│ │ ├── top_starred_repos.go # top-starred-repos bar list
│ │ ├── donut_chart.go # renderDonutCard — shared by language cards
│ │ └── card_test.go # Rendering + escape + format tests
│ └── theme/
│ └── theme.go # 65-palette map ported from github-profile-summary-cards
├── .github/workflows/
│ ├── ci.yml # go vet + go test on push/PR
│ ├── release.yml # GHCR image + cross-platform binaries on tag
│ └── demo.yml # Renders every theme for the repo owner on push to main
├── docs/ # This directory
├── plans/ # Research reports + implementation plans
└── demo/ # Auto-generated gallery
├── README.md # Lightweight index (links only, zero images)
└── <theme>/ # Per-theme page: 15 SVGs + README pairing LY / AT variants
# (`output/` is entirely gitignored; see demo/ for reference renders)
Module responsibilities
internal/github
All network I/O. Exposes a *Client with three fetchers; every call takes a context.Context so pagination aborts cleanly on timeout or Ctrl-C:
| Fetcher | Input | Populates |
|---|---|---|
FetchProfile(ctx, login, opts) |
username, visibility flags | Profile basics, totals, owned-repos aggregation, last-year daily calendar, TopRepos |
FetchContributionsAllTime(ctx, p, opts) |
Profile | SeedRepos, DailyContributionsAllTime, TotalCommitsAllTime |
FetchProductive(ctx, p, repos, loc, cap) |
Profile + seed + tz + cap | Productive, Weekday, CommitsByLanguage, ProductiveAllTime, WeekdayAllTime, CommitsByLanguageAllTime |
Call order in main.go: Profile → AllTime → Productive. Client.query handles GitHub rate limits transparently — on 429 or 403 with X-RateLimit-Remaining: 0, it honors Retry-After / X-RateLimit-Reset (capped at 5 minutes) and retries once.
internal/card
Pure rendering. Every card implements the Card interface:
type Card interface {
Filename() string
SVG(*github.Profile, theme.Theme) ([]byte, error)
}
RenderAll iterates allCards, writes each to <outDir>/<themeID>/<Filename>.
Shared helpers:
renderDonutCard— language donut + legend (used by 3 language cards)renderProductiveTime— 24h bar chart (used by both productive-time cards)renderWeekday— 7-bar day-of-week chart (used by both productive-weekday cards)renderContributions— smooth area chart (used by both contributions cards)renderHeatmap— 7×N calendar grid withmixHex-derived intensity rampmixHex/parseHex—#rrggbbblending (used by heatmap, by-year, weekday for peak-vs-dim bars)header,footer— SVG chromeniceTicks,formatTick— axis math
internal/theme
Static map of 65 themes. Each theme specifies title/text/background/stroke/accent/muted plus StrokeOpacity for correct light-theme borders.
Card ↔ data flow
profileQuery ─────► Profile.{identity, owned repos, totals, last-year calendar}
│
contributionYearQuery ─┬──► SeedRepos + DailyContributionsAllTime + TotalCommitsAllTime
│
└─ seed into ─►
│
commitHistoryQuery ──► Productive + Weekday + CommitsByLanguage (+ AllTime variants)
│
▼
15 SVG files per theme
Test coverage
internal/card/card_test.go—RenderAllproduces 15 valid SVGs; XML escape through real render pipeline;formatIntcases;TestDonutSingleSlice/TestDonutEmpty(donut edge cases);TestCardsFitFrame(renders every card against an adversarial profile and asserts text + coordinates stay in the 340×200 frame);TestFitTitleFontSize(pins the auto-shrink table for every real title);TestNiceTicksCoversMax(guards theyMax ≥ dataMaxinvariant so bars can't overflow chartH).internal/github/profile_test.go—sortLangStatsordering and tiebreak.main_test.go—TestUTCOffsetLabelcovers UTC, Asia/Saigon, half-hour (Kolkata), quarter-hour (Kathmandu) zones.
No network-touching tests; real runs verified via -token + local build.
Naming conventions
- Go files use snake_case for multi-word names (
repos_per_language.go,contributions_all_time.go). - Cards'
Filename()returns a plain kebab-case name (profile-details.svg,most-commit-language-all-time.svg, …). Embedders reference by name, so no numeric prefix is needed. - Themes in snake_case to match upstream (
github_dark,nord_bright).