deps: pad the issue table so the mail is legible !132
2 files changed, +72 −2
Layout: unified · split
internal/deps/report.go +29 −2
| @@ -39,10 +39,37 @@ func Body(branch string, reports []store.DepReport) string { | ||
| 39 | 39 | continue |
| 40 | 40 | } |
| 41 | 41 | sort.Slice(rows, func(i, j int) bool { return rows[i].Name < rows[j].Name }) |
| 42 | fmt.Fprintf(&b, "\n### %s\n\n| Package | Current | Latest |\n| --- | --- | --- |\n", ecosystemNames[eco]) | |
| 42 | fmt.Fprintf(&b, "\n### %s\n\n", ecosystemNames[eco]) | |
| 43 | cells := [][3]string{{"Package", "Current", "Latest"}} | |
| 43 | 44 | for _, r := range rows { |
| 44 | fmt.Fprintf(&b, "| `%s` | %s | %s |\n", r.Name, r.Current, r.Latest) | |
| 45 | cells = append(cells, [3]string{"`" + r.Name + "`", r.Current, r.Latest}) | |
| 45 | 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. | |
| 56 | func 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) | |
| 46 | 73 | } |
| 47 | 74 | return b.String() |
| 48 | 75 | } |
internal/deps/report_test.go added +43
| @@ -0,0 +1,43 @@ | ||
| 1 | package deps | |
| 2 | ||
| 3 | import ( | |
| 4 | "strings" | |
| 5 | "testing" | |
| 6 | ||
| 7 | "gitbay.org/gitbay/internal/store" | |
| 8 | ) | |
| 9 | ||
| 10 | // The body is read as a rendered table on the web and as the plain text of | |
| 11 | // the notification mail, so every row has to line up on its own. | |
| 12 | func TestBodyAlignsColumns(t *testing.T) { | |
| 13 | body := Body("main", []store.DepReport{ | |
| 14 | {Ecosystem: EcoGo, Name: "go.yaml.in/yaml/v3", Current: "v3.0.4", Latest: "v3.0.5"}, | |
| 15 | {Ecosystem: EcoGo, Name: "golang.org/x/net", Current: "v0.57.0", Latest: "v0.58.0"}, | |
| 16 | {Ecosystem: EcoNPM, Name: "react", Current: "18.2.0", Latest: "19.0.0"}, | |
| 17 | }) | |
| 18 | var pipes []int | |
| 19 | for _, line := range strings.Split(body, "\n") { | |
| 20 | if !strings.HasPrefix(line, "|") { | |
| 21 | pipes = append(pipes, -1) // section break | |
| 22 | continue | |
| 23 | } | |
| 24 | pipes = append(pipes, len(line)) | |
| 25 | } | |
| 26 | // Within a run of table lines every line is the same length. | |
| 27 | var run []int | |
| 28 | for _, n := range append(pipes, -1) { | |
| 29 | if n == -1 { | |
| 30 | for _, w := range run { | |
| 31 | if w != run[0] { | |
| 32 | t.Fatalf("ragged table in:\n%s", body) | |
| 33 | } | |
| 34 | } | |
| 35 | run = nil | |
| 36 | continue | |
| 37 | } | |
| 38 | run = append(run, n) | |
| 39 | } | |
| 40 | if !strings.Contains(body, "| `golang.org/x/net` | v0.57.0 | v0.58.0 |") { | |
| 41 | t.Errorf("row not padded to its column:\n%s", body) | |
| 42 | } | |
| 43 | } | |