diff --git a/g_docs.go b/g_docs.go index a92c917..6f78329 100644 --- a/g_docs.go +++ b/g_docs.go @@ -33,6 +33,8 @@ import ( "gopkg.in/yaml.v2" + "io/ioutil" + "github.com/astaxie/beego/swagger" "github.com/astaxie/beego/utils" ) @@ -50,6 +52,30 @@ var importlist map[string]string var controllerList map[string]map[string]*swagger.Item //controllername Paths items var modelsList map[string]map[string]swagger.Schema var rootapi swagger.Swagger +var astPkgs map[string]*ast.Package + +// refer to builtin.go +var basicTypes = map[string]string{ + "bool": "boolean:", + "uint": "integer:int32", + "uint8": "integer:int32", + "uint16": "integer:int32", + "uint32": "integer:int32", + "uint64": "integer:int64", + "int": "integer:int64", + "int8": "integer:int32", + "int16:int32": "integer:int32", + "int32": "integer:int32", + "int64": "integer:int64", + "uintptr": "integer:int64", + "float32": "number:float", + "float64": "number:double", + "string": "string:", + "complex64": "number:float", + "complex128": "number:double", + "byte": "string:byte", + "rune": "string:byte", +} func init() { pkgCache = make(map[string]struct{}) @@ -57,6 +83,39 @@ func init() { importlist = make(map[string]string) controllerList = make(map[string]map[string]*swagger.Item) modelsList = make(map[string]map[string]swagger.Schema) + curPath, _ := os.Getwd() + astPkgs = map[string]*ast.Package{} + parsePackagesFromDir(curPath) +} + +func parsePackagesFromDir(path string) { + parsePackageFromDir(path) + list, err := ioutil.ReadDir(path) + if err != nil { + ColorLog("[ERRO] Can't read directory %s : %s\n", path, err) + os.Exit(1) + } + for _, item := range list { + if item.IsDir() && item.Name() != "vendor" { + parsePackagesFromDir(path + "/" + item.Name()) + } + } +} + +func parsePackageFromDir(path string) { + fileSet := token.NewFileSet() + folderPkgs, err := parser.ParseDir(fileSet, path, func(info os.FileInfo) bool { + name := info.Name() + return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") + }, parser.ParseComments) + + if err != nil { + ColorLog("[ERRO] the model %s parser.ParseDir error\n", path) + os.Exit(1) + } + for k, v := range folderPkgs { + astPkgs[k] = v + } } func generateDocs(curpath string) { @@ -414,13 +473,13 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat schema.Type = typeFormat[0] schema.Format = typeFormat[1] } else { - cmpath, m, mod, realTypes := getModel(schemaName) + m, mod, realTypes := getModel(schemaName) schema.Ref = "#/definitions/" + m if _, ok := modelsList[pkgpath+controllerName]; !ok { modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0) } modelsList[pkgpath+controllerName][schemaName] = mod - appendModels(cmpath, pkgpath, controllerName, realTypes) + appendModels(pkgpath, controllerName, realTypes) } if isArray { rs.Schema = &swagger.Schema{ @@ -460,7 +519,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat pp := strings.Split(p[2], ".") typ := pp[len(pp)-1] if len(pp) >= 2 { - cmpath, m, mod, realTypes := getModel(p[2]) + m, mod, realTypes := getModel(p[2]) para.Schema = &swagger.Schema{ Ref: "#/definitions/" + m, } @@ -468,7 +527,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0) } modelsList[pkgpath+controllerName][typ] = mod - appendModels(cmpath, pkgpath, controllerName, realTypes) + appendModels(pkgpath, controllerName, realTypes) } else { isArray := false paraType := "" @@ -610,22 +669,10 @@ func getparams(str string) []string { return r } -func getModel(str string) (pkgpath, objectname string, m swagger.Schema, realTypes []string) { +func getModel(str string) (objectname string, m swagger.Schema, realTypes []string) { strs := strings.Split(str, ".") objectname = strs[len(strs)-1] - pkgpath = strings.Join(strs[:len(strs)-1], "/") - curpath, _ := os.Getwd() - pkgRealpath := path.Join(curpath, pkgpath) - fileSet := token.NewFileSet() - astPkgs, err := parser.ParseDir(fileSet, pkgRealpath, func(info os.FileInfo) bool { - name := info.Name() - return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") - }, parser.ParseComments) - - if err != nil { - ColorLog("[ERRO] the model %s parser.ParseDir error\n", str) - os.Exit(1) - } + packageName := "" m.Type = "object" for _, pkg := range astPkgs { for _, fl := range pkg.Files { @@ -634,7 +681,8 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Schema, realTyp if k != objectname { continue } - parseObject(d, k, &m, &realTypes, astPkgs) + packageName = pkg.Name + parseObject(d, k, &m, &realTypes, astPkgs, pkg.Name) } } } @@ -647,11 +695,12 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Schema, realTyp if len(rootapi.Definitions) == 0 { rootapi.Definitions = make(map[string]swagger.Schema) } + objectname = packageName + "." + objectname rootapi.Definitions[objectname] = m return } -func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string, astPkgs map[string]*ast.Package) { +func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string, astPkgs map[string]*ast.Package, packageName string) { ts, ok := d.Decl.(*ast.TypeSpec) if !ok { ColorLog("Unknown type without TypeSec: %v\n", d) @@ -666,7 +715,18 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string if st.Fields.List != nil { m.Properties = make(map[string]swagger.Propertie) for _, field := range st.Fields.List { + realType := "" isSlice, realType, sType := typeAnalyser(field) + if (isSlice && isBasicType(realType)) || sType == "object" { + if len(strings.Split(realType, " ")) > 1 { + realType = strings.Replace(realType, " ", ".", -1) + realType = strings.Replace(realType, "&", "", -1) + realType = strings.Replace(realType, "{", "", -1) + realType = strings.Replace(realType, "}", "", -1) + } else { + realType = packageName + "." + realType + } + } *realTypes = append(*realTypes, realType) mp := swagger.Propertie{} if isSlice { @@ -709,7 +769,42 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string } var tagValues []string + var err error + stag := reflect.StructTag(strings.Trim(field.Tag.Value, "`")) + + defaultValue := stag.Get("doc") + if defaultValue != ""{ + r, _ := regexp.Compile(`default\((.*)\)`) + if r.MatchString(defaultValue) { + res := r.FindStringSubmatch(defaultValue) + mp.Default = res[1] + switch realType{ + case "int","int64", "int32", "int16", "int8": + if mp.Default, err = strconv.Atoi(res[1]); err != nil{ + ColorLog("[WARN] Invalid default value type(%s): %s\n",realType, res[1]) + } + + case "bool": + if mp.Default, err = strconv.ParseBool(res[1]); err != nil{ + ColorLog("[WARN] Invalid default value type(%s): %s\n",realType, res[1]) + } + case "float64": + if mp.Default, err = strconv.ParseFloat(res[1], 64); err != nil{ + ColorLog("[WARN] Invalid default value type(%s): %s\n",realType, res[1]) + } + case "float32": + if mp.Default, err = strconv.ParseFloat(res[1], 32); err != nil{ + ColorLog("[WARN] Invalid default value type(%s): %s\n",realType, res[1]) + } + default: + mp.Default = res[1] + } + }else{ + ColorLog("[WARN] Invalid default value: %s\n", defaultValue) + } + } + tag := stag.Get("json") if tag != "" { @@ -747,7 +842,7 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string for _, fl := range pkg.Files { for nameOfObj, obj := range fl.Scope.Objects { if obj.Name == fmt.Sprint(field.Type) { - parseObject(obj, nameOfObj, m, realTypes, astPkgs) + parseObject(obj, nameOfObj, m, realTypes, astPkgs, pkg.Name) } } } @@ -793,18 +888,6 @@ func isBasicType(Type string) bool { return false } -// refer to builtin.go -var basicTypes = map[string]string{ - "bool": "boolean:", - "uint": "integer:int32", "uint8": "integer:int32", "uint16": "integer:int32", "uint32": "integer:int32", "uint64": "integer:int64", - "int": "integer:int64", "int8": "integer:int32", "int16:int32": "integer:int32", "int32": "integer:int32", "int64": "integer:int64", - "uintptr": "integer:int64", - "float32": "number:float", "float64": "number:double", - "string": "string:", - "complex64": "number:float", "complex128": "number:double", - "byte": "string:byte", "rune": "string:byte", -} - // regexp get json tag func grepJSONTag(tag string) string { r, _ := regexp.Compile(`json:"([^"]*)"`) @@ -816,23 +899,16 @@ func grepJSONTag(tag string) string { } // append models -func appendModels(cmpath, pkgpath, controllerName string, realTypes []string) { - var p string - if cmpath != "" { - p = strings.Join(strings.Split(cmpath, "/"), ".") + "." - } else { - p = "" - } +func appendModels(pkgpath, controllerName string, realTypes []string) { for _, realType := range realTypes { if realType != "" && !isBasicType(strings.TrimLeft(realType, "[]")) && !strings.HasPrefix(realType, "map") && !strings.HasPrefix(realType, "&") { - if _, ok := modelsList[pkgpath+controllerName][p+realType]; ok { + if _, ok := modelsList[pkgpath+controllerName][realType]; ok { continue } - //fmt.Printf(pkgpath + ":" + controllerName + ":" + cmpath + ":" + realType + "\n") - _, _, mod, newRealTypes := getModel(p + realType) - modelsList[pkgpath+controllerName][p+realType] = mod - appendModels(cmpath, pkgpath, controllerName, newRealTypes) + _, mod, newRealTypes := getModel(realType) + modelsList[pkgpath+controllerName][realType] = mod + appendModels(pkgpath, controllerName, newRealTypes) } } } diff --git a/pack.go b/pack.go index bdf3581..d6d84e4 100644 --- a/pack.go +++ b/pack.go @@ -21,7 +21,6 @@ import ( "flag" "fmt" "io" - "io/ioutil" "os" "os/exec" path "path/filepath" @@ -454,24 +453,6 @@ func packDirectory(excludePrefix []string, excludeSuffix []string, return } -func isBeegoProject(thePath string) bool { - fh, _ := os.Open(thePath) - fis, _ := fh.Readdir(-1) - regex := regexp.MustCompile(`(?s)package main.*?import.*?\(.*?github.com/astaxie/beego".*?\).*func main()`) - for _, fi := range fis { - if fi.IsDir() == false && strings.HasSuffix(fi.Name(), ".go") { - data, err := ioutil.ReadFile(path.Join(thePath, fi.Name())) - if err != nil { - continue - } - if len(regex.Find(data)) > 0 { - return true - } - } - } - return false -} - func packApp(cmd *Command, args []string) int { ShowShortVersionBanner() diff --git a/run.go b/run.go index 8aab6b5..b84e199 100644 --- a/run.go +++ b/run.go @@ -87,7 +87,7 @@ func runApp(cmd *Command, args []string) int { currentGoPath = _gopath appname = path.Base(currpath) } else { - panic(fmt.Sprintf("No Beego application '%s' found in your GOPATH", args[0])) + exitPrint(fmt.Sprintf("No Beego application '%s' found in your GOPATH", args[0])) } ColorLog("[INFO] Using '%s' as 'appname'\n", appname) @@ -103,13 +103,13 @@ func runApp(cmd *Command, args []string) int { Debugf("current path:%s\n", currpath) - if runmode == "prod" || runmode == "dev"{ + if runmode == "prod" || runmode == "dev" { os.Setenv("BEEGO_RUNMODE", runmode) ColorLog("[INFO] Using '%s' as 'runmode'\n", os.Getenv("BEEGO_RUNMODE")) - }else if runmode != ""{ + } else if runmode != "" { os.Setenv("BEEGO_RUNMODE", runmode) ColorLog("[WARN] Using '%s' as 'runmode'\n", os.Getenv("BEEGO_RUNMODE")) - }else if os.Getenv("BEEGO_RUNMODE") != ""{ + } else if os.Getenv("BEEGO_RUNMODE") != "" { ColorLog("[WARN] Using '%s' as 'runmode'\n", os.Getenv("BEEGO_RUNMODE")) } diff --git a/util.go b/util.go index fd2e1ce..32db2f5 100644 --- a/util.go +++ b/util.go @@ -15,14 +15,16 @@ package main import ( + "fmt" + "io/ioutil" "log" "os" + "path" "path/filepath" + "regexp" "runtime" "strings" "time" - "path" - "fmt" ) // Go is a basic promise implementation: it wraps calls a function in a goroutine @@ -35,7 +37,7 @@ func Go(f func() error) chan error { return ch } -// if os.env DEBUG set, debug is on +// Debugf outputs a formtted debug message, when os.env DEBUG is set. func Debugf(format string, a ...interface{}) { if os.Getenv("DEBUG") != "" { _, file, line, ok := runtime.Caller(1) @@ -174,6 +176,37 @@ func GetGOPATHs() []string { return paths } +func isBeegoProject(thePath string) bool { + mainFiles := []string{} + hasBeegoRegex := regexp.MustCompile(`(?s)package main.*?import.*?\(.*?github.com/astaxie/beego".*?\).*func main()`) + // Walk the application path tree to look for main files. + // Main files must satisfy the 'hasBeegoRegex' regular expression. + err := filepath.Walk(thePath, func(fpath string, f os.FileInfo, err error) error { + if !f.IsDir() { // Skip sub-directories + data, _err := ioutil.ReadFile(fpath) + if _err != nil { + return _err + } + if len(hasBeegoRegex.Find(data)) > 0 { + mainFiles = append(mainFiles, fpath) + } + } + return nil + }) + + if err != nil { + log.Fatalf("Unable to walk '%s' tree: %v", thePath, err) + return false + } + + if len(mainFiles) > 0 { + return true + } + return false +} + +// SearchGOPATHs searchs the user GOPATH(s) for the specified application name. +// It returns a boolean, the application's GOPATH and its full path. func SearchGOPATHs(app string) (bool, string, string) { gps := GetGOPATHs() if len(gps) == 0 { diff --git a/watch.go b/watch.go index efde48a..1c8f593 100644 --- a/watch.go +++ b/watch.go @@ -240,9 +240,8 @@ func shouldIgnoreFile(filename string) bool { } if r.MatchString(filename) { return true - } else { - continue } + continue } return false }