Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -1309,7 +1309,7 @@ the Development section below, and who ships a register the four above then draw
|
|||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
doc.go the package doc, and the vocabulary the rest of it uses for a render and its fences
|
doc.go the package doc, and the vocabulary the package is written in
|
||||||
fejkdata.go Generator, New, options, the embedded data set, List
|
fejkdata.go Generator, New, options, the embedded data set, List
|
||||||
node.go the node model and JSON -> node compilation
|
node.go the node model and JSON -> node compilation
|
||||||
table.go tables: the rows TSV, its options and links, row selection and draws
|
table.go tables: the rows TSV, its options and links, row selection and draws
|
||||||
|
|||||||
@@ -19,9 +19,11 @@
|
|||||||
// the pick produced: `pick` and `drawn` settle a choice, `table.draw` a row, and
|
// the pick produced: `pick` and `drawn` settle a choice, `table.draw` a row, and
|
||||||
// `draw` carries what a read produced.
|
// `draw` carries what a read produced.
|
||||||
// - expansion — one render of one format, by `expand`. A nested template is an
|
// - expansion — one render of one format, by `expand`. A nested template is an
|
||||||
// expansion of its own, and so is each iteration of a `template.repeat`.
|
// expansion of its own.
|
||||||
// - render — one `Fake` call or one record, started by `renderOnce` where nothing
|
// - render — what owns one `drawSet`, and so what one reference draw spans:
|
||||||
// outer started it. It owns one `drawSet`, so it is what a reference draw spans.
|
// `Generator.Fake`, `FakeRecord`, `FakeStruct`, `Template.Fake` and
|
||||||
|
// `RecordTemplate.Fake` each start one, as does `expandAnew` per iteration of a
|
||||||
|
// `template.repeat` — an iteration is an expansion and a render of its own.
|
||||||
// - hold — keeping one draw of a name, so every route to it reads that value.
|
// - hold — keeping one draw of a name, so every route to it reads that value.
|
||||||
// `template.held` lists what an expansion holds, `draws` keeps them and
|
// `template.held` lists what an expansion holds, `draws` keeps them and
|
||||||
// `readField` reads through it; a reference path is held for the whole render.
|
// `readField` reads through it; a reference path is held for the whole render.
|
||||||
@@ -32,10 +34,11 @@
|
|||||||
// rows of its ancestors, and `draws.rowOf` draws one where none is pinned.
|
// rows of its ancestors, and `draws.rowOf` draws one where none is pinned.
|
||||||
// - family — a table and every table reaching it through a chain of
|
// - family — a table and every table reaching it through a chain of
|
||||||
// `table.parentT`, named by the root that chain ends at: `table.family`.
|
// `table.parentT`, named by the root that chain ends at: `table.family`.
|
||||||
// - whole — a read that lands on a `row`, so the table's format renders every
|
// - whole — a read that lands on a `row`, so the row renders through its table's
|
||||||
// column of it: `tableRead.whole`.
|
// format: `tableRead.whole` says a read did, `table.whole` is the node.
|
||||||
// - fence — a load-time check refusing data that two routes to one draw would
|
// - fence — a load-time check refusing data that two routes to one draw would
|
||||||
// disagree in. Every one runs in `New`, which is why `Fake` cannot fail on
|
// disagree in, which is why a render cannot fail: `heldCheck` over an
|
||||||
// loaded data: `heldCheck` over an expansion's held names, `drawCheck` over a
|
// expansion's held names, `drawCheck` over a render's reference paths, and
|
||||||
// render's reference paths, and `checkFamilies` over the rows they pin.
|
// `checkFamilies` over the rows they pin. `New` runs them over the data set,
|
||||||
|
// `NewTemplate` and `FakeStruct` over what those compile.
|
||||||
package fejkdata
|
package fejkdata
|
||||||
|
|||||||
+39
-32
@@ -2,18 +2,20 @@ package fejkdata
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
"go/build"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
"os"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var backticked = regexp.MustCompile("`([A-Za-z][A-Za-z0-9]*(?:\\.[A-Za-z][A-Za-z0-9]*)?)`")
|
// Every backtick in the Vocabulary is a symbol, so an unrecognised spelling fails
|
||||||
|
// rather than going unchecked.
|
||||||
|
var backticked = regexp.MustCompile("`([^`]+)`")
|
||||||
|
|
||||||
// TestVocabularyNamesDeclaredSymbols proves every symbol the package doc's Vocabulary
|
// TestVocabularyNamesDeclaredSymbols proves the package doc's Vocabulary names
|
||||||
// names is declared here, so a renamed unit renames its entry with it.
|
// symbols this package declares, so a renamed unit renames its entry with it.
|
||||||
func TestVocabularyNamesDeclaredSymbols(t *testing.T) {
|
func TestVocabularyNamesDeclaredSymbols(t *testing.T) {
|
||||||
files := sourceFiles(t)
|
files := sourceFiles(t)
|
||||||
declared := declaredSymbols(files)
|
declared := declaredSymbols(files)
|
||||||
@@ -24,21 +26,15 @@ func TestVocabularyNamesDeclaredSymbols(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sourceFiles parses the package's own files, the tests left out: the Vocabulary
|
|
||||||
// indexes what ships, not what proves it.
|
|
||||||
func sourceFiles(t *testing.T) []*ast.File {
|
func sourceFiles(t *testing.T) []*ast.File {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
entries, err := os.ReadDir(".")
|
pkg, err := build.ImportDir(".", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read the package directory: %v", err)
|
t.Fatalf("read the package directory: %v", err)
|
||||||
}
|
}
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
var files []*ast.File
|
var files []*ast.File
|
||||||
for _, e := range entries {
|
for _, name := range pkg.GoFiles {
|
||||||
name := e.Name()
|
|
||||||
if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
|
f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("parse %s: %v", name, err)
|
t.Fatalf("parse %s: %v", name, err)
|
||||||
@@ -48,7 +44,6 @@ func sourceFiles(t *testing.T) []*ast.File {
|
|||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
// vocabulary is the package doc from its Vocabulary heading on.
|
|
||||||
func vocabulary(t *testing.T, files []*ast.File) string {
|
func vocabulary(t *testing.T, files []*ast.File) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
@@ -63,45 +58,57 @@ func vocabulary(t *testing.T, files []*ast.File) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// declaredSymbols is every name the package declares, a method and a struct field
|
// declaredSymbols is every name the package declares at the top level, a method and
|
||||||
// under its type as well: fence, draws.pin, tableRead.whole.
|
// a struct field keyed under its type: draws.pin, tableRead.whole. A name declared
|
||||||
|
// inside a function body is not one a Vocabulary entry may name.
|
||||||
func declaredSymbols(files []*ast.File) map[string]bool {
|
func declaredSymbols(files []*ast.File) map[string]bool {
|
||||||
names := map[string]bool{}
|
names := map[string]bool{}
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
ast.Inspect(f, func(n ast.Node) bool {
|
for _, decl := range f.Decls {
|
||||||
switch d := n.(type) {
|
switch d := decl.(type) {
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
names[d.Name.Name] = true
|
names[d.Name.Name] = true
|
||||||
if d.Recv != nil {
|
if recv := receiverType(d.Recv); recv != "" {
|
||||||
names[receiverType(d.Recv)+"."+d.Name.Name] = true
|
names[recv+"."+d.Name.Name] = true
|
||||||
}
|
|
||||||
case *ast.TypeSpec:
|
|
||||||
names[d.Name.Name] = true
|
|
||||||
addFields(names, d)
|
|
||||||
case *ast.ValueSpec:
|
|
||||||
for _, id := range d.Names {
|
|
||||||
names[id.Name] = true
|
|
||||||
}
|
}
|
||||||
|
case *ast.GenDecl:
|
||||||
|
addSpecs(names, d)
|
||||||
}
|
}
|
||||||
return true
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
func addFields(names map[string]bool, d *ast.TypeSpec) {
|
func addSpecs(names map[string]bool, d *ast.GenDecl) {
|
||||||
s, isStruct := d.Type.(*ast.StructType)
|
for _, spec := range d.Specs {
|
||||||
|
switch s := spec.(type) {
|
||||||
|
case *ast.TypeSpec:
|
||||||
|
names[s.Name.Name] = true
|
||||||
|
addFields(names, s)
|
||||||
|
case *ast.ValueSpec:
|
||||||
|
for _, id := range s.Names {
|
||||||
|
names[id.Name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFields(names map[string]bool, s *ast.TypeSpec) {
|
||||||
|
st, isStruct := s.Type.(*ast.StructType)
|
||||||
if !isStruct {
|
if !isStruct {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, field := range s.Fields.List {
|
for _, field := range st.Fields.List {
|
||||||
for _, id := range field.Names {
|
for _, id := range field.Names {
|
||||||
names[d.Name.Name+"."+id.Name] = true
|
names[s.Name.Name+"."+id.Name] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func receiverType(recv *ast.FieldList) string {
|
func receiverType(recv *ast.FieldList) string {
|
||||||
|
if recv == nil || len(recv.List) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
e := recv.List[0].Type
|
e := recv.List[0].Type
|
||||||
if star, isPointer := e.(*ast.StarExpr); isPointer {
|
if star, isPointer := e.(*ast.StarExpr); isPointer {
|
||||||
e = star.X
|
e = star.X
|
||||||
|
|||||||
Reference in New Issue
Block a user