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

View File

@@ -34,7 +34,6 @@ $ go get -u github.com/mmcloughlin/avo
`avo` assembly generators are pure Go programs. Here's a function that adds two `uint64` values: `avo` assembly generators are pure Go programs. Here's a function that adds two `uint64` values:
[embedmd]:# (examples/add/asm.go)
```go ```go
//go:build ignore //go:build ignore
// +build ignore // +build ignore
@@ -57,14 +56,12 @@ func main() {
`go run` this code to see the assembly output. To integrate this into the rest of your Go package we recommend a [`go:generate`](https://blog.golang.org/generate) line to produce the assembly and the corresponding Go stub file. `go run` this code to see the assembly output. To integrate this into the rest of your Go package we recommend a [`go:generate`](https://blog.golang.org/generate) line to produce the assembly and the corresponding Go stub file.
[embedmd]:# (examples/add/add_test.go go /.*go:generate.*/)
```go ```go
//go:generate go run asm.go -out add.s -stubs stub.go //go:generate go run asm.go -out add.s -stubs stub.go
``` ```
After running `go generate` the [`add.s`](examples/add/add.s) file will contain the Go assembly. After running `go generate` the [`add.s`](examples/add/add.s) file will contain the Go assembly.
[embedmd]:# (examples/add/add.s)
```s ```s
// Code generated by command: go run asm.go -out add.s -stubs stub.go. DO NOT EDIT. // Code generated by command: go run asm.go -out add.s -stubs stub.go. DO NOT EDIT.
@@ -81,7 +78,6 @@ TEXT ·Add(SB), NOSPLIT, $0-24
The same call will produce the stub file [`stub.go`](examples/add/stub.go) which will enable the function to be called from your Go code. The same call will produce the stub file [`stub.go`](examples/add/stub.go) which will enable the function to be called from your Go code.
[embedmd]:# (examples/add/stub.go)
```go ```go
// Code generated by command: go run asm.go -out add.s -stubs stub.go. DO NOT EDIT. // Code generated by command: go run asm.go -out add.s -stubs stub.go. DO NOT EDIT.
@@ -101,7 +97,6 @@ See [`examples`](examples) for the full suite of examples.
Sum a slice of `uint64`s: Sum a slice of `uint64`s:
[embedmd]:# (examples/sum/asm.go /func main/ /^}/)
```go ```go
func main() { func main() {
TEXT("Sum", NOSPLIT, "func(xs []uint64) uint64") TEXT("Sum", NOSPLIT, "func(xs []uint64) uint64")
@@ -136,7 +131,6 @@ func main() {
The result from this code generator is: The result from this code generator is:
[embedmd]:# (examples/sum/sum.s)
```s ```s
// Code generated by command: go run asm.go -out sum.s -stubs stub.go. DO NOT EDIT. // Code generated by command: go run asm.go -out sum.s -stubs stub.go. DO NOT EDIT.

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
}

View File

@@ -0,0 +1,118 @@
<p align="center">
<img src="logo.svg" width="40%" border="0" alt="avo" />
<br />
<img src="https://img.shields.io/github/workflow/status/mmcloughlin/avo/ci/master.svg?style=flat-square" alt="Build Status" />
<a href="https://pkg.go.dev/github.com/mmcloughlin/avo"><img src="https://img.shields.io/badge/doc-reference-007d9b?logo=go&style=flat-square" alt="go.dev" /></a>
<a href="https://goreportcard.com/report/github.com/mmcloughlin/avo"><img src="https://goreportcard.com/badge/github.com/mmcloughlin/avo?style=flat-square" alt="Go Report Card" /></a>
</p>
<p align="center">Generate x86 Assembly with Go</p>
`avo` makes high-performance Go assembly easier to write, review and maintain. The `avo` package presents a familiar assembly-like interface that simplifies development without sacrificing performance:
* **Use Go control structures** for assembly generation; `avo` programs _are_ Go programs
* **Register allocation**: write functions with virtual registers and `avo` assigns physical registers for you
* **Automatically load arguments and store return values**: ensure memory offsets are correct for complex structures
* **Generation of stub files** to interface with your Go package
For more about `avo`:
* Introductory talk ["Better `x86` Assembly Generation with Go"](https://www.youtube.com/watch?v=6Y5CZ7_tyA4) at [dotGo 2019](https://2019.dotgo.eu/) ([slides](https://speakerdeck.com/mmcloughlin/better-x86-assembly-generation-with-go))
* [Longer tutorial at Gophercon 2019](https://www.youtube.com/watch?v=WaD8sNqroAw) showing a highly-optimized dot product ([slides](https://speakerdeck.com/mmcloughlin/better-x86-assembly-generation-with-go-gophercon-2019))
* Watch [Filippo Valsorda](https://filippo.io/) live code the [rewrite of `filippo.io/edwards25519` assembly with `avo`](https://vimeo.com/679848853)
* Discuss `avo` and general Go assembly topics in the [#assembly](https://gophers.slack.com/archives/C6WDZJ70S) channel of [Gophers Slack](https://invite.slack.golangbridge.org/)
_Note: APIs subject to change while `avo` is still in an experimental phase. You can use it to build [real things](examples) but we suggest you pin a version with your package manager of choice._
## Quick Start
Install `avo` with `go get`:
```
$ go get -u github.com/mmcloughlin/avo
```
`avo` assembly generators are pure Go programs. Here's a function that adds two `uint64` values:
```go
{{ include "examples/add/asm.go" -}}
```
`go run` this code to see the assembly output. To integrate this into the rest of your Go package we recommend a [`go:generate`](https://blog.golang.org/generate) line to produce the assembly and the corresponding Go stub file.
```go
{{ snippet "examples/add/add_test.go" "go:generate" "" -}}
```
After running `go generate` the [`add.s`](examples/add/add.s) file will contain the Go assembly.
```s
{{ include "examples/add/add.s" -}}
```
The same call will produce the stub file [`stub.go`](examples/add/stub.go) which will enable the function to be called from your Go code.
```go
{{ include "examples/add/stub.go" -}}
```
See the [`examples/add`](examples/add) directory for the complete working example.
## Examples
See [`examples`](examples) for the full suite of examples.
### Slice Sum
Sum a slice of `uint64`s:
```go
{{ snippet "examples/sum/asm.go" "func main" "^}" -}}
```
The result from this code generator is:
```s
{{ include "examples/sum/sum.s" -}}
```
Full example at [`examples/sum`](examples/sum).
### Features
For demonstrations of `avo` features:
* **[args](examples/args):** Loading function arguments.
* **[returns](examples/returns):** Building return values.
* **[complex](examples/complex):** Working with `complex{64,128}` types.
* **[data](examples/data):** Defining `DATA` sections.
* **[ext](examples/ext):** Interacting with types from external packages.
* **[pragma](examples/pragma):** Apply compiler directives to generated functions.
### Real Examples
Implementations of full algorithms:
* **[sha1](examples/sha1):** [SHA-1](https://en.wikipedia.org/wiki/SHA-1) cryptographic hash.
* **[fnv1a](examples/fnv1a):** [FNV-1a](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash) hash function.
* **[dot](examples/dot):** Vector dot product.
* **[md5x16](examples/md5x16):** AVX-512 accelerated [MD5](https://en.wikipedia.org/wiki/MD5).
* **[geohash](examples/geohash):** Integer [geohash](https://en.wikipedia.org/wiki/Geohash) encoding.
* **[stadtx](examples/stadtx):** [`StadtX` hash](https://github.com/demerphq/BeagleHash) port from [dgryski/go-stadtx](https://github.com/dgryski/go-stadtx).
## Contributing
Contributions to `avo` are welcome:
* Feedback from using `avo` in a real project is incredibly valuable. Consider [porting an existing project to `avo`](https://github.com/mmcloughlin/avo/issues/40).
* [Submit bug reports](https://github.com/mmcloughlin/avo/issues/new) to the issues page.
* Pull requests accepted. Take a look at outstanding [issues](https://github.com/mmcloughlin/avo/issues) for ideas (especially the ["good first issue"](https://github.com/mmcloughlin/avo/labels/good%20first%20issue) label).
* Join us in the [#assembly](https://gophers.slack.com/archives/C6WDZJ70S) channel of [Gophers Slack](https://invite.slack.golangbridge.org/).
## Credits
Inspired by the [PeachPy](https://github.com/Maratyszcza/PeachPy) and [asmjit](https://github.com/asmjit/asmjit) projects. Thanks to [Damian Gryski](https://github.com/dgryski) for advice, and his [extensive library of PeachPy Go projects](https://github.com/mmcloughlin/avo/issues/40).
## License
`avo` is available under the [BSD 3-Clause License](LICENSE).

View File

@@ -1,3 +1,9 @@
#!/bin/bash -ex #!/bin/bash -ex
# Generate some specific files with docgen.
go install ./internal/cmd/docgen
docgen -type readme -output README.md
# Process simple file embeddings with embedmd.
find . -name '*.md' | xargs embedmd -w find . -name '*.md' | xargs embedmd -w