diff options
Diffstat (limited to 'cmd/gen-imworkingon/src_contribs.go')
-rw-r--r-- | cmd/gen-imworkingon/src_contribs.go | 223 |
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]) +} |