tpl: Add some GoDoc info to template func docs

Closes #3418
This commit is contained in:
Bjørn Erik Pedersen 2017-05-02 11:03:08 +02:00
parent f9e41f6497
commit cff2f31334
4 changed files with 1448 additions and 1122 deletions

File diff suppressed because it is too large Load diff

View file

@ -26,10 +26,12 @@ func New() *Namespace {
type Namespace struct { type Namespace struct {
} }
// ToInt converts the given value to an int.
func (ns *Namespace) ToInt(v interface{}) (int, error) { func (ns *Namespace) ToInt(v interface{}) (int, error) {
return _cast.ToIntE(v) return _cast.ToIntE(v)
} }
// ToString converts the given value to a string.
func (ns *Namespace) ToString(v interface{}) (string, error) { func (ns *Namespace) ToString(v interface{}) (string, error) {
return _cast.ToStringE(v) return _cast.ToStringE(v)
} }

View file

@ -25,7 +25,7 @@ func init() {
docs := make(map[string]interface{}) docs := make(map[string]interface{})
d := &deps.Deps{} d := &deps.Deps{}
var namespaces []*internal.TemplateFuncsNamespace var namespaces internal.TemplateFuncsNamespaces
for _, nsf := range internal.TemplateFuncsNamespaceRegistry { for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
nf := nsf(d) nf := nsf(d)

View file

@ -16,11 +16,20 @@
package internal package internal
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt"
"go/doc"
"go/parser"
"go/token"
"io/ioutil"
"log"
"os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime" "runtime"
"strings" "strings"
"sync"
"github.com/spf13/hugo/deps" "github.com/spf13/hugo/deps"
) )
@ -42,6 +51,8 @@ type TemplateFuncsNamespace struct {
MethodMappings map[string]TemplateFuncMethodMapping MethodMappings map[string]TemplateFuncMethodMapping
} }
type TemplateFuncsNamespaces []*TemplateFuncsNamespace
func (t *TemplateFuncsNamespace) AddMethodMapping(m interface{}, aliases []string, examples [][2]string) { func (t *TemplateFuncsNamespace) AddMethodMapping(m interface{}, aliases []string, examples [][2]string) {
if t.MethodMappings == nil { if t.MethodMappings == nil {
t.MethodMappings = make(map[string]TemplateFuncMethodMapping) t.MethodMappings = make(map[string]TemplateFuncMethodMapping)
@ -94,35 +105,172 @@ func methodToName(m interface{}) string {
return name return name
} }
func (t *TemplateFuncsNamespace) MarshalJSON() ([]byte, error) { type goDocFunc struct {
type Func struct { Name string
Name string Description string
Description string // TODO(bep) Args []string
Aliases []string Aliases []string
Examples [][2]string Examples [][2]string
}
func (t goDocFunc) toJSON() ([]byte, error) {
args, err := json.Marshal(t.Args)
if err != nil {
return nil, err
} }
// TODO(bep) map/lookup from docs template Namespace + Func name. aliases, err := json.Marshal(t.Aliases)
var funcs []Func if err != nil {
return nil, err
}
examples, err := json.Marshal(t.Examples)
if err != nil {
return nil, err
}
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf(`%q:
{ "Description": %q, "Args": %s, "Aliases": %s, "Examples": %s }
`, t.Name, t.Description, args, aliases, examples))
return buf.Bytes(), nil
}
func (namespaces TemplateFuncsNamespaces) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
buf.WriteString("{")
for i, ns := range namespaces {
if i != 0 {
buf.WriteString(",")
}
b, err := ns.toJSON()
if err != nil {
return nil, err
}
buf.Write(b)
}
buf.WriteString("}")
return buf.Bytes(), nil
}
func (t *TemplateFuncsNamespace) toJSON() ([]byte, error) {
var buf bytes.Buffer
godoc := getGetTplPackagesGoDoc()[t.Name]
var funcs []goDocFunc
buf.WriteString(fmt.Sprintf(`%q: {`, t.Name))
ctx := t.Context.(func() interface{})() ctx := t.Context.(func() interface{})()
ctxType := reflect.TypeOf(ctx) ctxType := reflect.TypeOf(ctx)
for i := 0; i < ctxType.NumMethod(); i++ { for i := 0; i < ctxType.NumMethod(); i++ {
method := ctxType.Method(i) method := ctxType.Method(i)
f := Func{ f := goDocFunc{
Name: method.Name, Name: method.Name,
} }
methodGoDoc := godoc[method.Name]
if mapping, ok := t.MethodMappings[method.Name]; ok { if mapping, ok := t.MethodMappings[method.Name]; ok {
f.Aliases = mapping.Aliases f.Aliases = mapping.Aliases
f.Examples = mapping.Examples f.Examples = mapping.Examples
f.Description = methodGoDoc.Description
f.Args = methodGoDoc.Args
} }
funcs = append(funcs, f) funcs = append(funcs, f)
} }
return json.Marshal(&struct { for i, f := range funcs {
Name string if i != 0 {
Funcs []Func buf.WriteString(",")
}{ }
Name: t.Name, funcStr, err := f.toJSON()
Funcs: funcs, if err != nil {
}) return nil, err
}
buf.Write(funcStr)
}
buf.WriteString("}")
return buf.Bytes(), nil
}
type methodGoDocInfo struct {
Description string
Args []string
}
var (
tplPackagesGoDoc map[string]map[string]methodGoDocInfo
tplPackagesGoDocInit sync.Once
)
func getGetTplPackagesGoDoc() map[string]map[string]methodGoDocInfo {
tplPackagesGoDocInit.Do(func() {
tplPackagesGoDoc = make(map[string]map[string]methodGoDocInfo)
pwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
fset := token.NewFileSet()
// pwd will be inside one of the namespace packages during tests
var basePath string
if strings.Contains(pwd, "tpl") {
basePath = filepath.Join(pwd, "..")
} else {
basePath = filepath.Join(pwd, "tpl")
}
files, err := ioutil.ReadDir(basePath)
if err != nil {
log.Fatal(err)
}
for _, fi := range files {
if !fi.IsDir() {
continue
}
namespaceDoc := make(map[string]methodGoDocInfo)
packagePath := filepath.Join(basePath, fi.Name())
d, err := parser.ParseDir(fset, packagePath, nil, parser.ParseComments)
if err != nil {
log.Fatal(err)
}
for _, f := range d {
p := doc.New(f, "./", 0)
for _, t := range p.Types {
if t.Name == "Namespace" {
for _, tt := range t.Methods {
var args []string
for _, p := range tt.Decl.Type.Params.List {
for _, pp := range p.Names {
args = append(args, pp.Name)
}
}
description := strings.TrimSpace(tt.Doc)
di := methodGoDocInfo{Description: description, Args: args}
namespaceDoc[tt.Name] = di
}
}
}
}
tplPackagesGoDoc[fi.Name()] = namespaceDoc
}
})
return tplPackagesGoDoc
} }