2022-04-17 19:41:29 -07:00
|
|
|
// Command docgen generates documentation from templates.
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bufio"
|
|
|
|
|
"bytes"
|
|
|
|
|
"embed"
|
|
|
|
|
"errors"
|
|
|
|
|
"flag"
|
|
|
|
|
"fmt"
|
|
|
|
|
"log"
|
2022-05-08 15:22:26 -07:00
|
|
|
"math"
|
2022-04-18 00:49:52 -07:00
|
|
|
"net/url"
|
2022-04-17 19:41:29 -07:00
|
|
|
"os"
|
|
|
|
|
"regexp"
|
2022-04-18 00:49:52 -07:00
|
|
|
"strconv"
|
2022-04-17 19:41:29 -07:00
|
|
|
"strings"
|
|
|
|
|
"text/template"
|
2022-04-17 22:38:54 -07:00
|
|
|
|
|
|
|
|
"github.com/mmcloughlin/avo/tests/thirdparty"
|
2022-04-17 19:41:29 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
log.SetPrefix("docgen: ")
|
|
|
|
|
log.SetFlags(0)
|
|
|
|
|
if err := mainerr(); err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
2022-05-08 16:55:51 -07:00
|
|
|
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)")
|
|
|
|
|
suitefilename = flag.String("suite", "", "third-party test suite configuration")
|
2022-04-17 19:41:29 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func mainerr() (err error) {
|
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
|
|
// Initialize template.
|
|
|
|
|
t := template.New("doc")
|
|
|
|
|
|
2022-05-08 17:23:31 -07:00
|
|
|
t.Option("missingkey=error")
|
|
|
|
|
|
2022-04-17 19:41:29 -07:00
|
|
|
t.Funcs(template.FuncMap{
|
|
|
|
|
"include": include,
|
|
|
|
|
"snippet": snippet,
|
2022-04-17 23:57:05 -07:00
|
|
|
"avatar": avatar,
|
2022-05-08 15:22:26 -07:00
|
|
|
"stars": stars,
|
2022-04-17 19:41:29 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Load template.
|
|
|
|
|
s, err := load()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, err := t.Parse(s); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-24 20:20:11 -07:00
|
|
|
// Load third-party projects.
|
2022-05-08 16:55:51 -07:00
|
|
|
if *suitefilename == "" {
|
|
|
|
|
return errors.New("missing test suite configuration")
|
2022-04-17 22:38:54 -07:00
|
|
|
}
|
|
|
|
|
|
2022-05-08 16:55:51 -07:00
|
|
|
suite, err := thirdparty.LoadSuiteFile(*suitefilename)
|
2022-04-17 22:38:54 -07:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-17 19:41:29 -07:00
|
|
|
// Execute.
|
2022-04-17 22:38:54 -07:00
|
|
|
data := map[string]interface{}{
|
2022-05-08 16:55:51 -07:00
|
|
|
"Suite": suite,
|
2022-04-17 22:38:54 -07:00
|
|
|
}
|
|
|
|
|
|
2022-04-17 19:41:29 -07:00
|
|
|
var buf bytes.Buffer
|
2022-04-17 22:38:54 -07:00
|
|
|
if err := t.Execute(&buf, data); err != nil {
|
2022-04-17 19:41:29 -07:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
body := buf.Bytes()
|
|
|
|
|
|
|
|
|
|
// Output.
|
|
|
|
|
if *output != "" {
|
2022-09-05 17:25:03 -07:00
|
|
|
err = os.WriteFile(*output, body, 0o640)
|
2022-04-17 19:41:29 -07:00
|
|
|
} 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 != "" {
|
2022-09-05 17:25:03 -07:00
|
|
|
b, err := os.ReadFile(*tmpl)
|
2022-04-17 19:41:29 -07:00
|
|
|
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) {
|
2022-09-05 17:25:03 -07:00
|
|
|
b, err := os.ReadFile(filename)
|
2022-04-17 19:41:29 -07:00
|
|
|
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
|
|
|
|
|
}
|
2022-04-17 23:57:05 -07:00
|
|
|
|
|
|
|
|
// avatar returns HTML for a Github user avatar.
|
2022-04-18 00:49:52 -07:00
|
|
|
func avatar(owner string, size int) (string, error) {
|
|
|
|
|
// Origin avatar URL from Github.
|
|
|
|
|
u := fmt.Sprintf("https://github.com/%s.png", owner)
|
|
|
|
|
|
|
|
|
|
// Use images.weserv.nl service to resize and apply circle mask.
|
|
|
|
|
v := url.Values{}
|
|
|
|
|
v.Set("url", u)
|
|
|
|
|
v.Set("w", strconv.Itoa(size))
|
|
|
|
|
v.Set("h", strconv.Itoa(size))
|
|
|
|
|
v.Set("fit", "cover")
|
|
|
|
|
v.Set("mask", "circle")
|
|
|
|
|
v.Set("maxage", "7d")
|
|
|
|
|
|
|
|
|
|
src := url.URL{
|
|
|
|
|
Scheme: "https",
|
|
|
|
|
Host: "images.weserv.nl",
|
|
|
|
|
RawQuery: v.Encode(),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build <img> tag.
|
|
|
|
|
format := `<img src="%s" width="%d" height="%d" hspace="4" valign="middle" />`
|
|
|
|
|
return fmt.Sprintf(format, src.String(), size, size), nil
|
2022-04-17 23:57:05 -07:00
|
|
|
}
|
2022-05-08 15:22:26 -07:00
|
|
|
|
|
|
|
|
// stars formats a Github star count, rounding to thousands in the same style as Github.
|
|
|
|
|
func stars(n int) string {
|
|
|
|
|
if n < 1000 {
|
|
|
|
|
return strconv.Itoa(n)
|
|
|
|
|
}
|
|
|
|
|
k := math.Round(float64(n)/100.0) / 10.0
|
|
|
|
|
return strconv.FormatFloat(k, 'f', -1, 64) + "k"
|
|
|
|
|
}
|