1 package main
2
3 import (
4 "bytes"
5 "fmt"
6 "go/ast"
7 "go/build"
8 "go/doc"
9 "go/parser"
10 "go/printer"
11 "go/token"
12 "os"
13 "path/filepath"
14 "strconv"
15 "strings"
16 )
17
18
19
20 func parseSourceFlag(sourceFlag string) (importPath, variableName string, err error) {
21
22
23
24
25 e, err := parser.ParseExpr(sourceFlag)
26 if err != nil {
27 return "", "", fmt.Errorf("invalid format, failed to parse %q as a Go expression", sourceFlag)
28 }
29 se, ok := e.(*ast.SelectorExpr)
30 if !ok {
31 return "", "", fmt.Errorf("invalid format, expression %v is not a selector expression but %T", sourceFlag, e)
32 }
33 importPath, err = stringValue(se.X)
34 if err != nil {
35 return "", "", fmt.Errorf("invalid format, expression %v is not a properly quoted Go string: %v", stringifyAST(se.X), err)
36 }
37 if build.IsLocalImport(importPath) {
38
39
40 return "", "", fmt.Errorf("relative import paths are not supported")
41 }
42 variableName = se.Sel.Name
43 return importPath, variableName, nil
44 }
45
46
47 func stringValue(e ast.Expr) (string, error) {
48 lit, ok := e.(*ast.BasicLit)
49 if !ok {
50 return "", fmt.Errorf("not a string, but %T", e)
51 }
52 if lit.Kind != token.STRING {
53 return "", fmt.Errorf("not a string, but %v", lit.Kind)
54 }
55 return strconv.Unquote(lit.Value)
56 }
57
58
59 func parseTagFlag(tagFlag string) (tag string, err error) {
60 tags := strings.Fields(tagFlag)
61 if len(tags) != 1 {
62 return "", fmt.Errorf("%q is not a valid single build tag, but %q", tagFlag, tags)
63 }
64 return tags[0], nil
65 }
66
67
68
69 func lookupNameAndComment(bctx build.Context, importPath, variableName string) (packageName, variableComment string, err error) {
70 wd, err := os.Getwd()
71 if err != nil {
72 return "", "", err
73 }
74 bpkg, err := bctx.Import(importPath, wd, 0)
75 if err != nil {
76 return "", "", fmt.Errorf("can't import package %q: %v", importPath, err)
77 }
78 dpkg, err := computeDoc(bpkg)
79 if err != nil {
80 return "", "", fmt.Errorf("can't get godoc of package %q: %v", importPath, err)
81 }
82 for _, v := range dpkg.Vars {
83 if len(v.Names) == 1 && v.Names[0] == variableName {
84 variableComment = strings.TrimSuffix(v.Doc, "\n")
85 break
86 }
87 }
88 return bpkg.Name, variableComment, nil
89 }
90
91 func stringifyAST(node interface{}) string {
92 var buf bytes.Buffer
93 err := printer.Fprint(&buf, token.NewFileSet(), node)
94 if err != nil {
95 return "printer.Fprint error: " + err.Error()
96 }
97 return buf.String()
98 }
99
100
101 func computeDoc(bpkg *build.Package) (*doc.Package, error) {
102 fset := token.NewFileSet()
103 files := make(map[string]*ast.File)
104 for _, file := range append(bpkg.GoFiles, bpkg.CgoFiles...) {
105 f, err := parser.ParseFile(fset, filepath.Join(bpkg.Dir, file), nil, parser.ParseComments)
106 if err != nil {
107 return nil, err
108 }
109 files[file] = f
110 }
111 apkg := &ast.Package{
112 Name: bpkg.Name,
113 Files: files,
114 }
115 return doc.New(apkg, bpkg.ImportPath, 0), nil
116 }
117
View as plain text