doc: generate README with docgen tool (#251)

Introduces a docgen tool for templated documentation generation, and uses it
to generate the README.

At the moment this change makes minimal difference to generating it with
embedmd. The difference is that docgen opens up the possibility to generate
documentation with more elaborate templating. The specific use case currently
in mind is including an adopters list that's kept in sync with the third-party
packages file.

Updates #101
This commit is contained in:
Michael McLoughlin
2022-04-17 19:41:29 -07:00
committed by GitHub
parent 3066c12247
commit 9fee3b0ead
4 changed files with 272 additions and 6 deletions

148
internal/cmd/docgen/main.go Normal file
View File

@@ -0,0 +1,148 @@
// Command docgen generates documentation from templates.
package main
import (
"bufio"
"bytes"
"embed"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"regexp"
"strings"
"text/template"
)
func main() {
log.SetPrefix("docgen: ")
log.SetFlags(0)
if err := mainerr(); err != nil {
log.Fatal(err)
}
}
var (
typ = flag.String("type", "", "documentation type")
tmpl = flag.String("tmpl", "", "explicit template file (overrides -type)")
output = flag.String("output", "", "path to output file (default stdout)")
)
func mainerr() (err error) {
flag.Parse()
// Initialize template.
t := template.New("doc")
t.Funcs(template.FuncMap{
"include": include,
"snippet": snippet,
})
// Load template.
s, err := load()
if err != nil {
return err
}
if _, err := t.Parse(s); err != nil {
return err
}
// Execute.
var buf bytes.Buffer
if err := t.Execute(&buf, nil); err != nil {
return err
}
body := buf.Bytes()
// Output.
if *output != "" {
err = ioutil.WriteFile(*output, body, 0o640)
} else {
_, err = os.Stdout.Write(body)
}
if err != nil {
return err
}
return nil
}
//go:embed templates
var templates embed.FS
// load template.
func load() (string, error) {
// Prefer explicit filename, if provided.
if *tmpl != "" {
b, err := ioutil.ReadFile(*tmpl)
if err != nil {
return "", err
}
return string(b), nil
}
// Otherwise expect a named type.
if *typ == "" {
return "", errors.New("missing documentation type")
}
path := fmt.Sprintf("templates/%s.tmpl", *typ)
b, err := templates.ReadFile(path)
if err != nil {
return "", fmt.Errorf("unknown documentation type %q", *typ)
}
return string(b), nil
}
// include template function.
func include(filename string) (string, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}
return string(b), nil
}
// snippet of a file between start and end regular expressions.
func snippet(filename, start, end string) (string, error) {
// Parse regular expressions.
startx, err := regexp.Compile(start)
if err != nil {
return "", err
}
endx, err := regexp.Compile(end)
if err != nil {
return "", err
}
// Read the full file.
data, err := include(filename)
if err != nil {
return "", err
}
// Collect matched lines.
var buf bytes.Buffer
output := false
s := bufio.NewScanner(strings.NewReader(data))
for s.Scan() {
line := s.Text()
if startx.MatchString(line) {
output = true
}
if output {
fmt.Fprintln(&buf, line)
}
if endx.MatchString(line) {
output = false
}
}
return buf.String(), nil
}