mirror of
https://github.com/mjl-/mox.git
synced 2025-07-12 16:24:37 +03:00
mox!
This commit is contained in:
270
vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/main.go
generated
vendored
Normal file
270
vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/main.go
generated
vendored
Normal file
@ -0,0 +1,270 @@
|
||||
/*
|
||||
Sherpadoc parses Go code and outputs sherpa documentation in JSON.
|
||||
|
||||
This documentation is provided to the sherpa HTTP handler to serve
|
||||
as documentation through the _docs function.
|
||||
|
||||
Example:
|
||||
|
||||
sherpadoc Awesome >awesome.json
|
||||
|
||||
Sherpadoc parses Go code, finds a struct named "Awesome", and gathers
|
||||
documentation:
|
||||
|
||||
Comments above the struct are used as section documentation. Fields
|
||||
in section structs must are treated as subsections, and can in turn
|
||||
contain subsections. These subsections and their methods are also
|
||||
exported and documented in the sherpa API. Add a struct tag "sherpa"
|
||||
to override the name of the subsection, for example `sherpa:"Another
|
||||
Awesome API"`.
|
||||
|
||||
Comments above method names are function documentation. A synopsis
|
||||
is automatically generated.
|
||||
|
||||
Types used as parameters or return values are added to the section
|
||||
documentation where they are used. The comments above the type are
|
||||
used, as well as the comments for each field in a struct. The
|
||||
documented field names know about the "json" struct field tags.
|
||||
|
||||
More eloborate example:
|
||||
|
||||
sherpadoc
|
||||
-title 'Awesome API by mjl' \
|
||||
-replace 'pkg.Type string,example.com/some/pkg.SomeType [] string' \
|
||||
path/to/awesome/code Awesome \
|
||||
>awesome.json
|
||||
|
||||
Most common Go code patterns for API functions have been implemented
|
||||
in sherpadoc, but you may run into missing support.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mjl-/sherpadoc"
|
||||
|
||||
"golang.org/x/mod/modfile"
|
||||
)
|
||||
|
||||
var (
|
||||
packagePath = flag.String("package-path", ".", "of source code to parse")
|
||||
replace = flag.String("replace", "", "comma-separated list of type replacements, e.g. \"somepkg.SomeType string\"")
|
||||
title = flag.String("title", "", "title of the API, default is the name of the type of the main API")
|
||||
adjustFunctionNames = flag.String("adjust-function-names", "", `by default, the first character of function names is turned into lower case; with "lowerWord" the first string of upper case characters is lower cased, with "none" the name is left as is`)
|
||||
)
|
||||
|
||||
// If there is a "vendor" directory, we'll load packages from there (instead of
|
||||
// through (slower) packages.Load), and we need to know the module name to resolve
|
||||
// imports to paths in vendor.
|
||||
var (
|
||||
gomodFile *modfile.File
|
||||
gomodDir string
|
||||
)
|
||||
|
||||
type field struct {
|
||||
Name string
|
||||
Typewords []string
|
||||
Doc string
|
||||
Fields []*field
|
||||
}
|
||||
|
||||
func (f field) TypeString() string {
|
||||
t := []string{}
|
||||
for _, e := range f.Typewords {
|
||||
if e == "nullable" {
|
||||
e = "*"
|
||||
}
|
||||
t = append(t, e)
|
||||
}
|
||||
return strings.Join(t, "")
|
||||
}
|
||||
|
||||
type typeKind int
|
||||
|
||||
const (
|
||||
typeStruct typeKind = iota
|
||||
typeInts
|
||||
typeStrings
|
||||
typeBytes
|
||||
)
|
||||
|
||||
// NamedType represents the type of a parameter or return value.
|
||||
type namedType struct {
|
||||
Name string
|
||||
Text string
|
||||
Kind typeKind
|
||||
Fields []*field // For kind is typeStruct.
|
||||
// For kind is typeInts
|
||||
IntValues []struct {
|
||||
Name string
|
||||
Value int
|
||||
Docs string
|
||||
}
|
||||
// For kind is typeStrings
|
||||
StringValues []struct {
|
||||
Name string
|
||||
Value string
|
||||
Docs string
|
||||
}
|
||||
}
|
||||
|
||||
type function struct {
|
||||
Name string
|
||||
Text string
|
||||
Params []sherpadoc.Arg
|
||||
Returns []sherpadoc.Arg
|
||||
}
|
||||
|
||||
// Section is an API section with docs, functions and subsections.
|
||||
// Types are gathered per section, and moved up the section tree to the first common ancestor, so types are only documented once.
|
||||
type section struct {
|
||||
TypeName string // Name of the type for this section.
|
||||
Name string // Name of the section. Either same as TypeName, or overridden with a "sherpa" struct tag.
|
||||
Text string
|
||||
Types []*namedType
|
||||
Typeset map[string]struct{}
|
||||
Functions []*function
|
||||
Sections []*section
|
||||
}
|
||||
|
||||
func check(err error, action string) {
|
||||
if err != nil {
|
||||
log.Fatalf("%s: %s", action, err)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
log.Println("usage: sherpadoc [flags] section")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
if len(args) != 1 {
|
||||
usage()
|
||||
}
|
||||
|
||||
// If vendor exists, we load packages from it.
|
||||
for dir, _ := os.Getwd(); dir != "" && dir != "/"; dir = filepath.Dir(dir) {
|
||||
p := filepath.Join(dir, "go.mod")
|
||||
if _, err := os.Stat(p); err != nil && os.IsNotExist(err) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
log.Printf("searching for go.mod: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(dir, "vendor")); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if gomod, err := os.ReadFile(p); err != nil {
|
||||
log.Fatalf("reading go.mod: %s", err)
|
||||
} else if mf, err := modfile.ParseLax("go.mod", gomod, nil); err != nil {
|
||||
log.Fatalf("parsing go.mod: %s", err)
|
||||
} else {
|
||||
gomodFile = mf
|
||||
gomodDir = dir
|
||||
}
|
||||
}
|
||||
|
||||
section := parseDoc(args[0], *packagePath)
|
||||
if *title != "" {
|
||||
section.Name = *title
|
||||
}
|
||||
|
||||
moveTypesUp(section)
|
||||
|
||||
doc := sherpaSection(section)
|
||||
doc.SherpaVersion = 0
|
||||
doc.SherpadocVersion = sherpadoc.SherpadocVersion
|
||||
|
||||
err := sherpadoc.Check(doc)
|
||||
check(err, "checking sherpadoc output before writing")
|
||||
|
||||
writeJSON(doc)
|
||||
}
|
||||
|
||||
func writeJSON(v interface{}) {
|
||||
buf, err := json.MarshalIndent(v, "", "\t")
|
||||
check(err, "marshal to json")
|
||||
_, err = os.Stdout.Write(buf)
|
||||
check(err, "writing json to stdout")
|
||||
_, err = fmt.Println()
|
||||
check(err, "write to stdout")
|
||||
}
|
||||
|
||||
type typeCount struct {
|
||||
t *namedType
|
||||
count int
|
||||
}
|
||||
|
||||
// Move types used in multiple sections up to their common ancestor.
|
||||
func moveTypesUp(sec *section) {
|
||||
// First, the process for each child.
|
||||
for _, s := range sec.Sections {
|
||||
moveTypesUp(s)
|
||||
}
|
||||
|
||||
// Count how often a type is used from here downwards.
|
||||
// If more than once, move the type up to here.
|
||||
counts := map[string]*typeCount{}
|
||||
countTypes(counts, sec)
|
||||
for _, tc := range counts {
|
||||
if tc.count <= 1 {
|
||||
continue
|
||||
}
|
||||
for _, sub := range sec.Sections {
|
||||
removeType(sub, tc.t)
|
||||
}
|
||||
if !hasType(sec, tc.t) {
|
||||
sec.Types = append(sec.Types, tc.t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func countTypes(counts map[string]*typeCount, sec *section) {
|
||||
for _, t := range sec.Types {
|
||||
_, ok := counts[t.Name]
|
||||
if !ok {
|
||||
counts[t.Name] = &typeCount{t, 0}
|
||||
}
|
||||
counts[t.Name].count++
|
||||
}
|
||||
for _, subsec := range sec.Sections {
|
||||
countTypes(counts, subsec)
|
||||
}
|
||||
}
|
||||
|
||||
func removeType(sec *section, t *namedType) {
|
||||
types := make([]*namedType, 0, len(sec.Types))
|
||||
for _, tt := range sec.Types {
|
||||
if tt.Name != t.Name {
|
||||
types = append(types, tt)
|
||||
}
|
||||
}
|
||||
sec.Types = types
|
||||
for _, sub := range sec.Sections {
|
||||
removeType(sub, t)
|
||||
}
|
||||
}
|
||||
|
||||
func hasType(sec *section, t *namedType) bool {
|
||||
for _, tt := range sec.Types {
|
||||
if tt.Name == t.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
857
vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/parse.go
generated
vendored
Normal file
857
vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/parse.go
generated
vendored
Normal file
@ -0,0 +1,857 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/doc"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
||||
"github.com/mjl-/sherpadoc"
|
||||
)
|
||||
|
||||
// ParsedPackage possibly includes some of its imports because the package that contains the section references it.
|
||||
type parsedPackage struct {
|
||||
Fset *token.FileSet // Used with a token.Pos to get offending locations.
|
||||
Path string // Of import, used for keeping duplicate type names from different packages unique.
|
||||
Pkg *ast.Package // Needed for its files: we need a file to find the package path and identifier used to reference other types.
|
||||
Docpkg *doc.Package
|
||||
Imports map[string]*parsedPackage // Package/import path to parsed packages.
|
||||
}
|
||||
|
||||
type typewords []string
|
||||
|
||||
func (pp *parsedPackage) lookupType(name string) *doc.Type {
|
||||
for _, t := range pp.Docpkg.Types {
|
||||
if t.Name == name {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Like log.Fatalf, but prefixes error message with offending file position (if known).
|
||||
// pp is the package where the position tok belongs to.
|
||||
func logFatalLinef(pp *parsedPackage, tok token.Pos, format string, args ...interface{}) {
|
||||
if !tok.IsValid() {
|
||||
log.Fatalf(format, args...)
|
||||
}
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
log.Fatalf("%s: %s", pp.Fset.Position(tok).String(), msg)
|
||||
}
|
||||
|
||||
// Documentation for a single field, with text above the field, and
|
||||
// on the right of the field combined.
|
||||
func fieldDoc(f *ast.Field) string {
|
||||
s := ""
|
||||
if f.Doc != nil {
|
||||
s += strings.Replace(strings.TrimSpace(f.Doc.Text()), "\n", " ", -1)
|
||||
}
|
||||
if f.Comment != nil {
|
||||
if s != "" {
|
||||
s += "; "
|
||||
}
|
||||
s += strings.TrimSpace(f.Comment.Text())
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Parse string literal. Errors are fatal.
|
||||
func parseStringLiteral(s string) string {
|
||||
r, err := strconv.Unquote(s)
|
||||
check(err, "parsing string literal")
|
||||
return r
|
||||
}
|
||||
|
||||
func jsonName(tag string, name string) string {
|
||||
s := reflect.StructTag(tag).Get("json")
|
||||
if s == "" || strings.HasPrefix(s, ",") {
|
||||
return name
|
||||
} else if s == "-" {
|
||||
return ""
|
||||
} else {
|
||||
return strings.Split(s, ",")[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Return the names (can be none) for a field. Takes exportedness
|
||||
// and JSON tag annotation into account.
|
||||
func nameList(names []*ast.Ident, tag *ast.BasicLit) []string {
|
||||
if names == nil {
|
||||
return nil
|
||||
}
|
||||
l := []string{}
|
||||
for _, name := range names {
|
||||
if ast.IsExported(name.Name) {
|
||||
l = append(l, name.Name)
|
||||
}
|
||||
}
|
||||
if len(l) == 1 && tag != nil {
|
||||
name := jsonName(parseStringLiteral(tag.Value), l[0])
|
||||
if name != "" {
|
||||
return []string{name}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Parses a top-level sherpadoc section.
|
||||
func parseDoc(apiName, packagePath string) *section {
|
||||
fset := token.NewFileSet()
|
||||
pkgs, firstErr := parser.ParseDir(fset, packagePath, nil, parser.ParseComments)
|
||||
check(firstErr, "parsing code")
|
||||
for _, pkg := range pkgs {
|
||||
docpkg := doc.New(pkg, "", doc.AllDecls)
|
||||
|
||||
for _, t := range docpkg.Types {
|
||||
if t.Name == apiName {
|
||||
par := &parsedPackage{
|
||||
Fset: fset,
|
||||
Path: packagePath,
|
||||
Pkg: pkg,
|
||||
Docpkg: docpkg,
|
||||
Imports: make(map[string]*parsedPackage),
|
||||
}
|
||||
return parseSection(t, par)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Fatalf("type %q not found", apiName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse a section and its optional subsections, recursively.
|
||||
// t is the type of the struct with the sherpa methods to be parsed.
|
||||
func parseSection(t *doc.Type, pp *parsedPackage) *section {
|
||||
sec := §ion{
|
||||
t.Name,
|
||||
t.Name,
|
||||
strings.TrimSpace(t.Doc),
|
||||
nil,
|
||||
map[string]struct{}{},
|
||||
nil,
|
||||
nil,
|
||||
}
|
||||
|
||||
// make list of methods to parse, sorted by position in file name.
|
||||
methods := make([]*doc.Func, len(t.Methods))
|
||||
copy(methods, t.Methods)
|
||||
sort.Slice(methods, func(i, j int) bool {
|
||||
return methods[i].Decl.Name.NamePos < methods[j].Decl.Name.NamePos
|
||||
})
|
||||
|
||||
for _, fn := range methods {
|
||||
parseMethod(sec, fn, pp)
|
||||
}
|
||||
|
||||
// parse subsections
|
||||
ts := t.Decl.Specs[0].(*ast.TypeSpec)
|
||||
expr := ts.Type
|
||||
st := expr.(*ast.StructType)
|
||||
for _, f := range st.Fields.List {
|
||||
ident, ok := f.Type.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
name := ident.Name
|
||||
if f.Tag != nil {
|
||||
name = reflect.StructTag(parseStringLiteral(f.Tag.Value)).Get("sherpa")
|
||||
}
|
||||
subt := pp.lookupType(ident.Name)
|
||||
if subt == nil {
|
||||
logFatalLinef(pp, ident.Pos(), "subsection %q not found", ident.Name)
|
||||
}
|
||||
subsec := parseSection(subt, pp)
|
||||
subsec.Name = name
|
||||
sec.Sections = append(sec.Sections, subsec)
|
||||
}
|
||||
return sec
|
||||
}
|
||||
|
||||
// Ensure type "t" (used in a field or argument) defined in package pp is parsed
|
||||
// and added to the section.
|
||||
func ensureNamedType(t *doc.Type, sec *section, pp *parsedPackage) {
|
||||
typePath := pp.Path + "." + t.Name
|
||||
if _, have := sec.Typeset[typePath]; have {
|
||||
return
|
||||
}
|
||||
|
||||
tt := &namedType{
|
||||
Name: t.Name,
|
||||
Text: strings.TrimSpace(t.Doc),
|
||||
}
|
||||
// add it early, so self-referencing types can't cause a loop
|
||||
sec.Types = append(sec.Types, tt)
|
||||
sec.Typeset[typePath] = struct{}{}
|
||||
|
||||
ts := t.Decl.Specs[0].(*ast.TypeSpec)
|
||||
if ts.Assign.IsValid() {
|
||||
logFatalLinef(pp, t.Decl.TokPos, "type aliases not yet supported")
|
||||
}
|
||||
|
||||
var gatherFields func(e ast.Expr, typeName string, xpp *parsedPackage)
|
||||
var gatherStructFields func(nt *ast.StructType, typeName string, xpp *parsedPackage)
|
||||
|
||||
gatherFields = func(e ast.Expr, typeName string, xpp *parsedPackage) {
|
||||
switch xt := e.(type) {
|
||||
case *ast.Ident:
|
||||
// Bare type name.
|
||||
tt := xpp.lookupType(xt.Name)
|
||||
if tt == nil {
|
||||
log.Fatalf("could not find type %q used in type %q in package %q", xt.Name, typeName, xpp.Path)
|
||||
}
|
||||
tts := tt.Decl.Specs[0].(*ast.TypeSpec)
|
||||
if ts.Assign.IsValid() {
|
||||
logFatalLinef(xpp, tt.Decl.TokPos, "type aliases not yet supported")
|
||||
}
|
||||
tst, ok := tts.Type.(*ast.StructType)
|
||||
if !ok {
|
||||
logFatalLinef(xpp, tt.Decl.TokPos, "unexpected field type %T", tts.Type)
|
||||
}
|
||||
gatherStructFields(tst, tt.Name, xpp)
|
||||
case *ast.StarExpr:
|
||||
// Field with "*", handle as if without *.
|
||||
gatherFields(xt.X, typeName, xpp)
|
||||
case *ast.SelectorExpr:
|
||||
// With package prefix, lookup the type in the package and gather its fields.
|
||||
dt, nxpp := parseFieldSelector(useSrc{xpp, typeName}, xt)
|
||||
tts := dt.Decl.Specs[0].(*ast.TypeSpec)
|
||||
if ts.Assign.IsValid() {
|
||||
logFatalLinef(nxpp, dt.Decl.TokPos, "type aliases not yet supported")
|
||||
}
|
||||
tst, ok := tts.Type.(*ast.StructType)
|
||||
if !ok {
|
||||
logFatalLinef(nxpp, dt.Decl.TokPos, "unexpected field type %T", tts.Type)
|
||||
}
|
||||
gatherStructFields(tst, dt.Name, nxpp)
|
||||
default:
|
||||
logFatalLinef(xpp, t.Decl.TokPos, "unsupported field with type %T", e)
|
||||
}
|
||||
}
|
||||
|
||||
gatherStructFields = func(nt *ast.StructType, typeName string, xpp *parsedPackage) {
|
||||
for _, f := range nt.Fields.List {
|
||||
if len(f.Names) == 0 {
|
||||
// Embedded field. Treat its fields as if they were included.
|
||||
gatherFields(f.Type, typeName, xpp)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if we need this type. Otherwise we may trip
|
||||
// over an unhandled type that we wouldn't include in
|
||||
// the output (eg due to a struct tag).
|
||||
names := nameList(f.Names, f.Tag)
|
||||
need := false
|
||||
for _, name := range names {
|
||||
if name != "" {
|
||||
need = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !need {
|
||||
continue
|
||||
}
|
||||
|
||||
ff := &field{
|
||||
"",
|
||||
nil,
|
||||
fieldDoc(f),
|
||||
[]*field{},
|
||||
}
|
||||
ff.Typewords = gatherFieldType(t.Name, ff, f.Type, f.Tag, sec, xpp)
|
||||
for _, name := range nameList(f.Names, f.Tag) {
|
||||
nf := &field{}
|
||||
*nf = *ff
|
||||
nf.Name = name
|
||||
tt.Fields = append(tt.Fields, nf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch nt := ts.Type.(type) {
|
||||
case *ast.StructType:
|
||||
tt.Kind = typeStruct
|
||||
gatherStructFields(nt, t.Name, pp)
|
||||
|
||||
case *ast.ArrayType:
|
||||
if ident, ok := nt.Elt.(*ast.Ident); ok && ident.Name == "byte" {
|
||||
tt.Kind = typeBytes
|
||||
} else {
|
||||
logFatalLinef(pp, t.Decl.TokPos, "named type with unsupported element type %T", ts.Type)
|
||||
}
|
||||
|
||||
case *ast.Ident:
|
||||
if strings.HasSuffix(typePath, "sherpa.Int64s") || strings.HasSuffix(typePath, "sherpa.Uint64s") {
|
||||
return
|
||||
}
|
||||
|
||||
tt.Text = t.Doc + ts.Comment.Text()
|
||||
switch nt.Name {
|
||||
case "byte", "int16", "uint16", "int32", "uint32", "int", "uint":
|
||||
tt.Kind = typeInts
|
||||
case "string":
|
||||
tt.Kind = typeStrings
|
||||
default:
|
||||
logFatalLinef(pp, t.Decl.TokPos, "unrecognized type identifier %#v", nt.Name)
|
||||
}
|
||||
|
||||
for _, c := range t.Consts {
|
||||
for _, spec := range c.Decl.Specs {
|
||||
vs, ok := spec.(*ast.ValueSpec)
|
||||
if !ok {
|
||||
logFatalLinef(pp, spec.Pos(), "unsupported non-ast.ValueSpec constant %#v", spec)
|
||||
}
|
||||
if len(vs.Names) != 1 {
|
||||
logFatalLinef(pp, vs.Pos(), "unsupported multiple .Names in %#v", vs)
|
||||
}
|
||||
name := vs.Names[0].Name
|
||||
if len(vs.Values) != 1 {
|
||||
logFatalLinef(pp, vs.Pos(), "unsupported multiple .Values in %#v", vs)
|
||||
}
|
||||
lit, ok := vs.Values[0].(*ast.BasicLit)
|
||||
if !ok {
|
||||
logFatalLinef(pp, vs.Pos(), "unsupported non-ast.BasicLit first .Values %#v", vs)
|
||||
}
|
||||
|
||||
comment := vs.Doc.Text() + vs.Comment.Text()
|
||||
switch lit.Kind {
|
||||
case token.INT:
|
||||
if tt.Kind != typeInts {
|
||||
logFatalLinef(pp, lit.Pos(), "int value for for non-int-enum %q", t.Name)
|
||||
}
|
||||
v, err := strconv.ParseInt(lit.Value, 10, 64)
|
||||
check(err, "parse int literal")
|
||||
iv := struct {
|
||||
Name string
|
||||
Value int
|
||||
Docs string
|
||||
}{name, int(v), strings.TrimSpace(comment)}
|
||||
tt.IntValues = append(tt.IntValues, iv)
|
||||
case token.STRING:
|
||||
if tt.Kind != typeStrings {
|
||||
logFatalLinef(pp, lit.Pos(), "string for non-string-enum %q", t.Name)
|
||||
}
|
||||
v, err := strconv.Unquote(lit.Value)
|
||||
check(err, "unquote literal")
|
||||
sv := struct {
|
||||
Name string
|
||||
Value string
|
||||
Docs string
|
||||
}{name, v, strings.TrimSpace(comment)}
|
||||
tt.StringValues = append(tt.StringValues, sv)
|
||||
default:
|
||||
logFatalLinef(pp, lit.Pos(), "unexpected literal kind %#v", lit.Kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
logFatalLinef(pp, t.Decl.TokPos, "unsupported field/param/return type %T", ts.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func hasOmitEmpty(tag *ast.BasicLit) bool {
|
||||
return hasJSONTagValue(tag, "omitempty")
|
||||
}
|
||||
|
||||
// isCommaString returns whether the tag (may be nil) contains a "json:,string" directive.
|
||||
func isCommaString(tag *ast.BasicLit) bool {
|
||||
return hasJSONTagValue(tag, "string")
|
||||
}
|
||||
|
||||
func hasJSONTagValue(tag *ast.BasicLit, v string) bool {
|
||||
if tag == nil {
|
||||
return false
|
||||
}
|
||||
st := reflect.StructTag(parseStringLiteral(tag.Value))
|
||||
s, ok := st.Lookup("json")
|
||||
if !ok || s == "-" {
|
||||
return false
|
||||
}
|
||||
t := strings.Split(s, ",")
|
||||
for _, e := range t[1:] {
|
||||
if e == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func gatherFieldType(typeName string, f *field, e ast.Expr, fieldTag *ast.BasicLit, sec *section, pp *parsedPackage) typewords {
|
||||
nullablePrefix := typewords{}
|
||||
if hasOmitEmpty(fieldTag) {
|
||||
nullablePrefix = typewords{"nullable"}
|
||||
}
|
||||
|
||||
name := checkReplacedType(useSrc{pp, typeName}, e)
|
||||
if name != nil {
|
||||
if name[0] != "nullable" {
|
||||
return append(nullablePrefix, name...)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
switch t := e.(type) {
|
||||
case *ast.Ident:
|
||||
tt := pp.lookupType(t.Name)
|
||||
if tt != nil {
|
||||
ensureNamedType(tt, sec, pp)
|
||||
return []string{t.Name}
|
||||
}
|
||||
commaString := isCommaString(fieldTag)
|
||||
name := t.Name
|
||||
switch name {
|
||||
case "byte":
|
||||
name = "uint8"
|
||||
case "bool", "int8", "uint8", "int16", "uint16", "int32", "uint32", "float32", "float64", "string", "any":
|
||||
case "int64", "uint64":
|
||||
if commaString {
|
||||
name += "s"
|
||||
}
|
||||
case "int", "uint":
|
||||
name += "32"
|
||||
default:
|
||||
logFatalLinef(pp, t.Pos(), "unsupported field type %q used in type %q in package %q", name, typeName, pp.Path)
|
||||
}
|
||||
if commaString && name != "int64s" && name != "uint64s" {
|
||||
logFatalLinef(pp, t.Pos(), "unsupported tag `json:,\"string\"` for non-64bit int in %s.%s", typeName, f.Name)
|
||||
}
|
||||
return append(nullablePrefix, name)
|
||||
case *ast.ArrayType:
|
||||
return append(nullablePrefix, append([]string{"[]"}, gatherFieldType(typeName, f, t.Elt, nil, sec, pp)...)...)
|
||||
case *ast.MapType:
|
||||
_ = gatherFieldType(typeName, f, t.Key, nil, sec, pp)
|
||||
vt := gatherFieldType(typeName, f, t.Value, nil, sec, pp)
|
||||
return append(nullablePrefix, append([]string{"{}"}, vt...)...)
|
||||
case *ast.InterfaceType:
|
||||
// If we export an interface as an "any" type, we want to make sure it's intended.
|
||||
// Require the user to be explicit with an empty interface.
|
||||
if t.Methods != nil && len(t.Methods.List) > 0 {
|
||||
logFatalLinef(pp, t.Pos(), "unsupported non-empty interface param/return type %T", t)
|
||||
}
|
||||
return append(nullablePrefix, "any")
|
||||
case *ast.StarExpr:
|
||||
tw := gatherFieldType(typeName, f, t.X, fieldTag, sec, pp)
|
||||
if tw[0] != "nullable" {
|
||||
tw = append([]string{"nullable"}, tw...)
|
||||
}
|
||||
return tw
|
||||
case *ast.SelectorExpr:
|
||||
return append(nullablePrefix, parseSelector(t, typeName, sec, pp))
|
||||
}
|
||||
logFatalLinef(pp, e.Pos(), "unimplemented ast.Expr %#v for struct %q field %q in gatherFieldType", e, typeName, f.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseArgType(e ast.Expr, sec *section, pp *parsedPackage) typewords {
|
||||
name := checkReplacedType(useSrc{pp, sec.Name}, e)
|
||||
if name != nil {
|
||||
return name
|
||||
}
|
||||
|
||||
switch t := e.(type) {
|
||||
case *ast.Ident:
|
||||
tt := pp.lookupType(t.Name)
|
||||
if tt != nil {
|
||||
ensureNamedType(tt, sec, pp)
|
||||
return []string{t.Name}
|
||||
}
|
||||
name := t.Name
|
||||
switch name {
|
||||
case "byte":
|
||||
name = "uint8"
|
||||
case "bool", "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64", "float32", "float64", "string", "any":
|
||||
case "int", "uint":
|
||||
name += "32"
|
||||
case "error":
|
||||
// allowed here, checked if in right location by caller
|
||||
default:
|
||||
logFatalLinef(pp, t.Pos(), "unsupported arg type %q", name)
|
||||
}
|
||||
return []string{name}
|
||||
case *ast.ArrayType:
|
||||
return append([]string{"[]"}, parseArgType(t.Elt, sec, pp)...)
|
||||
case *ast.Ellipsis:
|
||||
// Ellipsis parameters to a function must be passed as an array, so document it that way.
|
||||
return append([]string{"[]"}, parseArgType(t.Elt, sec, pp)...)
|
||||
case *ast.MapType:
|
||||
_ = parseArgType(t.Key, sec, pp)
|
||||
vt := parseArgType(t.Value, sec, pp)
|
||||
return append([]string{"{}"}, vt...)
|
||||
case *ast.InterfaceType:
|
||||
// If we export an interface as an "any" type, we want to make sure it's intended.
|
||||
// Require the user to be explicit with an empty interface.
|
||||
if t.Methods != nil && len(t.Methods.List) > 0 {
|
||||
logFatalLinef(pp, t.Pos(), "unsupported non-empty interface param/return type %T", t)
|
||||
}
|
||||
return []string{"any"}
|
||||
case *ast.StarExpr:
|
||||
return append([]string{"nullable"}, parseArgType(t.X, sec, pp)...)
|
||||
case *ast.SelectorExpr:
|
||||
return []string{parseSelector(t, sec.TypeName, sec, pp)}
|
||||
}
|
||||
logFatalLinef(pp, e.Pos(), "unimplemented ast.Expr %#v in parseArgType", e)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse the selector of a field, returning the type and the parsed package it exists in. This cannot be a builtin type.
|
||||
func parseFieldSelector(u useSrc, t *ast.SelectorExpr) (*doc.Type, *parsedPackage) {
|
||||
packageIdent, ok := t.X.(*ast.Ident)
|
||||
if !ok {
|
||||
u.Fatalf(t.Pos(), "unexpected non-ident for SelectorExpr.X")
|
||||
}
|
||||
pkgName := packageIdent.Name
|
||||
typeName := t.Sel.Name
|
||||
|
||||
importPath := u.lookupPackageImportPath(pkgName)
|
||||
if importPath == "" {
|
||||
u.Fatalf(t.Pos(), "cannot find source for type %q that references package %q (perhaps try -replace)", u, pkgName)
|
||||
}
|
||||
|
||||
opp := u.Ppkg.ensurePackageParsed(importPath)
|
||||
tt := opp.lookupType(typeName)
|
||||
if tt == nil {
|
||||
u.Fatalf(t.Pos(), "could not find type %q in package %q", typeName, importPath)
|
||||
}
|
||||
return tt, opp
|
||||
}
|
||||
|
||||
func parseSelector(t *ast.SelectorExpr, srcTypeName string, sec *section, pp *parsedPackage) string {
|
||||
packageIdent, ok := t.X.(*ast.Ident)
|
||||
if !ok {
|
||||
logFatalLinef(pp, t.Pos(), "unexpected non-ident for SelectorExpr.X")
|
||||
}
|
||||
pkgName := packageIdent.Name
|
||||
typeName := t.Sel.Name
|
||||
|
||||
if pkgName == "time" && typeName == "Time" {
|
||||
return "timestamp"
|
||||
}
|
||||
if pkgName == "sherpa" {
|
||||
switch typeName {
|
||||
case "Int64s":
|
||||
return "int64s"
|
||||
case "Uint64s":
|
||||
return "uint64s"
|
||||
}
|
||||
}
|
||||
|
||||
importPath := pp.lookupPackageImportPath(srcTypeName, pkgName)
|
||||
if importPath == "" {
|
||||
logFatalLinef(pp, t.Pos(), "cannot find source for %q (perhaps try -replace)", fmt.Sprintf("%s.%s", pkgName, typeName))
|
||||
}
|
||||
|
||||
opp := pp.ensurePackageParsed(importPath)
|
||||
tt := opp.lookupType(typeName)
|
||||
if tt == nil {
|
||||
logFatalLinef(pp, t.Pos(), "could not find type %q in package %q", typeName, importPath)
|
||||
}
|
||||
ensureNamedType(tt, sec, opp)
|
||||
return typeName
|
||||
}
|
||||
|
||||
type replacement struct {
|
||||
original string // a Go type, eg "pkg.Type" or "*pkg.Type"
|
||||
target typewords
|
||||
}
|
||||
|
||||
var _replacements []replacement
|
||||
|
||||
func typeReplacements() []replacement {
|
||||
if _replacements != nil {
|
||||
return _replacements
|
||||
}
|
||||
|
||||
_replacements = []replacement{}
|
||||
for _, repl := range strings.Split(*replace, ",") {
|
||||
if repl == "" {
|
||||
continue
|
||||
}
|
||||
tokens := strings.Split(repl, " ")
|
||||
if len(tokens) < 2 {
|
||||
log.Fatalf("bad replacement %q, must have at least two tokens, space-separated", repl)
|
||||
}
|
||||
r := replacement{tokens[0], tokens[1:]}
|
||||
_replacements = append(_replacements, r)
|
||||
}
|
||||
return _replacements
|
||||
}
|
||||
|
||||
// Use of a type Name from package Ppkg. Used to look up references from that
|
||||
// location (the file where the type is defined, with its imports) for a given Go
|
||||
// ast.
|
||||
type useSrc struct {
|
||||
Ppkg *parsedPackage
|
||||
Name string
|
||||
}
|
||||
|
||||
func (u useSrc) lookupPackageImportPath(pkgName string) string {
|
||||
return u.Ppkg.lookupPackageImportPath(u.Name, pkgName)
|
||||
}
|
||||
|
||||
func (u useSrc) String() string {
|
||||
return fmt.Sprintf("%s.%s", u.Ppkg.Path, u.Name)
|
||||
}
|
||||
|
||||
func (u useSrc) Fatalf(tok token.Pos, format string, args ...interface{}) {
|
||||
logFatalLinef(u.Ppkg, tok, format, args...)
|
||||
}
|
||||
|
||||
// Return a go type name, eg "*time.Time".
|
||||
// This function does not parse the types itself, because it would mean they could
|
||||
// be added to the sherpadoc output even if they aren't otherwise used (due to
|
||||
// replacement).
|
||||
func goTypeName(u useSrc, e ast.Expr) string {
|
||||
switch t := e.(type) {
|
||||
case *ast.Ident:
|
||||
return t.Name
|
||||
case *ast.ArrayType:
|
||||
return "[]" + goTypeName(u, t.Elt)
|
||||
case *ast.Ellipsis:
|
||||
// Ellipsis parameters to a function must be passed as an array, so document it that way.
|
||||
return "[]" + goTypeName(u, t.Elt)
|
||||
case *ast.MapType:
|
||||
return fmt.Sprintf("map[%s]%s", goTypeName(u, t.Key), goTypeName(u, t.Value))
|
||||
case *ast.InterfaceType:
|
||||
return "interface{}"
|
||||
case *ast.StarExpr:
|
||||
return "*" + goTypeName(u, t.X)
|
||||
case *ast.SelectorExpr:
|
||||
packageIdent, ok := t.X.(*ast.Ident)
|
||||
if !ok {
|
||||
u.Fatalf(t.Pos(), "unexpected non-ident for SelectorExpr.X")
|
||||
}
|
||||
pkgName := packageIdent.Name
|
||||
typeName := t.Sel.Name
|
||||
|
||||
importPath := u.lookupPackageImportPath(pkgName)
|
||||
if importPath != "" {
|
||||
return fmt.Sprintf("%s.%s", importPath, typeName)
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", pkgName, typeName)
|
||||
// todo: give proper error message for *ast.StructType
|
||||
}
|
||||
u.Fatalf(e.Pos(), "unimplemented ast.Expr %#v in goTypeName", e)
|
||||
return ""
|
||||
}
|
||||
|
||||
func checkReplacedType(u useSrc, e ast.Expr) typewords {
|
||||
repls := typeReplacements()
|
||||
if len(repls) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := goTypeName(u, e)
|
||||
return replacementType(repls, name)
|
||||
}
|
||||
|
||||
func replacementType(repls []replacement, name string) typewords {
|
||||
for _, repl := range repls {
|
||||
if repl.original == name {
|
||||
return repl.target
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensures the package for importPath has been parsed at least once, and return it.
|
||||
func (pp *parsedPackage) ensurePackageParsed(importPath string) *parsedPackage {
|
||||
r := pp.Imports[importPath]
|
||||
if r != nil {
|
||||
return r
|
||||
}
|
||||
|
||||
var localPath string
|
||||
var astPkg *ast.Package
|
||||
var fset *token.FileSet
|
||||
|
||||
// If dependencies are vendored, we load packages from vendor/. This is typically
|
||||
// faster than using package.Load (the fallback), which may spawn commands.
|
||||
// For me, while testing, for loading a simple package from the same module goes
|
||||
// from 50-100 ms to 1-5ms. Loading "net" from 200ms to 65ms.
|
||||
|
||||
if gomodFile != nil {
|
||||
if importPath == gomodFile.Module.Mod.Path {
|
||||
localPath = gomodDir
|
||||
} else if strings.HasPrefix(importPath, gomodFile.Module.Mod.Path+"/") {
|
||||
localPath = filepath.Join(gomodDir, strings.TrimPrefix(importPath, gomodFile.Module.Mod.Path+"/"))
|
||||
} else {
|
||||
p := filepath.Join(gomodDir, "vendor", importPath)
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
localPath = p
|
||||
} else {
|
||||
localPath = filepath.Join(runtime.GOROOT(), "src", importPath)
|
||||
}
|
||||
}
|
||||
|
||||
fset = token.NewFileSet()
|
||||
astPkgs, err := parser.ParseDir(fset, localPath, nil, parser.ParseComments|parser.DeclarationErrors)
|
||||
check(err, "parsing go files from "+localPath)
|
||||
for name, pkg := range astPkgs {
|
||||
if strings.HasSuffix(name, "_test") {
|
||||
continue
|
||||
}
|
||||
if astPkg != nil {
|
||||
log.Fatalf("loading package %q: multiple packages found", importPath)
|
||||
}
|
||||
astPkg = pkg
|
||||
}
|
||||
} else {
|
||||
config := &packages.Config{
|
||||
Mode: packages.NeedName | packages.NeedFiles,
|
||||
}
|
||||
pkgs, err := packages.Load(config, importPath)
|
||||
check(err, "loading package")
|
||||
if len(pkgs) != 1 {
|
||||
log.Fatalf("loading package %q: got %d packages, expected 1", importPath, len(pkgs))
|
||||
}
|
||||
pkg := pkgs[0]
|
||||
if len(pkg.GoFiles) == 0 {
|
||||
log.Fatalf("loading package %q: no go files found", importPath)
|
||||
}
|
||||
|
||||
fset = token.NewFileSet()
|
||||
localPath = filepath.Dir(pkg.GoFiles[0])
|
||||
astPkgs, err := parser.ParseDir(fset, localPath, nil, parser.ParseComments)
|
||||
check(err, "parsing go files from directory")
|
||||
var ok bool
|
||||
astPkg, ok = astPkgs[pkg.Name]
|
||||
if !ok {
|
||||
log.Fatalf("loading package %q: could not find astPkg for %q", importPath, pkg.Name)
|
||||
}
|
||||
}
|
||||
|
||||
docpkg := doc.New(astPkg, "", doc.AllDecls|doc.PreserveAST)
|
||||
|
||||
npp := &parsedPackage{
|
||||
Fset: fset,
|
||||
Path: localPath,
|
||||
Pkg: astPkg,
|
||||
Docpkg: docpkg,
|
||||
Imports: make(map[string]*parsedPackage),
|
||||
}
|
||||
pp.Imports[importPath] = npp
|
||||
return npp
|
||||
}
|
||||
|
||||
// LookupPackageImportPath returns the import/package path for pkgName as used as
|
||||
// used in the type named typeName.
|
||||
func (pp *parsedPackage) lookupPackageImportPath(typeName, pkgName string) string {
|
||||
file := pp.lookupTypeFile(typeName)
|
||||
for _, imp := range file.Imports {
|
||||
if imp.Name != nil && imp.Name.Name == pkgName || imp.Name == nil && (parseStringLiteral(imp.Path.Value) == pkgName || strings.HasSuffix(parseStringLiteral(imp.Path.Value), "/"+pkgName)) {
|
||||
return parseStringLiteral(imp.Path.Value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// LookupTypeFile returns the go source file that containst he definition of the type named typeName.
|
||||
func (pp *parsedPackage) lookupTypeFile(typeName string) *ast.File {
|
||||
for _, file := range pp.Pkg.Files {
|
||||
for _, decl := range file.Decls {
|
||||
switch d := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
for _, spec := range d.Specs {
|
||||
switch s := spec.(type) {
|
||||
case *ast.TypeSpec:
|
||||
if s.Name.Name == typeName {
|
||||
return file
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Fatalf("could not find type %q", fmt.Sprintf("%s.%s", pp.Path, typeName))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Populate "params" with the arguments from "fields", which are function parameters or return type.
|
||||
func parseArgs(params *[]sherpadoc.Arg, fields *ast.FieldList, sec *section, pp *parsedPackage, isParams bool) {
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
addParam := func(name string, tw typewords) {
|
||||
param := sherpadoc.Arg{Name: name, Typewords: tw}
|
||||
*params = append(*params, param)
|
||||
}
|
||||
for _, f := range fields.List {
|
||||
typ := parseArgType(f.Type, sec, pp)
|
||||
// Handle named params. Can be both arguments to a function or return types.
|
||||
for _, name := range f.Names {
|
||||
addParam(name.Name, typ)
|
||||
}
|
||||
// Return types often don't have a name, don't forget them.
|
||||
if len(f.Names) == 0 {
|
||||
addParam("", typ)
|
||||
}
|
||||
}
|
||||
|
||||
for i, p := range *params {
|
||||
if p.Typewords[len(p.Typewords)-1] != "error" {
|
||||
continue
|
||||
}
|
||||
if isParams || i != len(*params)-1 {
|
||||
logFatalLinef(pp, fields.Pos(), "can only have error type as last return value")
|
||||
}
|
||||
pp := *params
|
||||
*params = pp[:len(pp)-1]
|
||||
}
|
||||
}
|
||||
|
||||
func adjustFunctionName(s string) string {
|
||||
switch *adjustFunctionNames {
|
||||
case "":
|
||||
return strings.ToLower(s[:1]) + s[1:]
|
||||
case "none":
|
||||
return s
|
||||
case "lowerWord":
|
||||
r := ""
|
||||
for i, c := range s {
|
||||
lc := unicode.ToLower(c)
|
||||
if lc == c {
|
||||
r += s[i:]
|
||||
break
|
||||
}
|
||||
r += string(lc)
|
||||
}
|
||||
return r
|
||||
default:
|
||||
panic(fmt.Sprintf("bad value for flag adjust-function-names: %q", *adjustFunctionNames))
|
||||
}
|
||||
}
|
||||
|
||||
// ParseMethod ensures the function fn from package pp ends up in section sec, with parameters/return named types filled in.
|
||||
func parseMethod(sec *section, fn *doc.Func, pp *parsedPackage) {
|
||||
f := &function{
|
||||
Name: adjustFunctionName(fn.Name),
|
||||
Text: fn.Doc,
|
||||
Params: []sherpadoc.Arg{},
|
||||
Returns: []sherpadoc.Arg{},
|
||||
}
|
||||
|
||||
// If first function parameter is context.Context, we skip it in the documentation.
|
||||
// The sherpa handler automatically fills it with the http request context when called.
|
||||
params := fn.Decl.Type.Params
|
||||
if params != nil && len(params.List) > 0 && len(params.List[0].Names) == 1 && goTypeName(useSrc{pp, sec.Name}, params.List[0].Type) == "context.Context" {
|
||||
params.List = params.List[1:]
|
||||
}
|
||||
isParams := true
|
||||
parseArgs(&f.Params, params, sec, pp, isParams)
|
||||
|
||||
isParams = false
|
||||
parseArgs(&f.Returns, fn.Decl.Type.Results, sec, pp, isParams)
|
||||
sec.Functions = append(sec.Functions, f)
|
||||
}
|
85
vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/sherpa.go
generated
vendored
Normal file
85
vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/sherpa.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mjl-/sherpadoc"
|
||||
)
|
||||
|
||||
func sherpaSection(sec *section) *sherpadoc.Section {
|
||||
doc := &sherpadoc.Section{
|
||||
Name: sec.Name,
|
||||
Docs: sec.Text,
|
||||
Functions: []*sherpadoc.Function{},
|
||||
Sections: []*sherpadoc.Section{},
|
||||
Structs: []sherpadoc.Struct{},
|
||||
Ints: []sherpadoc.Ints{},
|
||||
Strings: []sherpadoc.Strings{},
|
||||
}
|
||||
for _, t := range sec.Types {
|
||||
switch t.Kind {
|
||||
case typeStruct:
|
||||
tt := sherpadoc.Struct{
|
||||
Name: t.Name,
|
||||
Docs: t.Text,
|
||||
Fields: []sherpadoc.Field{},
|
||||
}
|
||||
for _, f := range t.Fields {
|
||||
ff := sherpadoc.Field{
|
||||
Name: f.Name,
|
||||
Docs: f.Doc,
|
||||
Typewords: f.Typewords,
|
||||
}
|
||||
tt.Fields = append(tt.Fields, ff)
|
||||
}
|
||||
doc.Structs = append(doc.Structs, tt)
|
||||
case typeInts:
|
||||
e := sherpadoc.Ints{
|
||||
Name: t.Name,
|
||||
Docs: strings.TrimSpace(t.Text),
|
||||
Values: t.IntValues,
|
||||
}
|
||||
doc.Ints = append(doc.Ints, e)
|
||||
case typeStrings:
|
||||
e := sherpadoc.Strings{
|
||||
Name: t.Name,
|
||||
Docs: strings.TrimSpace(t.Text),
|
||||
Values: t.StringValues,
|
||||
}
|
||||
doc.Strings = append(doc.Strings, e)
|
||||
case typeBytes:
|
||||
// todo: hack. find proper way to docment them. better for larger functionality: add generic support for lists of types. for now we'll fake this being a string...
|
||||
e := sherpadoc.Strings{
|
||||
Name: t.Name,
|
||||
Docs: strings.TrimSpace(t.Text),
|
||||
Values: []struct{Name string; Value string; Docs string}{},
|
||||
}
|
||||
doc.Strings = append(doc.Strings, e)
|
||||
default:
|
||||
panic("missing case")
|
||||
}
|
||||
}
|
||||
for _, fn := range sec.Functions {
|
||||
// Ensure returns always have a name. Go can leave them nameless.
|
||||
// Either they all have names or they don't, so the names we make up will never clash.
|
||||
for i := range fn.Returns {
|
||||
if fn.Returns[i].Name == "" {
|
||||
fn.Returns[i].Name = fmt.Sprintf("r%d", i)
|
||||
}
|
||||
}
|
||||
|
||||
f := &sherpadoc.Function{
|
||||
Name: fn.Name,
|
||||
Docs: strings.TrimSpace(fn.Text),
|
||||
Params: fn.Params,
|
||||
Returns: fn.Returns,
|
||||
}
|
||||
doc.Functions = append(doc.Functions, f)
|
||||
}
|
||||
for _, subsec := range sec.Sections {
|
||||
doc.Sections = append(doc.Sections, sherpaSection(subsec))
|
||||
}
|
||||
doc.Docs = strings.TrimSpace(doc.Docs)
|
||||
return doc
|
||||
}
|
Reference in New Issue
Block a user