package main import ( "fmt" "net/url" "os" "regexp" "strings" "time" "sigs.k8s.io/yaml" ) 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"` Status string `json:"status"` } 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.Status == "" { c.Status, err = c.fetchStatus() if err != nil { return err } } return nil } var ( reGitHubPR = regexp.MustCompile(`^https://github.com/([^/?#]+)/([^/?#]+)/pull/([0-9]+)(?:\?[^#]*)?(?:#.*)?$`) reGitLabMR = regexp.MustCompile(`^https://([^/]+)/([^?#]+)/-/merge_requests/([0-9]+)(?:\?[^#]*)?(?:#.*)?$`) rePiperMailDate = regexp.MustCompile(`^\s*([^<]+)\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 string `json:"state"` } if err := httpGetJSON(urlStr, &obj); err != nil { return "", err } return obj.State, 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 string `json:"state"` } if err := httpGetJSON(urlStr, &obj); err != nil { return "", err } return obj.State, nil } if len(c.URLs) > 1 { for _, u := range c.URLs[1:] { if strings.Contains(u, "/commit/") { return "merged", 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 timestamps for %q", c.URLs[0]) }