117 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			117 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/build"
 | |
| 	"go/doc"
 | |
| 	"go/parser"
 | |
| 	"go/printer"
 | |
| 	"go/token"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // parseSourceFlag parses the "-source" flag value. It must have "import/path".VariableName format.
 | |
| // It returns an error if the parsed import path is relative.
 | |
| func parseSourceFlag(sourceFlag string) (importPath, variableName string, err error) {
 | |
| 	// Parse sourceFlag as a Go expression, albeit a strange one:
 | |
| 	//
 | |
| 	// 	"import/path".VariableName
 | |
| 	//
 | |
| 	e, err := parser.ParseExpr(sourceFlag)
 | |
| 	if err != nil {
 | |
| 		return "", "", fmt.Errorf("invalid format, failed to parse %q as a Go expression", sourceFlag)
 | |
| 	}
 | |
| 	se, ok := e.(*ast.SelectorExpr)
 | |
| 	if !ok {
 | |
| 		return "", "", fmt.Errorf("invalid format, expression %v is not a selector expression but %T", sourceFlag, e)
 | |
| 	}
 | |
| 	importPath, err = stringValue(se.X)
 | |
| 	if err != nil {
 | |
| 		return "", "", fmt.Errorf("invalid format, expression %v is not a properly quoted Go string: %v", stringifyAST(se.X), err)
 | |
| 	}
 | |
| 	if build.IsLocalImport(importPath) {
 | |
| 		// Generated code is executed in a temporary directory,
 | |
| 		// and can't use relative import paths. So disallow them.
 | |
| 		return "", "", fmt.Errorf("relative import paths are not supported")
 | |
| 	}
 | |
| 	variableName = se.Sel.Name
 | |
| 	return importPath, variableName, nil
 | |
| }
 | |
| 
 | |
| // stringValue returns the string value of string literal e.
 | |
| func stringValue(e ast.Expr) (string, error) {
 | |
| 	lit, ok := e.(*ast.BasicLit)
 | |
| 	if !ok {
 | |
| 		return "", fmt.Errorf("not a string, but %T", e)
 | |
| 	}
 | |
| 	if lit.Kind != token.STRING {
 | |
| 		return "", fmt.Errorf("not a string, but %v", lit.Kind)
 | |
| 	}
 | |
| 	return strconv.Unquote(lit.Value)
 | |
| }
 | |
| 
 | |
| // parseTagFlag parses the "-tag" flag value. It must be a single build tag.
 | |
| func parseTagFlag(tagFlag string) (tag string, err error) {
 | |
| 	tags := strings.Fields(tagFlag)
 | |
| 	if len(tags) != 1 {
 | |
| 		return "", fmt.Errorf("%q is not a valid single build tag, but %q", tagFlag, tags)
 | |
| 	}
 | |
| 	return tags[0], nil
 | |
| }
 | |
| 
 | |
| // lookupNameAndComment imports package using provided build context, and
 | |
| // returns the package name and variable comment.
 | |
| func lookupNameAndComment(bctx build.Context, importPath, variableName string) (packageName, variableComment string, err error) {
 | |
| 	wd, err := os.Getwd()
 | |
| 	if err != nil {
 | |
| 		return "", "", err
 | |
| 	}
 | |
| 	bpkg, err := bctx.Import(importPath, wd, 0)
 | |
| 	if err != nil {
 | |
| 		return "", "", fmt.Errorf("can't import package %q: %v", importPath, err)
 | |
| 	}
 | |
| 	dpkg, err := computeDoc(bpkg)
 | |
| 	if err != nil {
 | |
| 		return "", "", fmt.Errorf("can't get godoc of package %q: %v", importPath, err)
 | |
| 	}
 | |
| 	for _, v := range dpkg.Vars {
 | |
| 		if len(v.Names) == 1 && v.Names[0] == variableName {
 | |
| 			variableComment = strings.TrimSuffix(v.Doc, "\n")
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return bpkg.Name, variableComment, nil
 | |
| }
 | |
| 
 | |
| func stringifyAST(node interface{}) string {
 | |
| 	var buf bytes.Buffer
 | |
| 	err := printer.Fprint(&buf, token.NewFileSet(), node)
 | |
| 	if err != nil {
 | |
| 		return "printer.Fprint error: " + err.Error()
 | |
| 	}
 | |
| 	return buf.String()
 | |
| }
 | |
| 
 | |
| // computeDoc computes the package documentation for the given package.
 | |
| func computeDoc(bpkg *build.Package) (*doc.Package, error) {
 | |
| 	fset := token.NewFileSet()
 | |
| 	files := make(map[string]*ast.File)
 | |
| 	for _, file := range append(bpkg.GoFiles, bpkg.CgoFiles...) {
 | |
| 		f, err := parser.ParseFile(fset, filepath.Join(bpkg.Dir, file), nil, parser.ParseComments)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		files[file] = f
 | |
| 	}
 | |
| 	apkg := &ast.Package{
 | |
| 		Name:  bpkg.Name,
 | |
| 		Files: files,
 | |
| 	}
 | |
| 	return doc.New(apkg, bpkg.ImportPath, 0), nil
 | |
| }
 |