mirror of
https://github.com/beego/bee.git
synced 2024-11-23 11:50:55 +00:00
Merge remote-tracking branch 'refs/remotes/origin/develop'
This commit is contained in:
commit
ac3071f7a3
166
g_docs.go
166
g_docs.go
@ -33,6 +33,8 @@ import (
|
|||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/astaxie/beego/swagger"
|
"github.com/astaxie/beego/swagger"
|
||||||
"github.com/astaxie/beego/utils"
|
"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 controllerList map[string]map[string]*swagger.Item //controllername Paths items
|
||||||
var modelsList map[string]map[string]swagger.Schema
|
var modelsList map[string]map[string]swagger.Schema
|
||||||
var rootapi swagger.Swagger
|
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() {
|
func init() {
|
||||||
pkgCache = make(map[string]struct{})
|
pkgCache = make(map[string]struct{})
|
||||||
@ -57,6 +83,39 @@ func init() {
|
|||||||
importlist = make(map[string]string)
|
importlist = make(map[string]string)
|
||||||
controllerList = make(map[string]map[string]*swagger.Item)
|
controllerList = make(map[string]map[string]*swagger.Item)
|
||||||
modelsList = make(map[string]map[string]swagger.Schema)
|
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) {
|
func generateDocs(curpath string) {
|
||||||
@ -414,13 +473,13 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
|
|||||||
schema.Type = typeFormat[0]
|
schema.Type = typeFormat[0]
|
||||||
schema.Format = typeFormat[1]
|
schema.Format = typeFormat[1]
|
||||||
} else {
|
} else {
|
||||||
cmpath, m, mod, realTypes := getModel(schemaName)
|
m, mod, realTypes := getModel(schemaName)
|
||||||
schema.Ref = "#/definitions/" + m
|
schema.Ref = "#/definitions/" + m
|
||||||
if _, ok := modelsList[pkgpath+controllerName]; !ok {
|
if _, ok := modelsList[pkgpath+controllerName]; !ok {
|
||||||
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0)
|
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0)
|
||||||
}
|
}
|
||||||
modelsList[pkgpath+controllerName][schemaName] = mod
|
modelsList[pkgpath+controllerName][schemaName] = mod
|
||||||
appendModels(cmpath, pkgpath, controllerName, realTypes)
|
appendModels(pkgpath, controllerName, realTypes)
|
||||||
}
|
}
|
||||||
if isArray {
|
if isArray {
|
||||||
rs.Schema = &swagger.Schema{
|
rs.Schema = &swagger.Schema{
|
||||||
@ -460,7 +519,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
|
|||||||
pp := strings.Split(p[2], ".")
|
pp := strings.Split(p[2], ".")
|
||||||
typ := pp[len(pp)-1]
|
typ := pp[len(pp)-1]
|
||||||
if len(pp) >= 2 {
|
if len(pp) >= 2 {
|
||||||
cmpath, m, mod, realTypes := getModel(p[2])
|
m, mod, realTypes := getModel(p[2])
|
||||||
para.Schema = &swagger.Schema{
|
para.Schema = &swagger.Schema{
|
||||||
Ref: "#/definitions/" + m,
|
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] = make(map[string]swagger.Schema, 0)
|
||||||
}
|
}
|
||||||
modelsList[pkgpath+controllerName][typ] = mod
|
modelsList[pkgpath+controllerName][typ] = mod
|
||||||
appendModels(cmpath, pkgpath, controllerName, realTypes)
|
appendModels(pkgpath, controllerName, realTypes)
|
||||||
} else {
|
} else {
|
||||||
isArray := false
|
isArray := false
|
||||||
paraType := ""
|
paraType := ""
|
||||||
@ -610,22 +669,10 @@ func getparams(str string) []string {
|
|||||||
return r
|
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, ".")
|
strs := strings.Split(str, ".")
|
||||||
objectname = strs[len(strs)-1]
|
objectname = strs[len(strs)-1]
|
||||||
pkgpath = strings.Join(strs[:len(strs)-1], "/")
|
packageName := ""
|
||||||
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)
|
|
||||||
}
|
|
||||||
m.Type = "object"
|
m.Type = "object"
|
||||||
for _, pkg := range astPkgs {
|
for _, pkg := range astPkgs {
|
||||||
for _, fl := range pkg.Files {
|
for _, fl := range pkg.Files {
|
||||||
@ -634,7 +681,8 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Schema, realTyp
|
|||||||
if k != objectname {
|
if k != objectname {
|
||||||
continue
|
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 {
|
if len(rootapi.Definitions) == 0 {
|
||||||
rootapi.Definitions = make(map[string]swagger.Schema)
|
rootapi.Definitions = make(map[string]swagger.Schema)
|
||||||
}
|
}
|
||||||
|
objectname = packageName + "." + objectname
|
||||||
rootapi.Definitions[objectname] = m
|
rootapi.Definitions[objectname] = m
|
||||||
return
|
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)
|
ts, ok := d.Decl.(*ast.TypeSpec)
|
||||||
if !ok {
|
if !ok {
|
||||||
ColorLog("Unknown type without TypeSec: %v\n", d)
|
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 {
|
if st.Fields.List != nil {
|
||||||
m.Properties = make(map[string]swagger.Propertie)
|
m.Properties = make(map[string]swagger.Propertie)
|
||||||
for _, field := range st.Fields.List {
|
for _, field := range st.Fields.List {
|
||||||
|
realType := ""
|
||||||
isSlice, realType, sType := typeAnalyser(field)
|
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)
|
*realTypes = append(*realTypes, realType)
|
||||||
mp := swagger.Propertie{}
|
mp := swagger.Propertie{}
|
||||||
if isSlice {
|
if isSlice {
|
||||||
@ -709,7 +769,42 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tagValues []string
|
var tagValues []string
|
||||||
|
var err error
|
||||||
|
|
||||||
stag := reflect.StructTag(strings.Trim(field.Tag.Value, "`"))
|
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")
|
tag := stag.Get("json")
|
||||||
|
|
||||||
if tag != "" {
|
if tag != "" {
|
||||||
@ -747,7 +842,7 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string
|
|||||||
for _, fl := range pkg.Files {
|
for _, fl := range pkg.Files {
|
||||||
for nameOfObj, obj := range fl.Scope.Objects {
|
for nameOfObj, obj := range fl.Scope.Objects {
|
||||||
if obj.Name == fmt.Sprint(field.Type) {
|
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
|
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
|
// regexp get json tag
|
||||||
func grepJSONTag(tag string) string {
|
func grepJSONTag(tag string) string {
|
||||||
r, _ := regexp.Compile(`json:"([^"]*)"`)
|
r, _ := regexp.Compile(`json:"([^"]*)"`)
|
||||||
@ -816,23 +899,16 @@ func grepJSONTag(tag string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// append models
|
// append models
|
||||||
func appendModels(cmpath, pkgpath, controllerName string, realTypes []string) {
|
func appendModels(pkgpath, controllerName string, realTypes []string) {
|
||||||
var p string
|
|
||||||
if cmpath != "" {
|
|
||||||
p = strings.Join(strings.Split(cmpath, "/"), ".") + "."
|
|
||||||
} else {
|
|
||||||
p = ""
|
|
||||||
}
|
|
||||||
for _, realType := range realTypes {
|
for _, realType := range realTypes {
|
||||||
if realType != "" && !isBasicType(strings.TrimLeft(realType, "[]")) &&
|
if realType != "" && !isBasicType(strings.TrimLeft(realType, "[]")) &&
|
||||||
!strings.HasPrefix(realType, "map") && !strings.HasPrefix(realType, "&") {
|
!strings.HasPrefix(realType, "map") && !strings.HasPrefix(realType, "&") {
|
||||||
if _, ok := modelsList[pkgpath+controllerName][p+realType]; ok {
|
if _, ok := modelsList[pkgpath+controllerName][realType]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
//fmt.Printf(pkgpath + ":" + controllerName + ":" + cmpath + ":" + realType + "\n")
|
_, mod, newRealTypes := getModel(realType)
|
||||||
_, _, mod, newRealTypes := getModel(p + realType)
|
modelsList[pkgpath+controllerName][realType] = mod
|
||||||
modelsList[pkgpath+controllerName][p+realType] = mod
|
appendModels(pkgpath, controllerName, newRealTypes)
|
||||||
appendModels(cmpath, pkgpath, controllerName, newRealTypes)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
pack.go
19
pack.go
@ -21,7 +21,6 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
path "path/filepath"
|
path "path/filepath"
|
||||||
@ -454,24 +453,6 @@ func packDirectory(excludePrefix []string, excludeSuffix []string,
|
|||||||
return
|
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 {
|
func packApp(cmd *Command, args []string) int {
|
||||||
ShowShortVersionBanner()
|
ShowShortVersionBanner()
|
||||||
|
|
||||||
|
2
run.go
2
run.go
@ -87,7 +87,7 @@ func runApp(cmd *Command, args []string) int {
|
|||||||
currentGoPath = _gopath
|
currentGoPath = _gopath
|
||||||
appname = path.Base(currpath)
|
appname = path.Base(currpath)
|
||||||
} else {
|
} 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)
|
ColorLog("[INFO] Using '%s' as 'appname'\n", appname)
|
||||||
|
39
util.go
39
util.go
@ -15,14 +15,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"path"
|
|
||||||
"fmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Go is a basic promise implementation: it wraps calls a function in a goroutine
|
// 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
|
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{}) {
|
func Debugf(format string, a ...interface{}) {
|
||||||
if os.Getenv("DEBUG") != "" {
|
if os.Getenv("DEBUG") != "" {
|
||||||
_, file, line, ok := runtime.Caller(1)
|
_, file, line, ok := runtime.Caller(1)
|
||||||
@ -174,6 +176,37 @@ func GetGOPATHs() []string {
|
|||||||
return paths
|
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) {
|
func SearchGOPATHs(app string) (bool, string, string) {
|
||||||
gps := GetGOPATHs()
|
gps := GetGOPATHs()
|
||||||
if len(gps) == 0 {
|
if len(gps) == 0 {
|
||||||
|
Loading…
Reference in New Issue
Block a user