...

Source file src/github.com/shurcooL/vfsgen/cmd/vfsgendev/parse.go

Documentation: github.com/shurcooL/vfsgen/cmd/vfsgendev

     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  // parseSourceFlag parses the "-source" flag value. It must have "import/path".VariableName format.
    19  // It returns an error if the parsed import path is relative.
    20  func parseSourceFlag(sourceFlag string) (importPath, variableName string, err error) {
    21  	// Parse sourceFlag as a Go expression, albeit a strange one:
    22  	//
    23  	// 	"import/path".VariableName
    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  		// Generated code is executed in a temporary directory,
    39  		// and can't use relative import paths. So disallow them.
    40  		return "", "", fmt.Errorf("relative import paths are not supported")
    41  	}
    42  	variableName = se.Sel.Name
    43  	return importPath, variableName, nil
    44  }
    45  
    46  // stringValue returns the string value of string literal e.
    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  // parseTagFlag parses the "-tag" flag value. It must be a single build tag.
    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  // lookupNameAndComment imports package using provided build context, and
    68  // returns the package name and variable comment.
    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  // computeDoc computes the package documentation for the given package.
   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