mirror of
				https://github.com/lingble/talos.git
				synced 2025-11-04 06:28:09 +00:00 
			
		
		
		
	Using a well known comment (docgen: nodoc), we can now tell docgen to ignore certain structs. Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
		
			
				
	
	
		
			292 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			292 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
// License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
						|
 | 
						|
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"go/ast"
 | 
						|
	"go/parser"
 | 
						|
	"go/token"
 | 
						|
	"log"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"reflect"
 | 
						|
	"strings"
 | 
						|
	"text/template"
 | 
						|
 | 
						|
	"gopkg.in/yaml.v2"
 | 
						|
)
 | 
						|
 | 
						|
var tpl = `
 | 
						|
{{ $tick := "` + "`" + `" -}}
 | 
						|
### {{ .Name }}
 | 
						|
 | 
						|
{{ range $entry := .Entries -}}
 | 
						|
#### {{ $entry.Name }}
 | 
						|
 | 
						|
{{ $entry.Text.Description }}
 | 
						|
Type: {{ $tick }}{{ $entry.Type }}{{ $tick }}
 | 
						|
 | 
						|
{{ if $entry.Text.Values -}}
 | 
						|
Valid Values:
 | 
						|
 | 
						|
{{ range $value := $entry.Text.Values -}}
 | 
						|
- {{ $tick }}{{ $value }}{{ $tick }}
 | 
						|
{{ end }}
 | 
						|
{{ end -}}
 | 
						|
{{ if $entry.Text.Examples -}}
 | 
						|
Examples:
 | 
						|
{{ range $example := $entry.Text.Examples }}
 | 
						|
{{ $tick }}{{ $tick }}{{ $tick }}yaml
 | 
						|
{{ $example }}
 | 
						|
{{ $tick }}{{ $tick }}{{ $tick }}
 | 
						|
{{ end }}
 | 
						|
{{ end }}
 | 
						|
{{- if $entry.Note -}}
 | 
						|
> {{ $entry.Note }}
 | 
						|
{{ end }}
 | 
						|
{{- end -}}
 | 
						|
---
 | 
						|
`
 | 
						|
 | 
						|
type Doc struct {
 | 
						|
	Title    string
 | 
						|
	Sections []*Section
 | 
						|
}
 | 
						|
 | 
						|
type Section struct {
 | 
						|
	Name    string
 | 
						|
	Entries []*Entry
 | 
						|
}
 | 
						|
 | 
						|
type Entry struct {
 | 
						|
	Name string
 | 
						|
	Type string
 | 
						|
	Text *Text
 | 
						|
	Note string
 | 
						|
}
 | 
						|
 | 
						|
type Text struct {
 | 
						|
	Description string   `json:"description"`
 | 
						|
	Values      []string `json:"values"`
 | 
						|
	Examples    []string `json:"examples"`
 | 
						|
}
 | 
						|
 | 
						|
func in(p string) (string, error) {
 | 
						|
	return filepath.Abs(p)
 | 
						|
}
 | 
						|
 | 
						|
func out(p string) (*os.File, error) {
 | 
						|
	abs, err := filepath.Abs(p)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return os.Create(abs)
 | 
						|
}
 | 
						|
 | 
						|
type structType struct {
 | 
						|
	name string
 | 
						|
	pos  token.Pos
 | 
						|
	node *ast.StructType
 | 
						|
}
 | 
						|
 | 
						|
func collectStructs(node ast.Node) []*structType {
 | 
						|
	structs := []*structType{}
 | 
						|
 | 
						|
	collectStructs := func(n ast.Node) bool {
 | 
						|
		g, ok := n.(*ast.GenDecl)
 | 
						|
		if !ok {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
 | 
						|
		if g.Doc != nil {
 | 
						|
			for _, comment := range g.Doc.List {
 | 
						|
				if strings.Contains(comment.Text, "docgen: nodoc") {
 | 
						|
					return true
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		for _, spec := range g.Specs {
 | 
						|
			t, ok := spec.(*ast.TypeSpec)
 | 
						|
			if !ok {
 | 
						|
				return true
 | 
						|
			}
 | 
						|
 | 
						|
			if t.Type == nil {
 | 
						|
				return true
 | 
						|
			}
 | 
						|
 | 
						|
			x, ok := t.Type.(*ast.StructType)
 | 
						|
			if !ok {
 | 
						|
				return true
 | 
						|
			}
 | 
						|
 | 
						|
			structName := t.Name.Name
 | 
						|
 | 
						|
			s := &structType{
 | 
						|
				name: structName,
 | 
						|
				node: x,
 | 
						|
				pos:  x.Pos(),
 | 
						|
			}
 | 
						|
 | 
						|
			structs = append(structs, s)
 | 
						|
		}
 | 
						|
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	ast.Inspect(node, collectStructs)
 | 
						|
 | 
						|
	return structs
 | 
						|
}
 | 
						|
 | 
						|
type field struct {
 | 
						|
}
 | 
						|
 | 
						|
func parseFieldType(p interface{}) string {
 | 
						|
	switch t := p.(type) {
 | 
						|
	case *ast.Ident:
 | 
						|
		return t.Name
 | 
						|
	case *ast.ArrayType:
 | 
						|
		return "array"
 | 
						|
	case *ast.MapType:
 | 
						|
		return "map"
 | 
						|
	case *ast.StructType:
 | 
						|
		return "struct"
 | 
						|
	case *ast.StarExpr:
 | 
						|
		return parseFieldType(t.X)
 | 
						|
	case *ast.SelectorExpr:
 | 
						|
		return parseFieldType(t.Sel)
 | 
						|
	default:
 | 
						|
		log.Printf("unknown: %#v", t)
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func collectFields(s *structType) (entries []*Entry) {
 | 
						|
	entries = []*Entry{}
 | 
						|
 | 
						|
	for _, field := range s.node.Fields.List {
 | 
						|
		if field.Tag == nil {
 | 
						|
			if field.Names == nil {
 | 
						|
				// This is an embedded struct.
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			log.Fatalf("field %q is missing a yaml tag", field.Names[0].Name)
 | 
						|
		}
 | 
						|
 | 
						|
		if field.Doc == nil {
 | 
						|
			log.Fatalf("field %q is missing a documentation", field.Names[0].Name)
 | 
						|
		}
 | 
						|
 | 
						|
		tag := reflect.StructTag(strings.Trim(field.Tag.Value, "`"))
 | 
						|
		name := tag.Get("yaml")
 | 
						|
		name = strings.Split(name, ",")[0]
 | 
						|
 | 
						|
		fieldType := parseFieldType(field.Type)
 | 
						|
 | 
						|
		text := &Text{}
 | 
						|
		if err := yaml.Unmarshal([]byte(field.Doc.Text()), text); err != nil {
 | 
						|
			log.Fatal(err)
 | 
						|
		}
 | 
						|
 | 
						|
		entry := &Entry{
 | 
						|
			Name: name,
 | 
						|
			Type: fieldType,
 | 
						|
			Text: text,
 | 
						|
		}
 | 
						|
 | 
						|
		if field.Comment != nil {
 | 
						|
			entry.Note = field.Comment.Text()
 | 
						|
		}
 | 
						|
 | 
						|
		entries = append(entries, entry)
 | 
						|
	}
 | 
						|
 | 
						|
	return entries
 | 
						|
}
 | 
						|
 | 
						|
func render(section *Section, f *os.File) {
 | 
						|
	t := template.Must(template.New("section.tpl").Parse(tpl))
 | 
						|
	err := t.Execute(f, section)
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func main() {
 | 
						|
	abs, err := in(os.Args[1])
 | 
						|
	if err != nil {
 | 
						|
		log.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	fmt.Printf("creating package file set: %q\n", abs)
 | 
						|
 | 
						|
	fset := token.NewFileSet()
 | 
						|
 | 
						|
	pkgs, err := parser.ParseDir(fset, abs, nil, parser.ParseComments)
 | 
						|
	if err != nil {
 | 
						|
		log.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	var structs []*structType
 | 
						|
 | 
						|
	for _, pkg := range pkgs {
 | 
						|
		for _, astFile := range pkg.Files {
 | 
						|
			tokenFile := fset.File(astFile.Pos())
 | 
						|
			if tokenFile == nil {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			fmt.Printf("parsing file in package %q: %s\n", pkg.Name, tokenFile.Name())
 | 
						|
			structs = append(structs, collectStructs(astFile)...)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if len(structs) == 0 {
 | 
						|
		log.Fatalf("failed to find types that could be documented in %s", abs)
 | 
						|
	}
 | 
						|
 | 
						|
	doc := &Doc{
 | 
						|
		Sections: []*Section{},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, s := range structs {
 | 
						|
		fmt.Printf("generating docs for type: %q\n", s.name)
 | 
						|
 | 
						|
		entries := collectFields(s)
 | 
						|
 | 
						|
		section := &Section{
 | 
						|
			Name:    s.name,
 | 
						|
			Entries: entries,
 | 
						|
		}
 | 
						|
 | 
						|
		doc.Sections = append(doc.Sections, section)
 | 
						|
	}
 | 
						|
 | 
						|
	node, err := parser.ParseFile(fset, filepath.Join(abs, "doc.go"), nil, parser.ParseComments)
 | 
						|
	if err != nil {
 | 
						|
		log.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	out, err := out(os.Args[2])
 | 
						|
	defer out.Close()
 | 
						|
 | 
						|
	out.WriteString("---\n")
 | 
						|
	out.WriteString("title: " + node.Name.Name + "\n")
 | 
						|
	out.WriteString("---\n")
 | 
						|
	out.WriteString("\n")
 | 
						|
	out.WriteString("<!-- markdownlint-disable MD024 -->")
 | 
						|
	out.WriteString("\n")
 | 
						|
	out.WriteString("\n")
 | 
						|
	out.WriteString(node.Doc.Text())
 | 
						|
 | 
						|
	for _, section := range doc.Sections {
 | 
						|
		render(section, out)
 | 
						|
	}
 | 
						|
}
 |