diff options
Diffstat (limited to 'cmd/generate')
-rw-r--r-- | cmd/generate/calendar.go | 122 | ||||
-rw-r--r-- | cmd/generate/gitcache.go | 66 | ||||
-rw-r--r-- | cmd/generate/httpcache.go | 95 | ||||
-rw-r--r-- | cmd/generate/imworkingon.html.tmpl | 160 | ||||
-rw-r--r-- | cmd/generate/main.go | 154 | ||||
-rw-r--r-- | cmd/generate/src_contribs.go | 400 | ||||
-rw-r--r-- | cmd/generate/src_contribs_test.go | 39 | ||||
-rw-r--r-- | cmd/generate/src_mastodon.go | 48 | ||||
-rw-r--r-- | cmd/generate/src_tags.go | 25 | ||||
-rw-r--r-- | cmd/generate/src_upstreams.go | 48 |
10 files changed, 0 insertions, 1157 deletions
diff --git a/cmd/generate/calendar.go b/cmd/generate/calendar.go deleted file mode 100644 index 29c3318..0000000 --- a/cmd/generate/calendar.go +++ /dev/null @@ -1,122 +0,0 @@ -package main - -import ( - "time" -) - -////////////////////////////////////////////////////////////////////// - -type Date struct { - Year int - Month time.Month - Day int -} - -func DateOf(t time.Time) Date { - y, m, d := t.Date() - return Date{Year: y, Month: m, Day: d} -} - -func (d Date) Time() time.Time { - return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, time.Local) -} - -func (d Date) AddDays(delta int) Date { - return DateOf(d.Time().AddDate(0, 0, delta)) -} - -func (d Date) Weekday() time.Weekday { - return d.Time().Weekday() -} - -func (a Date) Cmp(b Date) int { - switch { - case a.Year < b.Year: - return -1 - case a.Year > b.Year: - return 1 - } - switch { - case a.Month < b.Month: - return -1 - case a.Month > b.Month: - return 1 - } - switch { - case a.Day < b.Day: - return -1 - case a.Day > b.Day: - return 1 - } - return 0 -} - -////////////////////////////////////////////////////////////////////// - -type CalendarDay[T any] struct { - Date - Data T -} - -////////////////////////////////////////////////////////////////////// - -// keyed by time.Weekday -type CalendarWeek[T any] [7]CalendarDay[T] - -////////////////////////////////////////////////////////////////////// - -// must be sorted, must be non-sparse -type Calendar[T any] []CalendarWeek[T] - -func (c Calendar[T]) NumWeekdaysInMonth(weekday time.Weekday, target Date) int { - num := 0 - for _, w := range c { - if w[weekday].Date == (Date{}) { - continue - } - switch { - case w[weekday].Year == target.Year: - switch { - case w[weekday].Month == target.Month: - num++ - case w[weekday].Month > target.Month: - return num - } - case w[weekday].Year > target.Year: - return num - } - } - return num -} - -////////////////////////////////////////////////////////////////////// - -func BuildCalendar[T any](things []T, dateOfThing func(T) Date) Calendar[T] { - if len(things) == 0 { - return nil - } - - newestDate := DateOf(time.Now().Local()) - - oldestDate := dateOfThing(things[0]) - byDate := make(map[Date]T, len(things)) - for _, thing := range things { - date := dateOfThing(thing) - if oldestDate.Cmp(date) > 0 { - oldestDate = date - } - byDate[date] = thing - } - - var ret Calendar[T] - for date := oldestDate; date.Cmp(newestDate) <= 0; date = date.AddDays(1) { - if len(ret) == 0 || date.Weekday() == 0 { - ret = append(ret, CalendarWeek[T]{}) - } - ret[len(ret)-1][date.Weekday()] = CalendarDay[T]{ - Date: date, - Data: byDate[date], - } - } - return ret -} diff --git a/cmd/generate/gitcache.go b/cmd/generate/gitcache.go deleted file mode 100644 index 7caf024..0000000 --- a/cmd/generate/gitcache.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "fmt" - "os" - "os/exec" - "strings" - - "git.mothstuff.lol/lukeshu/eclipse/lib/gitcache" -) - -var gitFetched = map[string]struct{}{} - -var gitCache = &gitcache.Cache{ - Dir: ".git-cache", -} - -func withGit(u string, fn func(dir string) error) error { - if _, ok := gitFetched[u]; !ok { - if err := gitCache.Fetch(os.Stderr, u); err != nil { - return err - } - } - return gitCache.WithFastClone(os.Stderr, u, fn) -} - -func getGitTagThatContainsAll(gitURL string, gitHashes ...string) (string, error) { - if len(gitHashes) == 0 { - return "", nil - } - var tag string - err := withGit(gitURL, func(dir string) error { - gitHash := gitHashes[0] - if len(gitHashes) > 1 { - cmdline := append([]string{"git", "merge-base", "--independent", "--"}, gitHashes...) - cmd := exec.Command(cmdline[0], cmdline[1:]...) - cmd.Dir = dir - var stdout strings.Builder - cmd.Stdout = &stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - return err - } - gitHash = strings.TrimSpace(stdout.String()) - } - cmd := exec.Command("git", "for-each-ref", - "--count=1", - "--format=%(refname:lstrip=2)", - "--contains="+gitHash, - "refs/tags/", - ) - cmd.Dir = dir - var stdout strings.Builder - cmd.Stdout = &stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - return err - } - tag = strings.TrimSpace(stdout.String()) - return nil - }) - if err != nil { - return "", fmt.Errorf("%q: %w", gitURL, err) - } - return tag, nil -} diff --git a/cmd/generate/httpcache.go b/cmd/generate/httpcache.go deleted file mode 100644 index 04762e3..0000000 --- a/cmd/generate/httpcache.go +++ /dev/null @@ -1,95 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "os" - "path/filepath" -) - -var httpCache = map[string]string{} - -func httpGet(u string) (string, error) { - if cache, ok := httpCache[u]; ok { - fmt.Printf("CACHE-GET %q\n", u) - return cache, nil - } - if err := os.Mkdir(".http-cache", 0777); err != nil && !os.IsExist(err) { - return "", err - } - cacheFile := filepath.Join(".http-cache", url.QueryEscape(u)) - if bs, err := os.ReadFile(cacheFile); err == nil { - httpCache[u] = string(bs) - fmt.Printf("CACHE-GET %q\n", u) - return httpCache[u], nil - } else if !os.IsNotExist(err) { - return "", err - } - - fmt.Printf("GET %q...", u) - resp, err := http.Get(u) - if err != nil { - fmt.Printf(" err\n") - return "", err - } - if resp.StatusCode != http.StatusOK { - fmt.Printf(" err\n") - return "", fmt.Errorf("unexpected HTTP status: %v", resp.Status) - } - bs, err := io.ReadAll(resp.Body) - if err != nil { - fmt.Printf(" err\n") - return "", err - } - fmt.Printf(" ok\n") - if err := os.WriteFile(cacheFile, bs, 0666); err != nil { - return "", err - } - httpCache[u] = string(bs) - return httpCache[u], nil -} - -func httpGetJSON(u string, out any) error { - str, err := httpGet(u) - if err != nil { - return err - } - return json.Unmarshal([]byte(str), out) -} - -func httpGetPaginatedJSON[T any](uStr string, out *[]T, pageFn func(i int) url.Values) error { - u, err := url.Parse(uStr) - if err != nil { - return err - } - query := u.Query() - - for i := 0; true; i++ { - pageParams := pageFn(i) - for k, v := range pageParams { - query[k] = v - } - - u.RawQuery = query.Encode() - var resp []T - if err := httpGetJSON(u.String(), &resp); err != nil { - return err - } - fmt.Printf(" -> %d records\n", len(resp)) - if len(resp) == 0 { - break - } - *out = append(*out, resp...) - } - - return nil -} - -func githubPagination(i int) url.Values { - params := make(url.Values) - params.Set("page", fmt.Sprintf("%v", i+1)) - return params -} diff --git a/cmd/generate/imworkingon.html.tmpl b/cmd/generate/imworkingon.html.tmpl deleted file mode 100644 index 85a56e1..0000000 --- a/cmd/generate/imworkingon.html.tmpl +++ /dev/null @@ -1,160 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <title>Luke is working onβ¦</title> - <meta name="viewport" content="width=device-width, initial-scale=1"> - <link rel="stylesheet" type="text/css" href="../blog/assets/style.css"> - <link rel="stylesheet" type="text/css" href="imworkingon.css"> -</head> -<body> - <header><a href="/">Luke T. Shumaker</a> Β» imworkingon</header> - - <section id="intro"> - <h1>Luke is working on<br/><small>improving the GNU/Linux ecosystem</small></h1> - <nav> - <p>This page provides several views into what I'm doing to improve the ecosystem:</p> - <ol> - <li><a href="#tags">Top themes</a></li> - <li><a href="#contribs-pending">In-progress work</a></li> - <li><a href="#contribs-completed">Completed work</a></li> - <li><a href="#standups">Daily statuses</a></li> - </ol> - </nav> - <p>If you find this work valuable, please consider <a class="em" href="../sponsor/">sponsoring me</a>.</p> - </section> - - <section id="tags"> - <h2>Top themes <a href="#tags">π</a></h2> - {{- range $tagName, $tagInfo := .Tags }} - <article class="tag" id="tag-{{ $tagName }}"> - <h2><a href="#tag-{{ $tagName }}">#{{ $tagName }}</a> : {{ $tagInfo.PrettyName }}</h2> - <div clasg="tag-desc">{{ $tagInfo.Desc | md2html }}</div> - </article> - {{- end }} - </section> - -{{- define "contrib" }} - {{ $contrib := . }} - {{ $upstream := $contrib | getUpstream }} - <article class="contrib {{ $contrib.StatusClass }}-contrib"> - <div class="contrib-upstream-name"><a class="em" href="{{ index $upstream.URLs 0 }}">{{ $upstream.Name }}</a></div> - <div class="contrib-upstream-desc">{{ $upstream.Desc | md2html }}</div> - <div class="contrib-urls"> - {{- range $url := $contrib.URLs }} - <a href="{{ $url }}"><code>{{ $url }}</code></a><br /> - {{- end }} - </div> - <div class="contrib-tags"> - {{- range $tag := $contrib.Tags }} - <a href="#tag-{{ $tag }}">#{{ $tag }}</a> {{/* */}} - {{- end }} - </div> - <div class="contrib-submitted">Submitted: {{ timeTag $contrib.SubmittedAt "2006-01-02" }}</div> - <div class="contrib-updated"> - {{- if not $contrib.LastUpdatedAt.IsZero -}} - Last updated: {{ timeTag $contrib.LastUpdatedAt "2006-01-02" }} - {{- if $contrib.LastUpdatedBy.Name }} by <a href="{{ $contrib.LastUpdatedBy.URL }}">{{ $contrib.LastUpdatedBy.Name }}</a>{{ end }} - {{- end -}} - </div> - <div class="contrib-status">Status: {{ $contrib.Status }}</div> - <div class="contrib-desc"> - {{- $contrib.Desc | md2html }} - {{- if $contrib.SponsoredBy }}<p>Sponsored-by: {{ $contrib.SponsoredBy }}</p>{{ end -}} - </div> - </article> -{{- end }} - - <section id="contribs-pending"> - <h2>In-progress work <a href="#contribs-pending">π</a></h2> - {{- range $contrib := .Contribs }} - {{- if or (eq $contrib.StatusClass "merged") (eq $contrib.StatusClass "released") }}{{ continue }}{{ end }} - {{ template "contrib" $contrib }} - {{- end }} - </section> - <section id="contribs-completed"> - <h2>Completed work <a href="#contribs-completed">π</a></h2> - {{- range $contrib := .Contribs }} - {{- if or (eq $contrib.StatusClass "merged") (eq $contrib.StatusClass "released") | not }}{{ continue }}{{ end }} - {{ template "contrib" $contrib }} - {{- end }} - </section> - <section id="standups"> - <h2>Daily statuses <a href="#standups">π</a></h2> - <p>Posted daily on <a href="https://fosstodon.org/@lukeshu">Mastodon</a> with the #DailyStandup tag.</p> - - <details><summary>Calendar view</summary> - <table> - <thead> - <tr> - <th></th> - <th><abbr title="Sunday">Su</abbr></th> - <th><abbr title="Monday">M</abbr></th> - <th><abbr title="Tuesday">Tu</abbr></th> - <th><abbr title="Wednesday">W</abbr></th> - <th><abbr title="Thursday">Th</abbr></th> - <th><abbr title="Friday">F</abbr></th> - <th><abbr title="Saturday">S</abbr></th> - <th></th> - </tr> - </thead> - <tbody> - {{- $cal := .StandupCalendar }} - {{- $curSunMonth := 0 }} - {{- $curSatMonth := 0 }} - {{- range $i, $week := reverse .StandupCalendar }} - <tr> - {{- $sun := (index $week time.Sunday) }} - {{- if not $sun.Day }} - <th></th> - {{- else if ne $sun.Month $curSunMonth }} - <th class="{{ monthClass $sun.Month }}" rowspan="{{ $cal.NumWeekdaysInMonth time.Sunday $sun.Date }}"> - <span>{{ $sun.Month }} {{ $sun.Year }}</span> - </th> - {{- $curSunMonth = $sun.Month }} - {{- end }} - {{- range $day := $week }} - {{- if not $day.Day }} - <td></td> - {{- else if not $day.Data }} - <td class="{{ monthClass $day.Month }}"> - {{ $day.Day }} - </td> - {{- else }} - <td class="{{ monthClass $day.Month }}"> - <a href="#standup-id-{{ $day.Data.ID }}"> - {{ $day.Day }} - </a> - </td> - {{- end }} - </td> - {{- end }} - {{- $sat := (index $week time.Saturday) }} - {{- if not $sat.Day }} - <th></th> - {{- else if ne $sat.Month $curSatMonth }} - <th class="{{ monthClass $sat.Month }}" rowspan="{{ $cal.NumWeekdaysInMonth time.Saturday $sat.Date }}"> - <span>{{ $sat.Month }} {{ $sat.Year }}</span> - </th> - {{- $curSatMonth = $sat.Month }} - {{- end }} - {{- end }} - </tr> - </tbody> - </table> - </details> - - {{- range $status := .Standups }} - <article class="standup" id="standup-id-{{ $status.ID }}"> - <div class="standup-title"><a href="{{ $status.URL }}">{{ timeTag $status.CreatedAt "Mon 2006-01-02" }}</a></div> - <div class="standup-content">{{ $status.Content }}</div> - </article> - {{- end }} - </section> - - <footer> - <p>The content of this page is Copyright Β© Luke T. Shumaker.</p> - <p>This page is licensed under the <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a> license.</p> - </footer> -</body> -</html> diff --git a/cmd/generate/main.go b/cmd/generate/main.go deleted file mode 100644 index e322e5c..0000000 --- a/cmd/generate/main.go +++ /dev/null @@ -1,154 +0,0 @@ -package main - -import ( - "bytes" - _ "embed" - "fmt" - "os" - "reflect" - "sort" - "strings" - "time" - - "html/template" - - "github.com/yuin/goldmark" -) - -func MarkdownToHTML(md string) (template.HTML, error) { - var html strings.Builder - if err := goldmark.Convert([]byte(md), &html); err != nil { - return template.HTML(""), err - } - return template.HTML(html.String()), nil -} - -var githubProjects = map[string]string{ - "flori/json": "ruby-json", -} - -func main() { - if err := mainWithError(); err != nil { - fmt.Fprintf(os.Stderr, "%s: error: %v\n", os.Args[0], err) - os.Exit(1) - } -} - -//go:embed imworkingon.html.tmpl -var htmlTmplStr string - -var timeTagTmpl = template.Must(template.New("time.tag.tmpl"). - Parse(`<time datetime="{{ .Machine }}" title="{{ .HumanVerbose }}">{{ .HumanPretty }}</time>`)) - -func mainWithError() error { - standups, err := ReadStandups("https://fosstodon.org", "lukeshu") - if err != nil { - return err - } - contribs, err := ReadContribs("imworkingon/contribs.yml") - if err != nil { - return err - } - tags, err := ReadTags("imworkingon/tags.yml") - if err != nil { - return err - } - upstreams, err := ReadUpstreams("imworkingon/upstreams.yml") - if err != nil { - return err - } - - sort.Slice(contribs, func(i, j int) bool { - iDate := contribs[i].LastUpdatedAt - if iDate.IsZero() { - iDate = contribs[i].SubmittedAt - } - jDate := contribs[j].LastUpdatedAt - if jDate.IsZero() { - jDate = contribs[j].SubmittedAt - } - return iDate.After(jDate) - }) - - tmpl := template.Must(template.New("imworkingon.html.tmpl"). - Funcs(template.FuncMap{ - "time": func() map[string]time.Weekday { - return map[string]time.Weekday{ - "Sunday": time.Sunday, - "Monday": time.Monday, - "Tuesday": time.Tuesday, - "Wednesday": time.Wednesday, - "Thursday": time.Thursday, - "Friday": time.Friday, - "Saturday": time.Saturday, - } - }, - "reverse": func(x any) any { - in := reflect.ValueOf(x) - l := in.Len() - out := reflect.MakeSlice(in.Type(), l, l) - for i := 0; i < l; i++ { - out.Index(l - (i + 1)).Set(in.Index(i)) - } - return out.Interface() - }, - "timeTag": func(ts time.Time, prettyFmt string) (template.HTML, error) { - ts = ts.Local() - var out strings.Builder - err := timeTagTmpl.Execute(&out, map[string]string{ - "Machine": ts.Format(time.RFC3339), - "HumanVerbose": ts.Format("2006-01-02 15:04:05Z07:00"), - "HumanPretty": ts.Format(prettyFmt), - }) - return template.HTML(out.String()), err - }, - "monthClass": func(m time.Month) string { - if m%2 == 0 { - return "even-month" - } else { - return "odd-month" - } - }, - "md2html": MarkdownToHTML, - "getUpstream": func(c Contribution) Upstream { - // First try any of the documented upstreams. - for _, cURL := range c.URLs { - for _, upstream := range upstreams { - for _, uURL := range upstream.URLs { - prefix := uURL - if !strings.HasSuffix(prefix, "/") { - prefix += "/" - } - if cURL == uURL || strings.HasPrefix(cURL, prefix) { - return upstream - } - } - } - } - if m := reGitHubPR.FindStringSubmatch(c.URLs[0]); m != nil { - user := m[1] - repo := m[2] - return Upstream{URLs: []string{c.URLs[0]}, Name: user + "/" + repo} - } - return Upstream{URLs: []string{c.URLs[0]}, Name: "???"} - }, - }). - Parse(htmlTmplStr)) - var out bytes.Buffer - if err := tmpl.Execute(&out, map[string]any{ - "Contribs": contribs, - "Tags": tags, - "Upstreams": upstreams, - "Standups": standups, - "StandupCalendar": BuildCalendar(standups, func(status *MastodonStatus) Date { return DateOf(status.CreatedAt.Local()) }), - }); err != nil { - return err - } - if err := os.WriteFile("public/imworkingon/index.new.html", out.Bytes(), 0666); err != nil { - return err - } - if err := os.Rename("public/imworkingon/index.new.html", "public/imworkingon/index.html"); err != nil { - return err - } - return nil -} diff --git a/cmd/generate/src_contribs.go b/cmd/generate/src_contribs.go deleted file mode 100644 index 6db6764..0000000 --- a/cmd/generate/src_contribs.go +++ /dev/null @@ -1,400 +0,0 @@ -package main - -import ( - "fmt" - "net/url" - "os" - "regexp" - "strings" - "time" - - "sigs.k8s.io/yaml" -) - -type User struct { - Name string `json:"name"` - URL string `json:"url"` -} - -type Contribution struct { - URLs []string `json:"urls"` - Tags []string `json:"tags"` - SponsoredBy string `json:"sponsored-by"` - Desc string `json:"desc"` - - SubmittedAt time.Time `json:"submitted-at"` - LastUpdatedAt time.Time `json:"last-updated-at"` - LastUpdatedBy User `json:"last-updated-by"` - Status string `json:"status"` - - StatusClass string `json:"-"` -} - -func ReadContribs(filename string) ([]Contribution, error) { - bs, err := os.ReadFile(filename) - if err != nil { - return nil, fmt.Errorf("contribs: %q: %w", filename, err) - } - var ret []Contribution - if err := yaml.UnmarshalStrict(bs, &ret); err != nil { - return nil, fmt.Errorf("contribs: %q: %w", filename, err) - } - for i := range ret { - contrib := ret[i] - if err := contrib.Fill(); err != nil { - return nil, fmt.Errorf("contribs: %q: %w", filename, err) - } - ret[i] = contrib - } - return ret, nil -} - -func (c *Contribution) Fill() error { - var err error - if c.SubmittedAt.IsZero() { - c.SubmittedAt, err = c.fetchSubmittedAt() - if err != nil { - return err - } - } - if c.LastUpdatedAt.IsZero() { - c.LastUpdatedAt, c.LastUpdatedBy, err = c.fetchLastUpdated() - if err != nil { - return err - } - } - if c.Status == "" { - c.Status, err = c.fetchStatus() - if err != nil { - return err - } - } - c.StatusClass, err = classifyStatus(c.Status) - if err != nil { - return err - } - for _, u := range c.URLs { - if m := reGoLangGerritCL.FindStringSubmatch(u); m != nil { - c.URLs = append(c.URLs, "https://golang.org/cl/"+m[1]) - } - } - return nil -} - -func classifyStatus(status string) (string, error) { - switch { - case strings.Contains(status, "released") || strings.Contains(status, "deployed"): - return "released", nil - case strings.Contains(status, "merged"): - return "merged", nil - case strings.Contains(status, "open"): - return "open", nil - case strings.Contains(status, "closed") || strings.Contains(status, "locked"): - return "closed", nil - default: - return "", fmt.Errorf("unrecognized status string: %q", status) - } -} - -var ( - reGoLangGerritCL = regexp.MustCompile(`https://go-review\.googlesource\.com/c/[^/?#]+/\+/([0-9]+)(?:\?[^#]*)?(?:#.*)?$`) - reGitHubPR = regexp.MustCompile(`^https://github\.com/([^/?#]+)/([^/?#]+)/pull/([0-9]+)(?:\?[^#]*)?(?:#.*)?$`) - reGitHubCommit = regexp.MustCompile(`^https://github\.com/([^/?#]+)/([^/?#]+)/commit/([0-9a-f]+)(?:\?[^#]*)?(?:#.*)?$`) - reGitLabMR = regexp.MustCompile(`^https://([^/]+)/([^?#]+)/-/merge_requests/([0-9]+)(?:\?[^#]*)?(?:#.*)?$`) - rePiperMailDate = regexp.MustCompile(`^\s*<I>([^<]+)</I>\s*$`) -) - -const ( - statusOpen = "open" - statusMerged = "merged, not yet in a release" - statusReleasedFmt = "merged, released in %s" -) - -func (c Contribution) fetchStatus() (string, error) { - if m := reGitHubPR.FindStringSubmatch(c.URLs[0]); m != nil { - user := m[1] - repo := m[2] - prnum := m[3] - - urlStr := "https://api.github.com/repos/" + user + "/" + repo + "/pulls/" + prnum - - var obj struct { - // State values are "open" and "closed". - State string `json:"state"` - Merged bool `json:"merged"` - MergeCommitSha string `json:"merge_commit_sha"` - } - if err := httpGetJSON(urlStr, &obj); err != nil { - return "", err - } - ret := obj.State - if obj.Merged { - ret = statusMerged - tag, err := getGitTagThatContainsAll("https://github.com/"+user+"/"+repo, obj.MergeCommitSha) - if err != nil { - return "", err - } - if tag != "" { - ret = fmt.Sprintf(statusReleasedFmt, tag) - } - } - - return ret, nil - } - if m := reGitLabMR.FindStringSubmatch(c.URLs[0]); m != nil { - authority := m[1] - projectID := m[2] - mrnum := m[3] - - urlStr := "https://" + authority + "/api/v4/projects/" + url.QueryEscape(projectID) + "/merge_requests/" + mrnum - - var obj struct { - // State values are "opened", "closed", "locked", and "merged". - State string `json:"state"` - MergeCommitSha string `json:"merge_commit_sha"` - SquashCommitSha string `json:"squash_commit_sha"` - } - if err := httpGetJSON(urlStr, &obj); err != nil { - return "", err - } - - ret := obj.State - if ret == "opened" { - ret = statusOpen - } - - if ret == "merged" { - ret = statusMerged - var mergeCommit string - if obj.MergeCommitSha != "" { - mergeCommit = obj.MergeCommitSha - } - if obj.SquashCommitSha != "" { - mergeCommit = obj.SquashCommitSha - } - if mergeCommit != "" { - tag, err := getGitTagThatContainsAll("https://"+authority+"/"+projectID+".git", mergeCommit) - if err != nil { - return "", err - } - if tag != "" { - ret = fmt.Sprintf(statusReleasedFmt, tag) - } - } - } - - return ret, nil - } - if len(c.URLs) > 1 { - var gitURL string - var gitCommits []string - for _, u := range c.URLs[1:] { - if m := reGitHubCommit.FindStringSubmatch(u); m != nil { - user := m[1] - repo := m[2] - hash := m[3] - - gitURL = "https://github.com/" + user + "/" + repo - gitCommits = append(gitCommits, hash) - } - } - if len(gitCommits) > 0 { - ret := statusMerged - tag, err := getGitTagThatContainsAll(gitURL, gitCommits...) - if err != nil { - return "", err - } - if tag != "" { - ret = fmt.Sprintf(statusReleasedFmt, tag) - } - return ret, nil - } - } - return "", fmt.Errorf("idk how to get status for %q", c.URLs[0]) -} - -func (c Contribution) fetchSubmittedAt() (time.Time, error) { - if m := reGitHubPR.FindStringSubmatch(c.URLs[0]); m != nil { - user := m[1] - repo := m[2] - prnum := m[3] - - urlStr := "https://api.github.com/repos/" + user + "/" + repo + "/pulls/" + prnum - - var obj struct { - CreatedAt time.Time `json:"created_at"` - } - if err := httpGetJSON(urlStr, &obj); err != nil { - return time.Time{}, err - } - return obj.CreatedAt, nil - } - if m := reGitLabMR.FindStringSubmatch(c.URLs[0]); m != nil { - authority := m[1] - projectID := m[2] - mrnum := m[3] - - urlStr := "https://" + authority + "/api/v4/projects/" + url.QueryEscape(projectID) + "/merge_requests/" + mrnum - - var obj struct { - CreatedAt time.Time `json:"created_at"` - } - if err := httpGetJSON(urlStr, &obj); err != nil { - return time.Time{}, err - } - return obj.CreatedAt, nil - } - if strings.Contains(c.URLs[0], "/pipermail/") { - htmlStr, err := httpGet(c.URLs[0]) - if err != nil { - return time.Time{}, err - } - for _, line := range strings.Split(htmlStr, "\n") { - if m := rePiperMailDate.FindStringSubmatch(line); m != nil { - return time.Parse(time.UnixDate, m[1]) - } - } - } - return time.Time{}, fmt.Errorf("idk how to get created timestamp for %q", c.URLs[0]) -} - -func (c Contribution) fetchLastUpdated() (time.Time, User, error) { - if m := reGitHubPR.FindStringSubmatch(c.URLs[0]); m != nil { - user := m[1] - repo := m[2] - prnum := m[3] - - var obj struct { - UpdatedAt time.Time `json:"updated_at"` - MergedAt time.Time `json:"merged_at"` - MergedBy struct { - Login string `json:"login"` - HTMLURL string `json:"html_url"` - } `json:"merged_by"` - } - if err := httpGetJSON("https://api.github.com/repos/"+user+"/"+repo+"/pulls/"+prnum, &obj); err != nil { - return time.Time{}, User{}, err - } - - retUpdatedAt := obj.UpdatedAt - var retUser User - - if obj.MergedAt == retUpdatedAt { - retUser.Name = obj.MergedBy.Login - retUser.URL = obj.MergedBy.HTMLURL - } - if retUser == (User{}) { - // "normal" comments - var comments []struct { - UpdatedAt time.Time `json:"updated_at"` - User struct { - Login string `json:"login"` - HTMLURL string `json:"html_url"` - } `json:"user"` - } - if err := httpGetPaginatedJSON("https://api.github.com/repos/"+user+"/"+repo+"/issues/"+prnum+"/comments", &comments, githubPagination); err != nil { - return time.Time{}, User{}, err - } - for _, comment := range comments { - if comment.UpdatedAt == retUpdatedAt || comment.UpdatedAt.Add(1*time.Second) == retUpdatedAt { - retUser.Name = comment.User.Login - retUser.URL = comment.User.HTMLURL - break - } - } - } - if retUser == (User{}) { - // comments on a specific part of the diff - var reviewComments []struct { - UpdatedAt time.Time `json:"updated_at"` - User struct { - Login string `json:"login"` - HTMLURL string `json:"html_url"` - } `json:"user"` - } - if err := httpGetPaginatedJSON("https://api.github.com/repos/"+user+"/"+repo+"/pulls/"+prnum+"/comments", &reviewComments, githubPagination); err != nil { - return time.Time{}, User{}, err - } - for _, comment := range reviewComments { - if comment.UpdatedAt == retUpdatedAt { - retUser.Name = comment.User.Login - retUser.URL = comment.User.HTMLURL - break - } - } - } - if retUser == (User{}) { - var events []struct { - CreatedAt time.Time `json:"created_at"` - Actor struct { - Login string `json:"login"` - HTMLURL string `json:"html_url"` - } `json:"actor"` - } - if err := httpGetJSON("https://api.github.com/repos/"+user+"/"+repo+"/issues/"+prnum+"/events", &events); err != nil { - return time.Time{}, User{}, err - } - for _, event := range events { - if event.CreatedAt == retUpdatedAt { - retUser.Name = event.Actor.Login - retUser.URL = event.Actor.HTMLURL - break - } - } - } - - return retUpdatedAt, retUser, nil - } - if m := reGitLabMR.FindStringSubmatch(c.URLs[0]); m != nil { - authority := m[1] - projectID := m[2] - mrnum := m[3] - - urlStr := "https://" + authority + "/api/v4/projects/" + url.QueryEscape(projectID) + "/merge_requests/" + mrnum - - var obj struct { - UpdatedAt time.Time `json:"updated_at"` - } - if err := httpGetJSON(urlStr, &obj); err != nil { - return time.Time{}, User{}, err - } - return obj.UpdatedAt, User{}, nil - } - - var ret time.Time - if len(c.URLs) > 1 { - for _, u := range c.URLs[1:] { - if m := reGitHubCommit.FindStringSubmatch(u); m != nil { - user := m[1] - repo := m[2] - hash := m[3] - - urlStr := "https://api.github.com/repos/" + user + "/" + repo + "/commits/" + hash - var obj struct { - Commit struct { - Author struct { - Date time.Time `json:"date"` - } `json:"author"` - Committer struct { - Date time.Time `json:"date"` - } `json:"committer"` - } `json:"commit"` - } - if err := httpGetJSON(urlStr, &obj); err != nil { - return time.Time{}, User{}, err - } - if obj.Commit.Author.Date.After(ret) { - ret = obj.Commit.Author.Date - } - if obj.Commit.Committer.Date.After(ret) { - ret = obj.Commit.Committer.Date - } - } - } - } - if !ret.IsZero() { - return ret, User{}, nil - } - - return time.Time{}, User{}, nil //fmt.Errorf("idk how to get updated timestamp for %q", c.URLs[0]) -} diff --git a/cmd/generate/src_contribs_test.go b/cmd/generate/src_contribs_test.go deleted file mode 100644 index 57ffc0f..0000000 --- a/cmd/generate/src_contribs_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alecthomas/assert/v2" -) - -func TestClassifyStatus(t *testing.T) { - testcases := map[string]struct { - Str string - Err string - }{ - "merged+deployed": {"released", ""}, - "merged, deployed": {"released", ""}, - "released in v1.2": {"released", ""}, - "merged, released in v1.2": {"released", ""}, - statusReleasedFmt: {"released", ""}, - - "merged": {"merged", ""}, - statusMerged: {"merged", ""}, - - "open": {"open", ""}, - - "closed": {"closed", ""}, - "locked": {"closed", ""}, - } - for in, exp := range testcases { - t.Run(in, func(t *testing.T) { - actStr, actErr := classifyStatus(in) - assert.Equal(t, exp.Str, actStr) - if exp.Err == "" { - assert.NoError(t, actErr) - } else { - assert.EqualError(t, actErr, exp.Err) - } - }) - } -} diff --git a/cmd/generate/src_mastodon.go b/cmd/generate/src_mastodon.go deleted file mode 100644 index b4b54a8..0000000 --- a/cmd/generate/src_mastodon.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "html/template" - "net/url" - "slices" - "time" -) - -type MastodonStatus struct { - ID string `json:"id"` - CreatedAt time.Time `json:"created_at"` - URL string `json:"url"` - Content template.HTML `json:"content"` -} - -// Returns statuses sorted from newest to oldest. -func ReadStandups(server, username string) ([]*MastodonStatus, error) { - var account struct { - ID string `json:"id"` - } - if err := httpGetJSON(server+"/api/v1/accounts/lookup?acct="+username, &account); err != nil { - return nil, err - } - - var statuses []*MastodonStatus - if err := httpGetPaginatedJSON(server+"/api/v1/accounts/"+account.ID+"/statuses", &statuses, func(_ int) url.Values { - params := make(url.Values) - params.Set("tagged", "DailyStandUp") - params.Set("exclude_reblogs", "true") - if len(statuses) > 0 { - params.Set("max_id", statuses[len(statuses)-1].ID) - } - return params - }); err != nil { - return nil, err - } - - ignoreList := []string{ - "https://fosstodon.org/@lukeshu/112198267818432116", - "https://fosstodon.org/@lukeshu/112198241414760456", - } - statuses = slices.DeleteFunc(statuses, func(status *MastodonStatus) bool { - return slices.Contains(ignoreList, status.URL) - }) - - return statuses, nil -} diff --git a/cmd/generate/src_tags.go b/cmd/generate/src_tags.go deleted file mode 100644 index 8dcf554..0000000 --- a/cmd/generate/src_tags.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "sigs.k8s.io/yaml" -) - -type TagInfo struct { - PrettyName string `json:"prettyName"` - Desc string `json:"desc"` -} - -func ReadTags(filename string) (map[string]TagInfo, error) { - bs, err := os.ReadFile(filename) - if err != nil { - return nil, fmt.Errorf("tags: %q: %w", filename, err) - } - var ret map[string]TagInfo - if err := yaml.UnmarshalStrict(bs, &ret); err != nil { - return nil, fmt.Errorf("tags: %q: %w", filename, err) - } - return ret, nil -} diff --git a/cmd/generate/src_upstreams.go b/cmd/generate/src_upstreams.go deleted file mode 100644 index 03f72ec..0000000 --- a/cmd/generate/src_upstreams.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - _ "embed" - "fmt" - "net/url" - "os" - "path" - "strings" - - "sigs.k8s.io/yaml" -) - -type Upstream struct { - URLs []string `json:"urls"` - Name string `json:"name"` - Desc string `json:"desc"` -} - -func ReadUpstreams(filename string) ([]Upstream, error) { - bs, err := os.ReadFile(filename) - if err != nil { - return nil, fmt.Errorf("upstreams: %q: %w", filename, err) - } - var ret []Upstream - if err := yaml.UnmarshalStrict(bs, &ret); err != nil { - return nil, fmt.Errorf("upstreams: %q: %w", filename, err) - } - for i := range ret { - upstream := ret[i] - if err := upstream.Fill(); err != nil { - return nil, fmt.Errorf("upstreams: %q: %w", filename, err) - } - ret[i] = upstream - } - return ret, nil -} - -func (upstream *Upstream) Fill() error { - if upstream.Name == "" { - u, err := url.Parse(upstream.URLs[0]) - if err != nil { - return err - } - _, upstream.Name = path.Split(strings.TrimSuffix(path.Clean(u.Path), ".git")) - } - return nil -} |