Boilerplate for how I like to write a backend web service.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

116 lines
3.4 KiB

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
}