Files
awesome-coding-agents/readme.go
T
tiennm99 237d7acd6a feat: initial scaffold of awesome-coding-agents tracker
Go updater that fetches AI agent coding tool repo stats via GitHub GraphQL
(batched, one query), sorts by star count, appends a daily snapshot to
data/history.jsonl, and regenerates README.md from templates/readme.tmpl.
Daily workflow at .github/workflows/update.yml refreshes rankings and
commits changes. Seed list in data/agents.yml covers 19 tracked repos.
2026-05-13 14:43:35 +07:00

97 lines
2.0 KiB
Go

package main
import (
"fmt"
"os"
"strings"
"text/template"
"time"
)
type Row struct {
Rank int
NameWithOwner string
URL string
Stars int
Delta7d int
HasDelta bool
Language string
PushedAt string
Description string
Category string
}
func renderReadme(tmplPath, outPath string, stats []Stat, deltas map[string]int) error {
rows := make([]Row, len(stats))
for i, s := range stats {
delta, has := deltas[s.NameWithOwner]
rows[i] = Row{
Rank: i + 1,
NameWithOwner: s.NameWithOwner,
URL: s.URL,
Stars: s.Stars,
Delta7d: delta,
HasDelta: has,
Language: s.Language,
PushedAt: s.PushedAt.Format("2006-01-02"),
Description: sanitizeCell(s.Description),
Category: s.Category,
}
}
funcs := template.FuncMap{
"formatDelta": func(d int, has bool) string {
if !has {
return "—"
}
if d > 0 {
return fmt.Sprintf("+%d", d)
}
if d < 0 {
return fmt.Sprintf("%d", d)
}
return "0"
},
"formatStars": func(n int) string {
switch {
case n < 1000:
return fmt.Sprintf("%d", n)
case n < 1_000_000:
return fmt.Sprintf("%.1fk", float64(n)/1000)
default:
return fmt.Sprintf("%.1fM", float64(n)/1_000_000)
}
},
}
tmpl, err := template.New("").Funcs(funcs).ParseFiles(tmplPath)
if err != nil {
return err
}
f, err := os.Create(outPath)
if err != nil {
return err
}
defer f.Close()
name := tmplPath
if i := strings.LastIndex(tmplPath, "/"); i >= 0 {
name = tmplPath[i+1:]
}
return tmpl.ExecuteTemplate(f, name, map[string]any{
"Rows": rows,
"UpdatedAt": time.Now().UTC().Format("2006-01-02 15:04 UTC"),
"Total": len(rows),
})
}
// sanitizeCell escapes pipe and newline characters so descriptions stay in one table cell.
func sanitizeCell(s string) string {
s = strings.ReplaceAll(s, "|", "\\|")
s = strings.ReplaceAll(s, "\n", " ")
s = strings.ReplaceAll(s, "\r", " ")
return strings.TrimSpace(s)
}