internal/deps/report.go

69d8aa4ff9115d6ca1116e09a232e7a6416e4645
gitbay/internal/deps/report.go history · blame · raw

90 lines · 2582 bytes

 1package deps
 2
 3import (
 4	"fmt"
 5	"sort"
 6	"strings"
 7
 8	"gitbay.org/gitbay/internal/store"
 9)
10
11// IssueTitle is fixed so the issue this worker maintains is recognizable
12// across sweeps, in listings, and to the maintainer.
13const IssueTitle = "Dependency updates available"
14
15// ecosystemNames are how the four appear in the issue body.
16var ecosystemNames = map[string]string{
17	EcoGo:    "Go",
18	EcoNPM:   "npm",
19	EcoCargo: "Cargo",
20	EcoPyPI:  "PyPI",
21}
22
23// order is the sequence sections appear in.
24var order = []string{EcoGo, EcoNPM, EcoCargo, EcoPyPI}
25
26// Body renders the issue: one table per ecosystem, sorted, so a diff
27// between sweeps reads as a diff of what is behind.
28func Body(branch string, reports []store.DepReport) string {
29	var b strings.Builder
30	fmt.Fprintf(&b, "Dependencies declared on `%s` are behind their latest release. "+
31		"This issue is kept up to date as that changes, and closed once nothing is behind.\n", branch)
32	byEco := map[string][]store.DepReport{}
33	for _, r := range reports {
34		byEco[r.Ecosystem] = append(byEco[r.Ecosystem], r)
35	}
36	for _, eco := range order {
37		rows := byEco[eco]
38		if len(rows) == 0 {
39			continue
40		}
41		sort.Slice(rows, func(i, j int) bool { return rows[i].Name < rows[j].Name })
42		fmt.Fprintf(&b, "\n### %s\n\n", ecosystemNames[eco])
43		cells := [][3]string{{"Package", "Current", "Latest"}}
44		for _, r := range rows {
45			cells = append(cells, [3]string{"`" + r.Name + "`", r.Current, r.Latest})
46		}
47		b.WriteString(table(cells))
48	}
49	return b.String()
50}
51
52// table pads every cell to its column, because the body is read twice: as
53// a rendered table on the web, where the padding is ignored, and as the
54// plain text of the notification mail, where it is the only thing keeping
55// the columns readable. Row 0 is the header.
56func table(cells [][3]string) string {
57	var w [3]int
58	for _, c := range cells {
59		for i := range w {
60			if n := len(c[i]); n > w[i] {
61				w[i] = n
62			}
63		}
64	}
65	var b strings.Builder
66	row := func(c [3]string) {
67		fmt.Fprintf(&b, "| %-*s | %-*s | %-*s |\n", w[0], c[0], w[1], c[1], w[2], c[2])
68	}
69	row(cells[0])
70	row([3]string{strings.Repeat("-", w[0]), strings.Repeat("-", w[1]), strings.Repeat("-", w[2])})
71	for _, c := range cells[1:] {
72		row(c)
73	}
74	return b.String()
75}
76
77// same reports whether two outdated sets are equal, which is what decides
78// between leaving the issue alone and rewriting it. Both sides arrive
79// sorted by (ecosystem, name).
80func same(a, b []store.DepReport) bool {
81	if len(a) != len(b) {
82		return false
83	}
84	for i := range a {
85		if a[i] != b[i] {
86			return false
87		}
88	}
89	return true
90}