Files
avo/internal/cmd/docgen/main.go
Michael McLoughlin 9fee3b0ead 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
2022-04-17 19:41:29 -07:00

149 lines
2.5 KiB
Go

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