-
Notifications
You must be signed in to change notification settings - Fork 240
/
gen.go
111 lines (93 loc) · 2.51 KB
/
gen.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//go:build ignore
package main
import (
"bytes"
"flag"
"fmt"
"go/parser"
"go/token"
"log"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
)
const (
sectionStart = "<!-- START EXAMPLES -->"
sectionEnd = "<!-- END EXAMPLES -->"
descStart = "demonstrating"
descPrefix = "how to"
)
var spaceRE = regexp.MustCompile(`\s+`)
func main() {
readme := flag.String("readme", "README.md", "file to update")
mask := flag.String("mask", "*/main.go", "")
flag.Parse()
buf, err := os.ReadFile(*readme)
if err != nil {
log.Fatal(err)
}
start, end := bytes.Index(buf, []byte(sectionStart)), bytes.Index(buf, []byte(sectionEnd))
if start == -1 || end == -1 {
log.Fatalf("could not find %s or %s in %s", sectionStart, sectionEnd, *readme)
}
files, err := filepath.Glob(*mask)
if err != nil {
log.Fatal(err)
}
type ex struct {
name, desc string
}
var examples []ex
for _, fn := range files {
f, err := parser.ParseFile(token.NewFileSet(), fn, nil, parser.ParseComments)
if err != nil {
log.Fatal(err)
}
n := filepath.Base(filepath.Dir(fn))
name := fmt.Sprintf("[%s](/%s)", n, n)
// clean comment
comment := spaceRE.ReplaceAllString(f.Doc.Text(), " ")
i := strings.Index(comment, descStart)
if i == -1 {
log.Fatalf("could not find %q in doc comment for %s", descStart, fn)
}
comment = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(comment[i+len(descStart):]), descPrefix))
i = strings.Index(comment, ".")
if i != -1 {
comment = comment[:i]
}
comment = strings.TrimSuffix(comment, ".")
examples = append(examples, ex{name, comment})
}
sort.Slice(examples, func(i, j int) bool { return strings.Compare(examples[i].name, examples[j].name) < 0 })
// determine max length
var namelen, desclen int
for _, e := range examples {
namelen, desclen = max(namelen, len(e.name)), max(desclen, len(e.desc))
}
// generate
out := new(bytes.Buffer)
out.Write(buf[:start+len(sectionStart)])
out.WriteString(fmt.Sprintf("\n| %s | %s |\n", pad("Example", " ", namelen), pad("Description", " ", desclen)))
out.WriteString(fmt.Sprintf("|%s|%s|\n", pad("", "-", namelen+2), pad("", "-", desclen+2)))
for _, e := range examples {
out.WriteString(fmt.Sprintf("| %s | %s |\n", pad(e.name, " ", namelen), pad(e.desc, " ", desclen)))
}
out.Write(buf[end:])
// write
err = os.WriteFile(*readme, out.Bytes(), 0644)
if err != nil {
log.Fatal(err)
}
}
func max(a, b int) int {
if a >= b {
return a
}
return b
}
func pad(s, v string, n int) string {
return s + strings.Repeat(v, n-len(s))
}