From 586f66da504f54886cb468f927457daf1a76f2f1 Mon Sep 17 00:00:00 2001 From: Juha Ylitalo Date: Mon, 20 Feb 2023 22:29:21 +0200 Subject: [PATCH] Go2md v0.3 (#3) * warn about missing doc.go * warn about types that are not exported * bugfixes (for example []) * `--output` argument to direct output into file * line-up tags in struct fields --- cmd/README.md | 6 +- cmd/cmd.go | 11 ++ cmd/doc.go | 3 + pkg/README.md | 5 +- pkg/doc.go | 2 + pkg/funcs.go | 311 +++++++++++++----------------------------------- pkg/mod.go | 5 + pkg/run.go | 31 ++++- pkg/template.md | 2 +- pkg/vartype.go | 179 ++++++++++++++++++++++++++++ version.txt | 2 +- 11 files changed, 319 insertions(+), 238 deletions(-) create mode 100644 cmd/doc.go create mode 100644 pkg/doc.go create mode 100644 pkg/vartype.go diff --git a/cmd/README.md b/cmd/README.md index f77497f..c105aa8 100644 --- a/cmd/README.md +++ b/cmd/README.md @@ -1,8 +1,10 @@ # github.com/jylitalo/go2md/cmd ## Overview +Package cmd provides command line arguments and flags parsing with spf13/cobra and +calls backend functionality from pkg package. -Imports: 4 +Imports: 6 ## Index - [func NewCommand(out io.Writer, version string) *cobra.Command](#func-newcommand) @@ -33,5 +35,5 @@ Default is to generate markdown from current directory. -- -Generated by [github.com/jylitalo/go2md](https://github.com/jylitalo/go2cmd/) v0.2 +Generated by [github.com/jylitalo/go2md](https://github.com/jylitalo/go2md/) v0.3 diff --git a/cmd/cmd.go b/cmd/cmd.go index 411745d..7f803ae 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -3,7 +3,9 @@ package cmd import ( "fmt" "io" + "os" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/jylitalo/go2md/pkg" @@ -19,9 +21,18 @@ func NewCommand(out io.Writer, version string) *cobra.Command { out.Write([]byte(fmt.Sprintf("go2md %s\n", version))) return nil } + if flag, _ := cmd.Flags().GetString("output"); flag != "" { + fout, err := os.Create(flag) + if err != nil { + log.WithFields(log.Fields{"err": err, "filename": flag}).Fatal("failed to create file") + } + defer fout.Close() + out = fout + } return pkg.Run(out, version) }, } cmd.Flags().BoolP("version", "v", false, "print go2md version") + cmd.Flags().StringP("output", "o", "", "write output to file") return cmd } diff --git a/cmd/doc.go b/cmd/doc.go new file mode 100644 index 0000000..0a087d5 --- /dev/null +++ b/cmd/doc.go @@ -0,0 +1,3 @@ +// Package cmd provides command line arguments and flags parsing with spf13/cobra and +// calls backend functionality from pkg package. +package cmd diff --git a/pkg/README.md b/pkg/README.md index 5a080f6..0cdf0c3 100644 --- a/pkg/README.md +++ b/pkg/README.md @@ -1,8 +1,9 @@ # github.com/jylitalo/go2md/pkg ## Overview +Package pkg provides the backend functionality for golang to markdown transformation. -Imports: 15 +Imports: 16 ## Index - [Variables](variables) @@ -37,5 +38,5 @@ Run reads all "*.go" files (excluding "*_test.go") and writes markdown document -- -Generated by [github.com/jylitalo/go2md](https://github.com/jylitalo/go2cmd/) v0.2 +Generated by [github.com/jylitalo/go2md](https://github.com/jylitalo/go2md/) v0.3 diff --git a/pkg/doc.go b/pkg/doc.go new file mode 100644 index 0000000..135e67d --- /dev/null +++ b/pkg/doc.go @@ -0,0 +1,2 @@ +// Package pkg provides the backend functionality for golang to markdown transformation. +package pkg diff --git a/pkg/funcs.go b/pkg/funcs.go index 31078cc..2b5d29d 100644 --- a/pkg/funcs.go +++ b/pkg/funcs.go @@ -4,18 +4,18 @@ import ( "fmt" "go/ast" "go/doc" - "go/token" "path/filepath" + "regexp" "strings" "text/template" log "github.com/sirupsen/logrus" ) -type varTypeOutput struct { - full string - link string -} +var ( + basePrefix = " " + exportedType, _ = regexp.Compile("^[A-Z]") +) func templateFuncs(version string, imports map[string]string) template.FuncMap { return template.FuncMap{ @@ -44,209 +44,59 @@ func intoImportLink(text string, imports map[string]string) string { return text } switch text { - case "bool", "char", "error", "float", "float32", "float64", "int", "int32", "int64", "string": + case "bool", "byte", "char", "error", "float", "float32", "float64", "int", "int32", "int64", "string": return text } - if strings.Contains(text, ".") { - fields := strings.SplitN(text, ".", 2) - if modPath, ok := imports[fields[0]]; ok { - modFields := strings.Split(modPath, "/") - if len(modFields) >= 3 { - mainPath := strings.Join(strings.Split(imports["main"], "/")[0:3], "/") - if strings.Join(modFields[0:3], "/") == mainPath { - relPath, err := filepath.Rel(imports["main"], modPath) - if err != nil { - log.WithFields(log.Fields{"imports['main']": imports["main"], "modPath": modPath}).Fatal("Unable to establish relative path") - } - return fmt.Sprintf(`%s`, relPath, intoLink("type "+fields[1]), text) - } - } - return fmt.Sprintf(`%s`, modPath, fields[1], text) + if !strings.Contains(text, ".") { + if exportedType.MatchString(text) { + return fmt.Sprintf(`%s`, intoLink("type "+text), text) } - return fmt.Sprintf(`%s`, fields[0], fields[1], text) + log.Warningf("Internal type: %s", text) + return text } - return fmt.Sprintf(`%s`, intoLink("type "+text), text) -} - -func variableType(variable ast.Expr, depth int, imports map[string]string) varTypeOutput { - basePrefix := " " - switch t := variable.(type) { - case nil: - return varTypeOutput{full: "nil", link: "nil"} - case *ast.ArrayType: - varType := variableType(t.Elt, depth, imports) - varType.full = "[]" + varType.full - if strings.HasPrefix(varType.link, "`, `">[]`, 1) - } - return varType - case *ast.BasicLit: - if t.Value != "" { - return varTypeOutput{ - full: t.Value, - link: fmt.Sprintf(`%s`, intoLink("type "+t.Value), t.Value), - } - } - switch t.Kind { - case token.INT: - return varTypeOutput{full: "int", link: "int"} - case token.FLOAT: - return varTypeOutput{full: "float", link: "float"} - case token.IMAG: - return varTypeOutput{full: "imag", link: "imag"} - case token.CHAR: - return varTypeOutput{full: "char", link: "char"} - case token.STRING: - return varTypeOutput{full: "string", link: "string"} - default: - log.WithField("t.Kind", t.Kind).Panic("unknown token kind") - } - case *ast.CallExpr: - funcName := variableType(t.Fun, depth, imports).full - fullArgs := []string{} - linkArgs := []string{} - for _, arg := range t.Args { - varType := variableType(arg, depth, imports) - fullArgs = append(fullArgs, varType.full) - linkArgs = append(linkArgs, varType.link) - } - return varTypeOutput{ - full: fmt.Sprintf("%s(%s)", funcName, strings.Join(fullArgs, ", ")), - link: fmt.Sprintf("%s(%s)", funcName, strings.Join(linkArgs, ", ")), - } - case *ast.CompositeLit: - elts := []string{} - eltsType := variableType(t.Type, depth, imports).full - for _, elt := range t.Elts { - elts = append(elts, variableType(elt, depth, imports).full) - } - switch subType := t.Type.(type) { - case *ast.ArrayType, *ast.MapType, *ast.SelectorExpr: - lines := strings.Split(strings.Join(elts, ",\n"), "\n") - return varTypeOutput{full: fmt.Sprintf( - "%s{\n%s%s,\n}", eltsType, basePrefix, - strings.Join(lines, "\n"+basePrefix), - )} - case nil: - return varTypeOutput{full: strings.Join(elts, ",\n")} - default: - log.Panicf("Unknown CompositeLit: %#v", subType) - } - case *ast.Ellipsis: - varType := variableType(t.Elt, depth, imports) - varType.full = "..." + varType.full - varType.link = "..." + varType.link - return varType - case *ast.FuncType: - return varTypeOutput{full: fmt.Sprintf( - "func(%s)%s", strings.Join(funcParams(t.Params, imports), ", "), - funcReturns(t.Results, imports), - )} - case *ast.Ident: - switch t.Name { - case "bool", "char", "error", "float", "float32", "float64", "int", "int32", "int64", "string": - return varTypeOutput{full: t.Name, link: t.Name} - } - return varTypeOutput{ - full: t.Name, - link: fmt.Sprintf(`%s`, intoLink("type "+t.Name), t.Name), - } - case *ast.InterfaceType: - return varTypeOutput{full: "interface{}", link: "interface{}"} - case *ast.KeyValueExpr: - keyType := variableType(t.Key, depth, imports) - valueType := variableType(t.Value, depth, imports) - switch t.Value.(type) { - case *ast.CompositeLit: - switch t.Key.(type) { - case *ast.BasicLit: - return varTypeOutput{ - full: fmt.Sprintf( - "%s: {\n%s%s,\n}", keyType.full, basePrefix, - strings.Join(strings.Split(valueType.full, "\n"), "\n"+basePrefix), - ), - link: fmt.Sprintf( - "%s: {\n%s%s,\n}", keyType.full, basePrefix, - strings.Join(strings.Split(valueType.full, "\n"), "\n"+basePrefix), - ), + fields := strings.SplitN(text, ".", 2) + if modPath, ok := imports[fields[0]]; ok { + modFields := strings.Split(modPath, "/") + if len(modFields) >= 3 { + mainPath := strings.Join(strings.Split(imports["main"], "/")[0:3], "/") + if strings.Join(modFields[0:3], "/") == mainPath { + relPath, err := filepath.Rel(imports["main"], modPath) + if err != nil { + log.WithFields(log.Fields{"imports['main']": imports["main"], "modPath": modPath}).Fatal("Unable to establish relative path") } + return fmt.Sprintf(`%s`, relPath, intoLink("type "+fields[1]), text) } } - return varTypeOutput{ - full: keyType.full + ": " + valueType.full, - link: keyType.link + ": " + valueType.link, - } - case *ast.MapType: - keyType := variableType(t.Key, depth, imports) - valueType := variableType(t.Value, depth, imports) - return varTypeOutput{ - full: fmt.Sprintf("map[%s]%s", keyType.full, valueType.full), - link: fmt.Sprintf("map[%s]%s", keyType.link, valueType.link), - } - case *ast.SelectorExpr: - return varTypeOutput{ - full: fmt.Sprintf("%s.%s", t.X, t.Sel), - link: intoImportLink(fmt.Sprintf("%s.%s", t.X, t.Sel), imports), - } - case *ast.StarExpr: - varType := variableType(t.X, depth, imports) - varType.full = "*" + varType.full - if strings.HasPrefix(varType.link, "`, `">*`, 1) - } else { - varType.link = "*" + varType.link - } - return varType - case *ast.StructType: - lines := []string{} - for _, field := range t.Fields.List { - lines = append(lines, typeField(field, depth+1, true, imports)) - } - return varTypeOutput{ - full: fmt.Sprintf("struct\n%s", strings.Join(lines, "\n")), - link: fmt.Sprintf("struct\n%s", strings.Join(lines, "\n")), - } - case *ast.UnaryExpr: - switch t.Op { - case token.AND: - varType := variableType(t.X, depth, imports) - varType.full = "&" + varType.full - if strings.HasPrefix(varType.link, "`, `">&`, 1) - } else { - varType.link = "&" + varType.link - } - return varType - default: - log.Panicf("unknown unary type %d for %s", t.Op, t.X) - } - default: - log.WithFields(log.Fields{ - "variable.(type)": fmt.Sprintf("%#v", variable)}, - ).Panicf("unknown variable type") + return fmt.Sprintf(`%s`, modPath, fields[1], text) } - log.WithField("variableType", fmt.Sprintf("%#v", variable)) - return varTypeOutput{} + return fmt.Sprintf(`%s`, fields[0], fields[1], text) } -func typeField(field *ast.Field, depth int, hyphen bool, imports map[string]string) string { - line := "" - prefix := " " - for i := 0; i < depth; i++ { - prefix = prefix + " " +func typeField(field *ast.Field, depth int, hyphen bool, imports map[string]string) varTypeOutput { + prefix := "" + for i := 0; i <= depth; i++ { + prefix = prefix + basePrefix } if hyphen { prefix = prefix + "- " } switch t := field.Type.(type) { case *ast.FuncType: - line = fmt.Sprintf( - "%sfunc %s(%s)%s", prefix, field.Names[0], - strings.Join(funcParams(t.Params, imports), ", "), funcReturns(t.Results, imports)) + fparams := funcParams(t.Params, imports) + freturns := funcReturns(t.Results, imports) + msg := fmt.Sprintf("%sfunc %s(%%s)%%s", prefix, field.Names[0]) + return sprintf(msg, fparams, freturns) default: - line = fmt.Sprintf("%s%s %s", prefix, field.Names[0], variableType(field.Type, depth, imports).link) + vto := variableType(field.Type, depth, hyphen, imports) + plainIdx := strings.LastIndex(vto.plainText, "\n}") + mdIdx := strings.LastIndex(vto.markdown, "\n}") + if plainIdx != -1 { + vto.plainText = vto.plainText[:plainIdx+1] + prefix + vto.plainText[plainIdx+1:] + vto.markdown = vto.markdown[:mdIdx+1] + prefix + vto.markdown[mdIdx+1:] + } + msg := fmt.Sprintf("%s%s %%s", prefix, field.Names[0]) + return sprintf(msg, vto) } - return line } func funcReceiver(funcObj doc.Func) string { @@ -261,53 +111,44 @@ func funcReceiver(funcObj doc.Func) string { // funcParams combines function parameters into string. // If you start from "funcObj doc.Func", you will get ast.Field from // "funcObj.Decl.Type.Params.List" -func funcParams(fields *ast.FieldList, imports map[string]string) []string { +func funcParams(fields *ast.FieldList, imports map[string]string) varTypeOutput { if fields == nil { - return nil + return sprintf("") } - params := []string{} + varTypes := []varTypeOutput{} for _, paramList := range fields.List { - varType := variableType(paramList.Type, 0, imports) - paramType := varType.full - if imports != nil { - paramType = varType.link - } + vto := variableType(paramList.Type, 0, false, imports) if len(paramList.Names) == 0 { - params = append(params, paramType) + varTypes = append(varTypes, vto) continue } + nameList := []string{} for _, param := range paramList.Names { - params = append(params, param.Name) + nameList = append(nameList, param.Name) } - last := len(params) - 1 - params[last] = params[last] + " " + paramType + names := strings.Join(nameList, ", ") + vto = sprintf(names+" %s", vto) + varTypes = append(varTypes, vto) } - return params + return join(varTypes, ", ") } // funcReturns combines function return values into string. // If you start from "funcObj doc.Func", you will get ast.Field from // "funcObj.Decl.Type.Results" -func funcReturns(fields *ast.FieldList, imports map[string]string) string { +func funcReturns(fields *ast.FieldList, imports map[string]string) varTypeOutput { switch { case fields == nil: - return "" - case len(fields.List) == 1 && imports == nil: - return " " + variableType(fields.List[0].Type, 0, nil).full + return sprintf("") case len(fields.List) == 1: - return " " + variableType(fields.List[0].Type, 0, imports).link - case imports == nil: - r := []string{} - for _, param := range fields.List { - r = append(r, variableType(param.Type, 0, imports).full) - } - return fmt.Sprintf(" (%s)", strings.Join(r, ", ")) + vto := variableType(fields.List[0].Type, 0, false, imports) + return sprintf(" %s", vto) default: - r := []string{} + varTypes := []varTypeOutput{} for _, param := range fields.List { - r = append(r, variableType(param.Type, 0, imports).link) + varTypes = append(varTypes, variableType(param.Type, 0, false, imports)) } - return fmt.Sprintf(" (%s)", strings.Join(r, ", ")) + return sprintf(" (%s)", join(varTypes, ", ")) } } @@ -318,8 +159,8 @@ func funcHeading(funcObj doc.Func) string { func funcElem(funcObj doc.Func) string { text := fmt.Sprintf( "func %s%s(%s)%s", funcReceiver(funcObj), funcObj.Name, - strings.Join(funcParams(funcObj.Decl.Type.Params, nil), ", "), - funcReturns(funcObj.Decl.Type.Results, nil), + funcParams(funcObj.Decl.Type.Params, nil).plainText, + funcReturns(funcObj.Decl.Type.Results, nil).plainText, ) link := intoLink(fmt.Sprintf("func %s%s", funcReceiver(funcObj), funcObj.Name)) return fmt.Sprintf("- [%s](#%s)", text, link) @@ -329,8 +170,8 @@ func funcSection(imports map[string]string) func(doc.Func) string { return func(funcObj doc.Func) string { return fmt.Sprintf( "func %s%s(%s)%s", funcReceiver(funcObj), funcObj.Name, - strings.Join(funcParams(funcObj.Decl.Type.Params, imports), ", "), - funcReturns(funcObj.Decl.Type.Results, imports), + funcParams(funcObj.Decl.Type.Params, imports).markdown, + funcReturns(funcObj.Decl.Type.Results, imports).markdown, ) } } @@ -377,22 +218,36 @@ func typeSection(imports map[string]string) func(doc.Type) string { for _, spec := range typeObj.Decl.Specs { switch t := spec.(*ast.TypeSpec).Type.(type) { case *ast.FuncType, *ast.Ident: - typeDesc := fmt.Sprintf("
\ntype %s %s\n
\n", typeObj.Name, variableType(t, 0, imports).full) + typeDesc := fmt.Sprintf("
\ntype %s %s\n
\n", typeObj.Name, variableType(t, 0, false, imports).markdown) return typeDesc case *ast.InterfaceType: typeName = "interface" for _, field := range t.Methods.List { - lines = append(lines, typeField(field, 0, false, imports)) + lines = append(lines, typeField(field, 0, false, imports).markdown) } if len(t.Methods.List) > 0 { lines = append(lines, "") } case *ast.StructType: typeName = "struct" + maxLength := 0 + structLines := []string{} + diffLen := []int{} for _, field := range t.Fields.List { - line := typeField(field, 0, false, imports) + info := typeField(field, 0, false, imports) + plainLen := len(strings.Split(info.plainText, "\n")[0]) + mdLen := len(strings.Split(info.markdown, "\n")[0]) + if plainLen > maxLength { + maxLength = plainLen + } + diffLen = append(diffLen, mdLen-plainLen) + structLines = append(structLines, info.markdown) + } + for idx, field := range t.Fields.List { + line := structLines[idx] if field.Tag != nil { - line = line + " " + field.Tag.Value + msg := fmt.Sprintf("%%-%ds %%s", maxLength+diffLen[idx]) + line = fmt.Sprintf(msg, line, field.Tag.Value) } lines = append(lines, line) } @@ -420,7 +275,7 @@ func varElem(imports map[string]string) func(doc.Value, string) string { varItem := spec.(*ast.ValueSpec) paramType := "" if varItem.Type != nil { - paramType = " " + variableType(varItem.Type, 0, imports).full + paramType = " " + variableType(varItem.Type, 0, false, imports).plainText } paramName := "" if len(varItem.Names) > 0 { @@ -430,7 +285,7 @@ func varElem(imports map[string]string) func(doc.Value, string) string { switch len(varItem.Values) { case 0: case 1: - value := variableType(varItem.Values[0], 0, imports).full + value := variableType(varItem.Values[0], 0, false, imports).plainText value = strings.Trim(value, paramType) switch varItem.Values[0].(type) { case *ast.ArrayType, *ast.MapType: @@ -441,7 +296,7 @@ func varElem(imports map[string]string) func(doc.Value, string) string { default: values := []string{} for _, value := range varItem.Values { - v := variableType(value, 0, imports).full + v := variableType(value, 0, false, imports).plainText switch value.(type) { case *ast.ArrayType, *ast.MapType: v = paramType + v diff --git a/pkg/mod.go b/pkg/mod.go index 4bc741b..cb5a080 100644 --- a/pkg/mod.go +++ b/pkg/mod.go @@ -57,3 +57,8 @@ func getPackageName(dir string) (string, error) { } return "", errors.New("unable to find go.mod with module name") } + +func fileExists(fname string) bool { + _, err := os.Stat(fname) + return err == nil || !os.IsNotExist(err) +} diff --git a/pkg/run.go b/pkg/run.go index b5e7953..b86993c 100644 --- a/pkg/run.go +++ b/pkg/run.go @@ -49,13 +49,33 @@ func buildImportMap(specs []*ast.ImportSpec) map[string]string { func dirImports(astPackages map[string]*ast.Package) map[string]string { mapping := map[string]string{} + stats := map[string]map[string][]string{} for _, astPkg := range astPackages { - for _, f := range astPkg.Files { - for k, v := range buildImportMap(f.Imports) { - mapping[k] = v + for fname, f := range astPkg.Files { + for alias, fq := range buildImportMap(f.Imports) { + mapping[alias] = fq + if _, ok := stats[alias]; ok { + if _, ok := stats[alias][fq]; ok { + stats[alias][fq] = append(stats[alias][fq], fname) + continue + } + stats[alias][fq] = []string{fname} + continue + } + stats[alias] = map[string][]string{fq: {fname}} } } } + for alias := range stats { + if len(stats[alias]) == 1 { + continue + } + files := []string{} + for fq, fname := range stats[alias] { + files = append(files, fmt.Sprintf("%s in %s", fq, strings.Join(fname, ", "))) + } + log.Warningf("%s has been imported as \n- %s", alias, strings.Join(files, "\n- ")) + } return mapping } @@ -66,8 +86,11 @@ func Run(out io.Writer, version string) error { if err != nil { return fmt.Errorf("unable to determine module name") } + if !fileExists("./doc.go") { + log.Warning("doc.go is missing") + } astPackages, err := parser.ParseDir(fset, ".", filter, parser.ParseComments) - if err != err { + if err != nil { return err } imports := dirImports(astPackages) diff --git a/pkg/template.md b/pkg/template.md index dc76d88..0704a9f 100644 --- a/pkg/template.md +++ b/pkg/template.md @@ -105,4 +105,4 @@ This section is empty. -- -Generated by [github.com/jylitalo/go2md](https://github.com/jylitalo/go2cmd/) v{{ version }} +Generated by [github.com/jylitalo/go2md](https://github.com/jylitalo/go2md/) v{{ version }} diff --git a/pkg/vartype.go b/pkg/vartype.go new file mode 100644 index 0000000..07b1445 --- /dev/null +++ b/pkg/vartype.go @@ -0,0 +1,179 @@ +package pkg + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + log "github.com/sirupsen/logrus" +) + +type varTypeOutput struct { + plainText string + markdown string +} + +func join(elems []varTypeOutput, sep string) varTypeOutput { + plains := []string{} + markdowns := []string{} + for _, item := range elems { + plains = append(plains, item.plainText) + markdowns = append(markdowns, item.markdown) + } + return varTypeOutput{ + plainText: strings.Join(plains, sep), + markdown: strings.Join(markdowns, sep), + } +} + +func sprintf(format string, elems ...varTypeOutput) varTypeOutput { + plainText := []any{} + markdown := []any{} + for _, elem := range elems { + plainText = append(plainText, elem.plainText) + markdown = append(markdown, elem.markdown) + } + return varTypeOutput{ + plainText: fmt.Sprintf(format, plainText...), + markdown: fmt.Sprintf(format, markdown...), + } +} + +func (vto *varTypeOutput) prefix(prefixText string) varTypeOutput { + vto.plainText = prefixText + vto.plainText + if strings.HasPrefix(vto.markdown, "`, `">`+prefixText, 1) + } else { + vto.markdown = prefixText + vto.markdown + } + return *vto +} + +func (vto *varTypeOutput) replace(old, new string, n int) { + vto.plainText = strings.Replace(vto.plainText, old, new, n) + vto.markdown = strings.Replace(vto.markdown, old, new, n) +} + +func variableType(variable ast.Expr, depth int, hyphen bool, imports map[string]string) varTypeOutput { + switch t := variable.(type) { + case nil: + return sprintf("nil") + case *ast.ArrayType: + varType := variableType(t.Elt, depth, hyphen, imports) + return varType.prefix("[]") + case *ast.BasicLit: + if t.Value != "" { + return varTypeOutput{ + plainText: t.Value, + markdown: fmt.Sprintf(`%s`, intoLink("type "+t.Value), t.Value), + } + } + switch t.Kind { + case token.INT: + return sprintf("int") + case token.FLOAT: + return sprintf("float") + case token.IMAG: + return sprintf("imag") + case token.CHAR: + return sprintf("char") + case token.STRING: + return sprintf("string") + default: + log.WithField("t.Kind", t.Kind).Panic("unknown token kind") + } + case *ast.CallExpr: + funcName := variableType(t.Fun, depth, hyphen, imports).plainText + varTypes := []varTypeOutput{} + for _, arg := range t.Args { + varTypes = append(varTypes, variableType(arg, depth, hyphen, imports)) + } + return sprintf(funcName+"(%s)", join(varTypes, ", ")) + case *ast.CompositeLit: + eltsType := variableType(t.Type, depth, hyphen, imports) + varTypes := []varTypeOutput{} + for _, elt := range t.Elts { + varTypes = append(varTypes, variableType(elt, depth, hyphen, imports)) + } + switch subType := t.Type.(type) { + case *ast.ArrayType, *ast.MapType, *ast.SelectorExpr: + vto := join(varTypes, ",\n") + vto.replace("\n", "\n"+basePrefix, -1) + msg := fmt.Sprintf("%s{\n%s%%s,\n}", eltsType.plainText, basePrefix) + return sprintf(msg, vto) + case nil: + return join(varTypes, ",\n") + default: + log.Panicf("Unknown CompositeLit: %#v", subType) + } + case *ast.Ellipsis: + return sprintf("...%s", variableType(t.Elt, depth, hyphen, imports)) + case *ast.FuncType: + vtoParams := funcParams(t.Params, imports) + vtoReturns := funcReturns(t.Results, imports) + return sprintf("func(%s)%s", vtoParams, vtoReturns) + case *ast.Ident: + switch t.Name { + case "bool", "byte", "char", "error", "float", "float32", "float64", "int", "int32", "int64", "string": + return sprintf(t.Name) + } + if exportedType.MatchString(t.Name) { + return varTypeOutput{ + plainText: t.Name, + markdown: fmt.Sprintf(`%s`, intoLink("type "+t.Name), t.Name), + } + } + log.Warningf("Internal type: %s", t.Name) + return sprintf(t.Name) + case *ast.InterfaceType: + return sprintf("interface{}") + case *ast.KeyValueExpr: + keyType := variableType(t.Key, depth, hyphen, imports) + valueType := variableType(t.Value, depth, hyphen, imports) + switch t.Value.(type) { + case *ast.CompositeLit: + switch t.Key.(type) { + case *ast.BasicLit: + valueType.replace("\n", "\n"+basePrefix, -1) + valueType = sprintf(basePrefix+"%s", valueType) + return sprintf("%s: {\n%s,\n}", keyType, valueType) + } + } + return sprintf("%s: %s", keyType, valueType) + case *ast.MapType: + keyType := variableType(t.Key, depth, hyphen, imports) + valueType := variableType(t.Value, depth, hyphen, imports) + return sprintf("map[%s]%s", keyType, valueType) + case *ast.SelectorExpr: + msg := fmt.Sprintf("%s.%s", t.X, t.Sel) + return varTypeOutput{plainText: msg, markdown: intoImportLink(msg, imports)} + case *ast.StarExpr: + vto := variableType(t.X, depth, hyphen, imports) + return vto.prefix("*") + case *ast.StructType: + varTypes := []varTypeOutput{} + for _, field := range t.Fields.List { + varTypes = append(varTypes, typeField(field, depth+1, hyphen, imports)) + } + vto := join(varTypes, "\n") + if hyphen { + return sprintf("struct\n%s", vto) + } + return sprintf("struct {\n%s\n}", vto) + case *ast.UnaryExpr: + switch t.Op { + case token.AND: + vto := variableType(t.X, depth, hyphen, imports) + return vto.prefix("&") + default: + log.Panicf("unknown unary type %d for %s", t.Op, t.X) + } + default: + log.WithFields(log.Fields{ + "variable.(type)": fmt.Sprintf("%#v", variable)}, + ).Panicf("unknown variable type") + } + log.WithField("variableType", fmt.Sprintf("%#v", variable)) + return varTypeOutput{} +} diff --git a/version.txt b/version.txt index 3b04cfb..be58634 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.2 +0.3