summaryrefslogtreecommitdiff
path: root/cmd/gen-imworkingon/src_contribs.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/gen-imworkingon/src_contribs.go')
-rw-r--r--cmd/gen-imworkingon/src_contribs.go223
1 files changed, 223 insertions, 0 deletions
diff --git a/cmd/gen-imworkingon/src_contribs.go b/cmd/gen-imworkingon/src_contribs.go
new file mode 100644
index 0000000..5694156
--- /dev/null
+++ b/cmd/gen-imworkingon/src_contribs.go
@@ -0,0 +1,223 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "time"
+
+ "sigs.k8s.io/yaml"
+)
+
+type User struct {
+ Name string `json:"name"`
+ URL string `json:"url"`
+}
+
+type Contribution struct {
+ ID string
+ 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 := reGoogleGerritCL.FindStringSubmatch(u); m != nil && m[1] == "go-review.googlesource.com" {
+ c.URLs = append(c.URLs, "https://golang.org/cl/"+m[3])
+ }
+ }
+ 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)
+ }
+}
+
+const (
+ statusOpen = "open"
+ statusMerged = "merged, not yet in a release"
+ statusReleasedFmt = "merged, released in %s"
+)
+
+type Forge interface {
+ FetchStatus(urls []string) (string, error)
+ FetchSubmittedAt(urls []string) (time.Time, error)
+ FetchLastUpdated(urls []string) (time.Time, User, error)
+}
+
+var forges = []Forge{
+ // precedence only matters for .FetchStatus.
+
+ // highest precedence
+ Gerrit{},
+ GitHub{},
+ GitLab{},
+ Forgejo{"codeberg.org"},
+ PartPiperMail{},
+ PartGit{},
+ // lowest precedence
+}
+
+func fetchPerURLStatus(urls []string, perURL func(string) (string, error)) (string, error) {
+ for _, u := range urls {
+ status, err := perURL(u)
+ if err != nil {
+ return "", err
+ }
+ if status != "" {
+ return status, nil
+ }
+ }
+ return "", nil
+}
+
+func (c Contribution) fetchStatus() (string, error) {
+ for _, forge := range forges {
+ status, err := forge.FetchStatus(c.URLs)
+ if err != nil {
+ return "", err
+ }
+ if status != "" {
+ return status, nil
+ }
+ }
+ return "", fmt.Errorf("idk how to get status for %q", c.URLs[0])
+}
+
+func fetchPerURLSubmittedAt(urls []string, perURL func(string) (time.Time, error)) (time.Time, error) {
+ var ret time.Time
+ for _, u := range urls {
+ submittedAt, err := perURL(u)
+ if err != nil {
+ return time.Time{}, err
+ }
+ if !submittedAt.IsZero() && (ret.IsZero() || submittedAt.Before(ret)) {
+ ret = submittedAt
+ }
+ }
+ return ret, nil
+}
+
+func (c Contribution) fetchSubmittedAt() (time.Time, error) {
+ var ret time.Time
+ for _, forge := range forges {
+ submittedAt, err := forge.FetchSubmittedAt(c.URLs)
+ if err != nil {
+ return time.Time{}, err
+ }
+ if !submittedAt.IsZero() && (ret.IsZero() || submittedAt.Before(ret)) {
+ ret = submittedAt
+ }
+ }
+ if !ret.IsZero() {
+ return ret, nil
+ }
+ return time.Time{}, fmt.Errorf("idk how to get created timestamp for %q", c.URLs[0])
+}
+
+func withinOneSecond(a, b time.Time) bool {
+ d := a.Sub(b)
+ if d < 0 {
+ d = -d
+ }
+ return d <= time.Second
+}
+
+func fetchPerURLLastUpdated(urls []string, perURL func(string) (time.Time, User, error)) (time.Time, User, error) {
+ var ret struct {
+ time.Time
+ User
+ }
+ for _, u := range urls {
+ updatedAt, updatedBy, err := perURL(u)
+ if err != nil {
+ return time.Time{}, User{}, err
+ }
+ if !updatedAt.IsZero() && (ret.Time.IsZero() || updatedAt.After(ret.Time)) {
+ ret.Time, ret.User = updatedAt, updatedBy
+ }
+ }
+ return ret.Time, ret.User, nil
+}
+
+func (c Contribution) fetchLastUpdated() (time.Time, User, error) {
+ var ret struct {
+ time.Time
+ User
+ }
+ for _, forge := range forges {
+ updatedAt, updatedBy, err := forge.FetchLastUpdated(c.URLs)
+ if err != nil {
+ return time.Time{}, User{}, err
+ }
+ if !updatedAt.IsZero() && (ret.Time.IsZero() || updatedAt.After(ret.Time)) {
+ ret.Time, ret.User = updatedAt, updatedBy
+ }
+ }
+ if !ret.Time.IsZero() {
+ return ret.Time, ret.User, nil
+ }
+ return time.Time{}, User{}, nil //fmt.Errorf("idk how to get updated timestamp for %q", c.URLs[0])
+}