mirror of
https://github.com/tiennm99/ghstats.git
synced 2026-05-14 08:58:12 +00:00
1763422570
* feat(card): configurable start of week for heatmap + weekday cards New -start-of-week CLI flag (and start_of_week action input) rotates the contribution-heatmap rows and the productive-weekday bars so users whose calendars start on Monday (or any other day) get matching output. Default stays Sunday to preserve existing renders. * docs: note start-of-week in design-guidelines, codebase-summary, deployment-guide - design-guidelines: heatmap row order + productive-weekday bar order now derive from Profile.WeekStart - codebase-summary: list new weekday_start_test.go cases + TestParseWeekday - deployment-guide: mention start_of_week as an optional action input in the workflow template
124 lines
7.9 KiB
Markdown
124 lines
7.9 KiB
Markdown
# 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; row 0 = Profile.WeekStart)
|
||
│ │ ├── 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:
|
||
|
||
```go
|
||
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 with `mixHex`-derived intensity ramp
|
||
- `mixHex` / `parseHex` — `#rrggbb` blending (used by heatmap, by-year, weekday for peak-vs-dim bars)
|
||
- `header`, `footer` — SVG chrome
|
||
- `niceTicks`, `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` — `RenderAll` produces 15 valid SVGs; XML escape through real render pipeline; `formatInt` cases; `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 the `yMax ≥ dataMax` invariant so bars can't overflow chartH).
|
||
- `internal/card/weekday_start_test.go` — `TestPadToWeekGridRotatesByWeekStart` (leading pad matches configured start day), `TestRenderWeekdayRespectsWeekStart` (bar order rotates with `WeekStart`), `TestRenderHeatmapLabelsRespectWeekStart` (heatmap row labels rotate accordingly).
|
||
- `internal/github/profile_test.go` — `sortLangStats` ordering and tiebreak.
|
||
- `main_test.go` — `TestUTCOffsetLabel` covers UTC, Asia/Saigon, half-hour (Kolkata), quarter-hour (Kathmandu) zones. `TestParseWeekday` covers the `-start-of-week` input parsing.
|
||
|
||
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`).
|