Skip to content

Commit

Permalink
refactor the project
Browse files Browse the repository at this point in the history
  • Loading branch information
jirfag committed Mar 16, 2019
1 parent d75411b commit 2fdafde
Show file tree
Hide file tree
Showing 33 changed files with 326 additions and 309 deletions.
13 changes: 0 additions & 13 deletions .codeclimate.yml

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/go-queryset
/vendor
/internal/parser/test/tmptestdir*/
37 changes: 37 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
run:
skip-dirs:
- ^internal/parser/test/tmptestdir.*

linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
gocyclo:
min-complexity: 15
goconst:
min-len: 2
min-occurrences: 2
lll:
line-length: 140

linters:
enable-all: true
disable:
- scopelint
- gochecknoglobals
- gosec

issues:
exclude-rules:
- linters:
- unparam
text: always receives
- linters:
- staticcheck
text: "SA9003:"

# golangci.com configuration
# https://github.com/golangci/golangci/wiki/Configuration
# service:
# golangci-lint-version: 1.14.0 # use fixed version to not introduce new linters unexpectedly
11 changes: 4 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
language: go
go:
- 1.8.x
- 1.9.x
- 1.11.x
- 1.12.x
before_install:
- go get github.com/mattn/goveralls
- go get github.com/alecthomas/gometalinter
- go get github.com/golang/dep/cmd/dep
- dep ensure -v
- gometalinter --install --vendored-linters
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.15.0
script:
- make test
- $HOME/gopath/bin/goveralls -ignore "queryset/test/autogenerated_models.go,examples/comparison/*/*.go,queryset/test/pkgimport/*.go,queryset/test/pkgimport/*/*/*.go" -v -service=travis-ci
- $HOME/gopath/bin/goveralls -ignore "internal/queryset/generator/test/autogenerated_models.go,examples/comparison/*/*.go,internal/queryset/generator/test/pkgimport/*.go,internal/queryset/generator/test/pkgimport/*/*/*.go" -v -service=travis-ci
20 changes: 5 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
test_static:
go install ./...
gometalinter --vendor --enable-all --min-confidence=0.3 --line-length=120 \
-e "parameter \w+ always receives" \
-e "/jinzhu/gorm/" \
-e "model is unused" \
-e '"expections" is a misspelling of "exceptions"' \
-e "autogenerated_" \
-e "should have comment" \
-e "examples/comparison/" \
--deadline=5m \
./...
golangci-lint run

test_unit: test_gen
mkdir -p test
go test -v ./parser/ ./queryset/ ./queryset/methods/
go test -v ./...

AUTOGEN_FILES = \
./queryset/test/autogenerated_models.go \
./internal/queryset/generator/test/autogenerated_models.go \
./examples/comparison/gorm4/autogenerated_gorm4.go \
./queryset/test/pkgimport/autogenerated_models.go
./internal/queryset/generator/test/pkgimport/autogenerated_models.go

test_gen: gen
@- $(foreach F,$(AUTOGEN_FILES), \
Expand All @@ -28,7 +18,7 @@ test_gen: gen
test: test_unit bench test_static

bench:
go test -bench=. -benchtime=1s -v -run=^$$ ./queryset/
go test -bench=. -benchtime=1s -v -run=^$$ ./internal/queryset/generator/

gen:
@- $(foreach F,$(AUTOGEN_FILES), \
Expand Down
26 changes: 21 additions & 5 deletions cmd/goqueryset/goqueryset.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
package main

import (
"context"
"flag"
"log"
"strings"
"path/filepath"
"time"

"github.com/jirfag/go-queryset/queryset"
"github.com/jirfag/go-queryset/internal/parser"
"github.com/jirfag/go-queryset/internal/queryset/generator"
)

func main() {
const defaultOutPath = "autogenerated_{in}"

inFile := flag.String("in", "models.go", "path to input file")
outFile := flag.String("out", "autogenerated_{in}", "path to output file")
outFile := flag.String("out", defaultOutPath, "path to output file")
timeout := flag.Duration("timeout", time.Minute, "timeout for generation")
flag.Parse()

*outFile = strings.Replace(*outFile, "{in}", *inFile, 1)
if err := queryset.GenerateQuerySets(*inFile, *outFile); err != nil {
if *outFile == defaultOutPath {
*outFile = filepath.Join(filepath.Dir(*inFile), "autogenerated_"+filepath.Base(*inFile))
}

g := generator.Generator{
StructsParser: &parser.Structs{},
}

ctx, finish := context.WithTimeout(context.Background(), *timeout)
defer finish()

if err := g.Generate(ctx, *inFile, *outFile); err != nil {
log.Fatalf("can't generate query sets: %s", err)
}
}
158 changes: 71 additions & 87 deletions parser/parser.go → internal/parser/parser.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package parser

import (
"context"
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"os"
"path/filepath"
"reflect"
"strings"

"golang.org/x/tools/go/loader"
"github.com/pkg/errors"

"golang.org/x/tools/go/packages"
)

// StructField represents one field in struct
Expand All @@ -33,57 +35,86 @@ func (sf StructField) Tag() reflect.StructTag {
return sf.tag
}

// ParsedStructs is a map from struct type name to list of fields
type ParsedStructs map[string]ParsedStruct

// ParsedStruct represents struct info
type ParsedStruct struct {
TypeName string
Fields []StructField
Doc *ast.CommentGroup // line comments; or nil
}

func fileNameToPkgName(filePath, absFilePath string) string {
dir := filepath.Dir(absFilePath)
gopathEnv := os.Getenv("GOPATH")
gopaths := strings.Split(gopathEnv, string(os.PathListSeparator))
var inGoPath string
for _, gopath := range gopaths {
if strings.HasPrefix(dir, gopath) {
inGoPath = gopath
break
}
}
if inGoPath == "" {
// not in GOPATH
return "./" + filepath.Dir(filePath)
}
r := strings.TrimPrefix(dir, inGoPath)
r = strings.TrimPrefix(r, "/") // may be and may not be
r = strings.TrimPrefix(r, "\\") // may be and may not be
r = strings.TrimPrefix(r, "src/")
r = strings.TrimPrefix(r, "src\\")
return r
type Result struct {
Structs map[string]ParsedStruct
PackageName string
Types *types.Package
}

func typeCheckFuncBodies(path string) bool {
return false // don't type-check func bodies to speedup parsing
}
type Structs struct{}

func loadProgramFromPackage(pkgFullName string) (*loader.Program, error) {
// The loader loads a complete Go program from source code.
conf := loader.Config{
ParserMode: parser.ParseComments,
TypeCheckFuncBodies: typeCheckFuncBodies,
func (p Structs) ParseFile(ctx context.Context, filePath string) (*Result, error) {
absFilePath, err := filepath.Abs(filePath)
if err != nil {
return nil, errors.Wrapf(err, "can't get abs path for %s", filePath)
}
conf.Import(pkgFullName)
lprog, err := conf.Load()

neededStructs, err := p.getStructNamesInFile(absFilePath)
if err != nil {
return nil, fmt.Errorf("can't load program from package %q: %s",
pkgFullName, err)
return nil, errors.Wrap(err, "can't get struct names")
}

// need load the full package type info because
// some deps can be in other files
inPkgName := filepath.Dir(filePath)
if !filepath.IsAbs(inPkgName) && !strings.HasPrefix(inPkgName, ".") {
// to make this dir name a local package name
// can't use filepath.Join because it calls Clean and removes "."+sep
inPkgName = fmt.Sprintf(".%c%s", filepath.Separator, inPkgName)
}

pkgs, err := packages.Load(&packages.Config{
Mode: packages.LoadAllSyntax,
Context: ctx,
Tests: false,
}, inPkgName)
if err != nil {
return nil, errors.Wrapf(err, "failed to load package for file %s", filePath)
}

if len(pkgs) != 1 {
return nil, fmt.Errorf("got too many (%d) packages: %#v", len(pkgs), pkgs)
}

structs := p.buildParsedStructs(pkgs[0], neededStructs)
return &Result{
Structs: structs,
PackageName: pkgs[0].Name,
Types: pkgs[0].Types,
}, nil
}

func (p Structs) buildParsedStructs(pkg *packages.Package, neededStructs structNamesInfo) map[string]ParsedStruct {
ret := map[string]ParsedStruct{}

scope := pkg.Types.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)

if neededStructs[name] == nil {
continue
}

t := obj.Type().(*types.Named)
s := t.Underlying().(*types.Struct)

parsedStruct := parseStruct(s, neededStructs[name])
if parsedStruct != nil {
parsedStruct.TypeName = name
ret[name] = *parsedStruct
} else {
// TODO
}
}

return lprog, nil
return ret
}

type structNamesInfo map[string]*ast.GenDecl
Expand All @@ -106,7 +137,7 @@ func (v *structNamesVisitor) Visit(n ast.Node) (w ast.Visitor) {
return v
}

func getStructNamesInFile(fname string) (structNamesInfo, error) {
func (p Structs) getStructNamesInFile(fname string) (structNamesInfo, error) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, fname, nil, parser.ParseComments)
if err != nil {
Expand All @@ -120,53 +151,6 @@ func getStructNamesInFile(fname string) (structNamesInfo, error) {
return v.names, nil
}

// GetStructsInFile lists all structures in file passed and returns them with all fields
func GetStructsInFile(filePath string) (*loader.PackageInfo, ParsedStructs, error) {
absFilePath, err := filepath.Abs(filePath)
if err != nil {
return nil, nil, fmt.Errorf("can't get abs path for %s", filePath)
}

neededStructs, err := getStructNamesInFile(absFilePath)
if err != nil {
return nil, nil, fmt.Errorf("can't get struct names: %s", err)
}

packageFullName := fileNameToPkgName(filePath, absFilePath)
lprog, err := loadProgramFromPackage(packageFullName)
if err != nil {
return nil, nil, err
}

pkgInfo := lprog.Package(packageFullName)
if pkgInfo == nil {
return nil, nil, fmt.Errorf("can't load types for file %s in package %q",
filePath, packageFullName)
}

ret := ParsedStructs{}

scope := pkgInfo.Pkg.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)

if neededStructs[name] == nil {
continue
}

t := obj.Type().(*types.Named)
s := t.Underlying().(*types.Struct)

parsedStruct := parseStruct(s, neededStructs[name])
if parsedStruct != nil {
parsedStruct.TypeName = name
ret[name] = *parsedStruct
}
}

return pkgInfo, ret, nil
}

func newStructField(f *types.Var, tag string) *StructField {
return &StructField{
name: f.Name(),
Expand Down
Loading

0 comments on commit 2fdafde

Please sign in to comment.