//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)) }