diff --git a/.gitignore b/.gitignore index ffab5d0..a4c4e18 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # Folders _obj _test +.idea # Architecture specific extensions/prefixes *.[568vq] diff --git a/Beefile b/Beefile new file mode 100644 index 0000000..7d682df --- /dev/null +++ b/Beefile @@ -0,0 +1,15 @@ +version: 0 +gopm: + enable: false + install: false +go_install: false +watch_ext: [] +dir_structure: + watch_all: false + controllers: "" + models: "" + others: [] +cmd_args: [] +envs: [] +database: + driver: "mysql" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7259aa6 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +.PHONY: all test clean build install + +GOFLAGS ?= $(GOFLAGS:) + +all: install test + +build: + go build $(GOFLAGS) ./... + +install: + go get $(GOFLAGS) ./... + +test: install + go test $(GOFLAGS) ./... + +bench: install + go test -run=NONE -bench=. $(GOFLAGS) ./... + +clean: + go clean $(GOFLAGS) -i ./... diff --git a/README.md b/README.md index cdb5e9d..fecb3e9 100644 --- a/README.md +++ b/README.md @@ -199,17 +199,17 @@ usage: bee migrate [Command] bee migrate [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"] run all outstanding migrations - -driver: [mysql | postgresql | sqlite], the default is mysql + -driver: [mysql | postgres | sqlite], the default is mysql -conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test bee migrate rollback [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"] rollback the last migration operation - -driver: [mysql | postgresql | sqlite], the default is mysql + -driver: [mysql | postgres | sqlite], the default is mysql -conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test bee migrate reset [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"] rollback all migrations - -driver: [mysql | postgresql | sqlite], the default is mysql + -driver: [mysql | postgres| sqlite], the default is mysql -conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test bee migrate refresh [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"] diff --git a/apiapp.go b/apiapp.go index 8ae376e..4d4e58a 100644 --- a/apiapp.go +++ b/apiapp.go @@ -69,7 +69,6 @@ EnableDocs = true var apiMaingo = `package main import ( - _ "{{.Appname}}/docs" _ "{{.Appname}}/routers" "github.com/astaxie/beego" @@ -87,7 +86,6 @@ func main() { var apiMainconngo = `package main import ( - _ "{{.Appname}}/docs" _ "{{.Appname}}/routers" "github.com/astaxie/beego" @@ -298,7 +296,7 @@ type ObjectController struct { beego.Controller } -// @Title create +// @Title Create // @Description create object // @Param body body models.Object true "The object content" // @Success 200 {string} models.Object.Id @@ -342,7 +340,7 @@ func (o *ObjectController) GetAll() { o.ServeJSON() } -// @Title update +// @Title Update // @Description update the object // @Param objectId path string true "The objectid you want to update" // @Param body body models.Object true "The body" @@ -363,7 +361,7 @@ func (o *ObjectController) Put() { o.ServeJSON() } -// @Title delete +// @Title Delete // @Description delete the object // @Param objectId path string true "The objectId you want to delete" // @Success 200 {string} delete success! @@ -391,7 +389,7 @@ type UserController struct { beego.Controller } -// @Title createUser +// @Title CreateUser // @Description create users // @Param body body models.User true "body for user content" // @Success 200 {int} models.User.Id @@ -405,7 +403,7 @@ func (u *UserController) Post() { u.ServeJSON() } -// @Title Get +// @Title GetAll // @Description get all Users // @Success 200 {object} models.User // @router / [get] @@ -434,7 +432,7 @@ func (u *UserController) Get() { u.ServeJSON() } -// @Title update +// @Title Update // @Description update the user // @Param uid path string true "The uid you want to update" // @Param body body models.User true "body for user content" @@ -456,7 +454,7 @@ func (u *UserController) Put() { u.ServeJSON() } -// @Title delete +// @Title Delete // @Description delete the user // @Param uid path string true "The uid you want to delete" // @Success 200 {string} delete success! @@ -469,7 +467,7 @@ func (u *UserController) Delete() { u.ServeJSON() } -// @Title login +// @Title Login // @Description Logs user into the system // @Param username query string true "The username for login" // @Param password query string true "The password for login" @@ -546,14 +544,19 @@ func init() { } func createapi(cmd *Command, args []string) int { - curpath, _ := os.Getwd() + ShowShortVersionBanner() + + w := NewColorWriter(os.Stdout) + if len(args) < 1 { ColorLog("[ERRO] Argument [appname] is missing\n") os.Exit(2) } + if len(args) > 1 { cmd.Flag.Parse(args[1:]) } + apppath, packpath, err := checkEnv(args[0]) if err != nil { fmt.Println(err) @@ -564,23 +567,23 @@ func createapi(cmd *Command, args []string) int { } if conn == "" { } - os.MkdirAll(apppath, 0755) - fmt.Println("create app folder:", apppath) - os.Mkdir(path.Join(apppath, "conf"), 0755) - fmt.Println("create conf:", path.Join(apppath, "conf")) - os.Mkdir(path.Join(apppath, "controllers"), 0755) - fmt.Println("create controllers:", path.Join(apppath, "controllers")) - os.Mkdir(path.Join(apppath, "docs"), 0755) - fmt.Println("create docs:", path.Join(apppath, "docs")) - os.Mkdir(path.Join(apppath, "tests"), 0755) - fmt.Println("create tests:", path.Join(apppath, "tests")) - fmt.Println("create conf app.conf:", path.Join(apppath, "conf", "app.conf")) - writetofile(path.Join(apppath, "conf", "app.conf"), - strings.Replace(apiconf, "{{.Appname}}", args[0], -1)) + ColorLog("[INFO] Creating API...\n") + + os.MkdirAll(apppath, 0755) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath, "\x1b[0m") + os.Mkdir(path.Join(apppath, "conf"), 0755) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf"), "\x1b[0m") + os.Mkdir(path.Join(apppath, "controllers"), 0755) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers"), "\x1b[0m") + os.Mkdir(path.Join(apppath, "tests"), 0755) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests"), "\x1b[0m") + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m") + WriteToFile(path.Join(apppath, "conf", "app.conf"), + strings.Replace(apiconf, "{{.Appname}}", path.Base(args[0]), -1)) if conn != "" { - fmt.Println("create main.go:", path.Join(apppath, "main.go")) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m") maingoContent := strings.Replace(apiMainconngo, "{{.Appname}}", packpath, -1) maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(driver), -1) if driver == "mysql" { @@ -588,7 +591,7 @@ func createapi(cmd *Command, args []string) int { } else if driver == "postgres" { maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1) } - writetofile(path.Join(apppath, "main.go"), + WriteToFile(path.Join(apppath, "main.go"), strings.Replace( maingoContent, "{{.conn}}", @@ -599,90 +602,72 @@ func createapi(cmd *Command, args []string) int { ColorLog("[INFO] Using '%s' as 'driver'\n", driver) ColorLog("[INFO] Using '%s' as 'conn'\n", conn) ColorLog("[INFO] Using '%s' as 'tables'\n", tables) - generateAppcode(string(driver), string(conn), "3", string(tables), path.Join(curpath, args[0])) + generateAppcode(string(driver), string(conn), "3", string(tables), apppath) } else { os.Mkdir(path.Join(apppath, "models"), 0755) - fmt.Println("create models:", path.Join(apppath, "models")) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models"), "\x1b[0m") os.Mkdir(path.Join(apppath, "routers"), 0755) - fmt.Println(path.Join(apppath, "routers") + string(path.Separator)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers")+string(path.Separator), "\x1b[0m") - fmt.Println("create controllers object.go:", path.Join(apppath, "controllers", "object.go")) - writetofile(path.Join(apppath, "controllers", "object.go"), + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "object.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "controllers", "object.go"), strings.Replace(apiControllers, "{{.Appname}}", packpath, -1)) - fmt.Println("create controllers user.go:", path.Join(apppath, "controllers", "user.go")) - writetofile(path.Join(apppath, "controllers", "user.go"), + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "user.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "controllers", "user.go"), strings.Replace(apiControllers2, "{{.Appname}}", packpath, -1)) - fmt.Println("create tests default.go:", path.Join(apppath, "tests", "default_test.go")) - writetofile(path.Join(apppath, "tests", "default_test.go"), + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests", "default_test.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "tests", "default_test.go"), strings.Replace(apiTests, "{{.Appname}}", packpath, -1)) - fmt.Println("create routers router.go:", path.Join(apppath, "routers", "router.go")) - writetofile(path.Join(apppath, "routers", "router.go"), + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers", "router.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "routers", "router.go"), strings.Replace(apirouter, "{{.Appname}}", packpath, -1)) - fmt.Println("create models object.go:", path.Join(apppath, "models", "object.go")) - writetofile(path.Join(apppath, "models", "object.go"), apiModels) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "object.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "models", "object.go"), apiModels) - fmt.Println("create models user.go:", path.Join(apppath, "models", "user.go")) - writetofile(path.Join(apppath, "models", "user.go"), apiModels2) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "user.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "models", "user.go"), apiModels2) - fmt.Println("create docs doc.go:", path.Join(apppath, "docs", "doc.go")) - writetofile(path.Join(apppath, "docs", "doc.go"), "package docs") - - fmt.Println("create main.go:", path.Join(apppath, "main.go")) - writetofile(path.Join(apppath, "main.go"), + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "main.go"), strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1)) } + ColorLog("[SUCC] New API successfully created!\n") return 0 } func checkEnv(appname string) (apppath, packpath string, err error) { - curpath, err := os.Getwd() - if err != nil { - return + gps := GetGOPATHs() + if len(gps) == 0 { + ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty") + os.Exit(2) } - - gopath := os.Getenv("GOPATH") - Debugf("gopath:%s", gopath) - if gopath == "" { - err = fmt.Errorf("you should set GOPATH in the env") - return - } - - appsrcpath := "" - haspath := false - wgopath := path.SplitList(gopath) - for _, wg := range wgopath { - wg = path.Join(wg, "src") - - if strings.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) { - haspath = true - appsrcpath = wg - break - } - - wg, _ = path.EvalSymlinks(wg) - - if strings.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) { - haspath = true - appsrcpath = wg - break + currpath, _ := os.Getwd() + currpath = path.Join(currpath, appname) + for _, gpath := range gps { + gsrcpath := path.Join(gpath, "src") + if strings.HasPrefix(currpath, gsrcpath) { + return currpath, currpath[len(gsrcpath)+1:], nil } } - if !haspath { - err = fmt.Errorf("can't create application outside of GOPATH `%s`\n"+ - "you first should `cd $GOPATH%ssrc` then use create\n", gopath, string(path.Separator)) - return - } - apppath = path.Join(curpath, appname) + // In case of multiple paths in the GOPATH, by default + // we use the first path + gopath := gps[0] + ColorLog("[%s]You current workdir is not a $GOPATH/src, bee will create the application in GOPATH: %s\n", WARN, gopath) + Debugf("GOPATH: %s", gopath) + + gosrcpath := path.Join(gopath, "src") + apppath = path.Join(gosrcpath, appname) if _, e := os.Stat(apppath); os.IsNotExist(e) == false { - err = fmt.Errorf("path `%s` exists, can not create app without remove it\n", apppath) + err = fmt.Errorf("Cannot create application without removing '%s' first.", apppath) + ColorLog("[ERRO] Path '%s' already exists\n", apppath) return } - packpath = strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/") + packpath = strings.Join(strings.Split(apppath[len(gosrcpath)+1:], string(path.Separator)), "/") return } diff --git a/bale.go b/bale.go index 336d813..2bf14f9 100644 --- a/bale.go +++ b/bale.go @@ -46,6 +46,8 @@ func init() { } func runBale(cmd *Command, args []string) int { + ShowShortVersionBanner() + err := loadConfig() if err != nil { ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err) @@ -60,13 +62,13 @@ func runBale(cmd *Command, args []string) int { ColorLog("[WARN] Skipped directory( %s )\n", p) continue } - ColorLog("[INFO] Packing directory( %s )\n", p) + ColorLog("[INFO] Packaging directory( %s )\n", p) filepath.Walk(p, walkFn) } // Generate auto-uncompress function. buf := new(bytes.Buffer) - buf.WriteString(fmt.Sprintf(_BALE_HEADER, conf.Bale.Import, + buf.WriteString(fmt.Sprintf(BaleHeader, conf.Bale.Import, strings.Join(resFiles, "\",\n\t\t\""), strings.Join(resFiles, ",\n\t\tbale.R"))) @@ -88,7 +90,7 @@ func runBale(cmd *Command, args []string) int { } const ( - _BALE_HEADER = `package main + BaleHeader = `package main import( "os" @@ -176,7 +178,7 @@ func walkFn(resPath string, info os.FileInfo, err error) error { defer fw.Close() // Write header. - fmt.Fprintf(fw, _HEADER, resPath) + fmt.Fprintf(fw, Header, resPath) // Copy and compress data. gz := gzip.NewWriter(&ByteWriter{Writer: fw}) @@ -184,7 +186,7 @@ func walkFn(resPath string, info os.FileInfo, err error) error { gz.Close() // Write footer. - fmt.Fprint(fw, _FOOTER) + fmt.Fprint(fw, Footer) resFiles = append(resFiles, resPath) return nil @@ -200,7 +202,7 @@ func filterSuffix(name string) bool { } const ( - _HEADER = `package bale + Header = `package bale import( "bytes" @@ -210,7 +212,7 @@ import( func R%s() []byte { gz, err := gzip.NewReader(bytes.NewBuffer([]byte{` - _FOOTER = ` + Footer = ` })) if err != nil { diff --git a/banner.go b/banner.go new file mode 100644 index 0000000..af33ef3 --- /dev/null +++ b/banner.go @@ -0,0 +1,73 @@ +package main + +import ( + "io" + "io/ioutil" + "os" + "runtime" + "text/template" + "time" +) + +type vars struct { + GoVersion string + GOOS string + GOARCH string + NumCPU int + GOPATH string + GOROOT string + Compiler string + BeeVersion string + BeegoVersion string +} + +// Now returns the current local time in the specified layout +func Now(layout string) string { + return time.Now().Format(layout) +} + +// InitBanner loads the banner and prints it to output +// All errors are ignored, the application will not +// print the banner in case of error. +func InitBanner(out io.Writer, in io.Reader) { + if in == nil { + ColorLog("[ERRO] The input is nil\n") + os.Exit(2) + } + + banner, err := ioutil.ReadAll(in) + if err != nil { + ColorLog("[ERRO] Error trying to read the banner\n") + ColorLog("[HINT] %v\n", err) + os.Exit(2) + } + + show(out, string(banner)) +} + +func show(out io.Writer, content string) { + t, err := template.New("banner"). + Funcs(template.FuncMap{"Now": Now}). + Parse(content) + + if err != nil { + ColorLog("[ERRO] Cannot parse the banner template\n") + ColorLog("[HINT] %v\n", err) + os.Exit(2) + } + + err = t.Execute(out, vars{ + runtime.Version(), + runtime.GOOS, + runtime.GOARCH, + runtime.NumCPU(), + os.Getenv("GOPATH"), + runtime.GOROOT(), + runtime.Compiler, + version, + getBeegoVersion(), + }) + if err != nil { + panic(err) + } +} diff --git a/bee.go b/bee.go index 11f2352..28ae437 100644 --- a/bee.go +++ b/bee.go @@ -25,7 +25,7 @@ import ( "strings" ) -const version = "1.4.1" +const version = "1.5.0" type Command struct { // Run runs the command. diff --git a/color.go b/color.go new file mode 100644 index 0000000..583d6c6 --- /dev/null +++ b/color.go @@ -0,0 +1,51 @@ +// Copyright 2013 bee authors +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package main + +import "io" + +type outputMode int + +// DiscardNonColorEscSeq supports the divided color escape sequence. +// But non-color escape sequence is not output. +// Please use the OutputNonColorEscSeq If you want to output a non-color +// escape sequences such as ncurses. However, it does not support the divided +// color escape sequence. +const ( + _ outputMode = iota + DiscardNonColorEscSeq + OutputNonColorEscSeq +) + +// NewColorWriter creates and initializes a new ansiColorWriter +// using io.Writer w as its initial contents. +// In the console of Windows, which change the foreground and background +// colors of the text by the escape sequence. +// In the console of other systems, which writes to w all text. +func NewColorWriter(w io.Writer) io.Writer { + return NewModeColorWriter(w, DiscardNonColorEscSeq) +} + +// NewModeColorWriter create and initializes a new ansiColorWriter +// by specifying the outputMode. +func NewModeColorWriter(w io.Writer, mode outputMode) io.Writer { + if _, ok := w.(*colorWriter); !ok { + return &colorWriter{ + w: w, + mode: mode, + } + } + return w +} diff --git a/colorwriter.go b/colorwriter.go new file mode 100644 index 0000000..86201e8 --- /dev/null +++ b/colorwriter.go @@ -0,0 +1,28 @@ +// Copyright 2013 bee authors +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// +build !windows + +package main + +import "io" + +type colorWriter struct { + w io.Writer + mode outputMode +} + +func (cw *colorWriter) Write(p []byte) (int, error) { + return cw.w.Write(p) +} diff --git a/colorwriter_windows.go b/colorwriter_windows.go new file mode 100644 index 0000000..0a8e88f --- /dev/null +++ b/colorwriter_windows.go @@ -0,0 +1,427 @@ +// Copyright 2013 bee authors +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// +build windows + +package main + +import ( + "bytes" + "io" + "strings" + "syscall" + "unsafe" +) + +type csiState int + +const ( + outsideCsiCode csiState = iota + firstCsiCode + secondCsiCode +) + +type parseResult int + +const ( + noConsole parseResult = iota + changedColor + unknown +) + +type colorWriter struct { + w io.Writer + mode outputMode + state csiState + paramStartBuf bytes.Buffer + paramBuf bytes.Buffer +} + +const ( + firstCsiChar byte = '\x1b' + secondeCsiChar byte = '[' + separatorChar byte = ';' + sgrCode byte = 'm' +) + +const ( + foregroundBlue = uint16(0x0001) + foregroundGreen = uint16(0x0002) + foregroundRed = uint16(0x0004) + foregroundIntensity = uint16(0x0008) + backgroundBlue = uint16(0x0010) + backgroundGreen = uint16(0x0020) + backgroundRed = uint16(0x0040) + backgroundIntensity = uint16(0x0080) + underscore = uint16(0x8000) + + foregroundMask = foregroundBlue | foregroundGreen | foregroundRed | foregroundIntensity + backgroundMask = backgroundBlue | backgroundGreen | backgroundRed | backgroundIntensity +) + +const ( + ansiReset = "0" + ansiIntensityOn = "1" + ansiIntensityOff = "21" + ansiUnderlineOn = "4" + ansiUnderlineOff = "24" + ansiBlinkOn = "5" + ansiBlinkOff = "25" + + ansiForegroundBlack = "30" + ansiForegroundRed = "31" + ansiForegroundGreen = "32" + ansiForegroundYellow = "33" + ansiForegroundBlue = "34" + ansiForegroundMagenta = "35" + ansiForegroundCyan = "36" + ansiForegroundWhite = "37" + ansiForegroundDefault = "39" + + ansiBackgroundBlack = "40" + ansiBackgroundRed = "41" + ansiBackgroundGreen = "42" + ansiBackgroundYellow = "43" + ansiBackgroundBlue = "44" + ansiBackgroundMagenta = "45" + ansiBackgroundCyan = "46" + ansiBackgroundWhite = "47" + ansiBackgroundDefault = "49" + + ansiLightForegroundGray = "90" + ansiLightForegroundRed = "91" + ansiLightForegroundGreen = "92" + ansiLightForegroundYellow = "93" + ansiLightForegroundBlue = "94" + ansiLightForegroundMagenta = "95" + ansiLightForegroundCyan = "96" + ansiLightForegroundWhite = "97" + + ansiLightBackgroundGray = "100" + ansiLightBackgroundRed = "101" + ansiLightBackgroundGreen = "102" + ansiLightBackgroundYellow = "103" + ansiLightBackgroundBlue = "104" + ansiLightBackgroundMagenta = "105" + ansiLightBackgroundCyan = "106" + ansiLightBackgroundWhite = "107" +) + +type drawType int + +const ( + foreground drawType = iota + background +) + +type winColor struct { + code uint16 + drawType drawType +} + +var colorMap = map[string]winColor{ + ansiForegroundBlack: {0, foreground}, + ansiForegroundRed: {foregroundRed, foreground}, + ansiForegroundGreen: {foregroundGreen, foreground}, + ansiForegroundYellow: {foregroundRed | foregroundGreen, foreground}, + ansiForegroundBlue: {foregroundBlue, foreground}, + ansiForegroundMagenta: {foregroundRed | foregroundBlue, foreground}, + ansiForegroundCyan: {foregroundGreen | foregroundBlue, foreground}, + ansiForegroundWhite: {foregroundRed | foregroundGreen | foregroundBlue, foreground}, + ansiForegroundDefault: {foregroundRed | foregroundGreen | foregroundBlue, foreground}, + + ansiBackgroundBlack: {0, background}, + ansiBackgroundRed: {backgroundRed, background}, + ansiBackgroundGreen: {backgroundGreen, background}, + ansiBackgroundYellow: {backgroundRed | backgroundGreen, background}, + ansiBackgroundBlue: {backgroundBlue, background}, + ansiBackgroundMagenta: {backgroundRed | backgroundBlue, background}, + ansiBackgroundCyan: {backgroundGreen | backgroundBlue, background}, + ansiBackgroundWhite: {backgroundRed | backgroundGreen | backgroundBlue, background}, + ansiBackgroundDefault: {0, background}, + + ansiLightForegroundGray: {foregroundIntensity, foreground}, + ansiLightForegroundRed: {foregroundIntensity | foregroundRed, foreground}, + ansiLightForegroundGreen: {foregroundIntensity | foregroundGreen, foreground}, + ansiLightForegroundYellow: {foregroundIntensity | foregroundRed | foregroundGreen, foreground}, + ansiLightForegroundBlue: {foregroundIntensity | foregroundBlue, foreground}, + ansiLightForegroundMagenta: {foregroundIntensity | foregroundRed | foregroundBlue, foreground}, + ansiLightForegroundCyan: {foregroundIntensity | foregroundGreen | foregroundBlue, foreground}, + ansiLightForegroundWhite: {foregroundIntensity | foregroundRed | foregroundGreen | foregroundBlue, foreground}, + + ansiLightBackgroundGray: {backgroundIntensity, background}, + ansiLightBackgroundRed: {backgroundIntensity | backgroundRed, background}, + ansiLightBackgroundGreen: {backgroundIntensity | backgroundGreen, background}, + ansiLightBackgroundYellow: {backgroundIntensity | backgroundRed | backgroundGreen, background}, + ansiLightBackgroundBlue: {backgroundIntensity | backgroundBlue, background}, + ansiLightBackgroundMagenta: {backgroundIntensity | backgroundRed | backgroundBlue, background}, + ansiLightBackgroundCyan: {backgroundIntensity | backgroundGreen | backgroundBlue, background}, + ansiLightBackgroundWhite: {backgroundIntensity | backgroundRed | backgroundGreen | backgroundBlue, background}, +} + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") + defaultAttr *textAttributes +) + +func init() { + screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout)) + if screenInfo != nil { + colorMap[ansiForegroundDefault] = winColor{ + screenInfo.WAttributes & (foregroundRed | foregroundGreen | foregroundBlue), + foreground, + } + colorMap[ansiBackgroundDefault] = winColor{ + screenInfo.WAttributes & (backgroundRed | backgroundGreen | backgroundBlue), + background, + } + defaultAttr = convertTextAttr(screenInfo.WAttributes) + } +} + +type coord struct { + X, Y int16 +} + +type smallRect struct { + Left, Top, Right, Bottom int16 +} + +type consoleScreenBufferInfo struct { + DwSize coord + DwCursorPosition coord + WAttributes uint16 + SrWindow smallRect + DwMaximumWindowSize coord +} + +func getConsoleScreenBufferInfo(hConsoleOutput uintptr) *consoleScreenBufferInfo { + var csbi consoleScreenBufferInfo + ret, _, _ := procGetConsoleScreenBufferInfo.Call( + hConsoleOutput, + uintptr(unsafe.Pointer(&csbi))) + if ret == 0 { + return nil + } + return &csbi +} + +func setConsoleTextAttribute(hConsoleOutput uintptr, wAttributes uint16) bool { + ret, _, _ := procSetConsoleTextAttribute.Call( + hConsoleOutput, + uintptr(wAttributes)) + return ret != 0 +} + +type textAttributes struct { + foregroundColor uint16 + backgroundColor uint16 + foregroundIntensity uint16 + backgroundIntensity uint16 + underscore uint16 + otherAttributes uint16 +} + +func convertTextAttr(winAttr uint16) *textAttributes { + fgColor := winAttr & (foregroundRed | foregroundGreen | foregroundBlue) + bgColor := winAttr & (backgroundRed | backgroundGreen | backgroundBlue) + fgIntensity := winAttr & foregroundIntensity + bgIntensity := winAttr & backgroundIntensity + underline := winAttr & underscore + otherAttributes := winAttr &^ (foregroundMask | backgroundMask | underscore) + return &textAttributes{fgColor, bgColor, fgIntensity, bgIntensity, underline, otherAttributes} +} + +func convertWinAttr(textAttr *textAttributes) uint16 { + var winAttr uint16 + winAttr |= textAttr.foregroundColor + winAttr |= textAttr.backgroundColor + winAttr |= textAttr.foregroundIntensity + winAttr |= textAttr.backgroundIntensity + winAttr |= textAttr.underscore + winAttr |= textAttr.otherAttributes + return winAttr +} + +func changeColor(param []byte) parseResult { + screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout)) + if screenInfo == nil { + return noConsole + } + + winAttr := convertTextAttr(screenInfo.WAttributes) + strParam := string(param) + if len(strParam) <= 0 { + strParam = "0" + } + csiParam := strings.Split(strParam, string(separatorChar)) + for _, p := range csiParam { + c, ok := colorMap[p] + switch { + case !ok: + switch p { + case ansiReset: + winAttr.foregroundColor = defaultAttr.foregroundColor + winAttr.backgroundColor = defaultAttr.backgroundColor + winAttr.foregroundIntensity = defaultAttr.foregroundIntensity + winAttr.backgroundIntensity = defaultAttr.backgroundIntensity + winAttr.underscore = 0 + winAttr.otherAttributes = 0 + case ansiIntensityOn: + winAttr.foregroundIntensity = foregroundIntensity + case ansiIntensityOff: + winAttr.foregroundIntensity = 0 + case ansiUnderlineOn: + winAttr.underscore = underscore + case ansiUnderlineOff: + winAttr.underscore = 0 + case ansiBlinkOn: + winAttr.backgroundIntensity = backgroundIntensity + case ansiBlinkOff: + winAttr.backgroundIntensity = 0 + default: + // unknown code + } + case c.drawType == foreground: + winAttr.foregroundColor = c.code + case c.drawType == background: + winAttr.backgroundColor = c.code + } + } + winTextAttribute := convertWinAttr(winAttr) + setConsoleTextAttribute(uintptr(syscall.Stdout), winTextAttribute) + + return changedColor +} + +func parseEscapeSequence(command byte, param []byte) parseResult { + if defaultAttr == nil { + return noConsole + } + + switch command { + case sgrCode: + return changeColor(param) + default: + return unknown + } +} + +func (cw *colorWriter) flushBuffer() (int, error) { + return cw.flushTo(cw.w) +} + +func (cw *colorWriter) resetBuffer() (int, error) { + return cw.flushTo(nil) +} + +func (cw *colorWriter) flushTo(w io.Writer) (int, error) { + var n1, n2 int + var err error + + startBytes := cw.paramStartBuf.Bytes() + cw.paramStartBuf.Reset() + if w != nil { + n1, err = cw.w.Write(startBytes) + if err != nil { + return n1, err + } + } else { + n1 = len(startBytes) + } + paramBytes := cw.paramBuf.Bytes() + cw.paramBuf.Reset() + if w != nil { + n2, err = cw.w.Write(paramBytes) + if err != nil { + return n1 + n2, err + } + } else { + n2 = len(paramBytes) + } + return n1 + n2, nil +} + +func isParameterChar(b byte) bool { + return ('0' <= b && b <= '9') || b == separatorChar +} + +func (cw *colorWriter) Write(p []byte) (int, error) { + r, nw, first, last := 0, 0, 0, 0 + if cw.mode != DiscardNonColorEscSeq { + cw.state = outsideCsiCode + cw.resetBuffer() + } + + var err error + for i, ch := range p { + switch cw.state { + case outsideCsiCode: + if ch == firstCsiChar { + cw.paramStartBuf.WriteByte(ch) + cw.state = firstCsiCode + } + case firstCsiCode: + switch ch { + case firstCsiChar: + cw.paramStartBuf.WriteByte(ch) + break + case secondeCsiChar: + cw.paramStartBuf.WriteByte(ch) + cw.state = secondCsiCode + last = i - 1 + default: + cw.resetBuffer() + cw.state = outsideCsiCode + } + case secondCsiCode: + if isParameterChar(ch) { + cw.paramBuf.WriteByte(ch) + } else { + nw, err = cw.w.Write(p[first:last]) + r += nw + if err != nil { + return r, err + } + first = i + 1 + result := parseEscapeSequence(ch, cw.paramBuf.Bytes()) + if result == noConsole || (cw.mode == OutputNonColorEscSeq && result == unknown) { + cw.paramBuf.WriteByte(ch) + nw, err := cw.flushBuffer() + if err != nil { + return r, err + } + r += nw + } else { + n, _ := cw.resetBuffer() + // Add one more to the size of the buffer for the last ch + r += n + 1 + } + + cw.state = outsideCsiCode + } + default: + cw.state = outsideCsiCode + } + } + + if cw.mode != DiscardNonColorEscSeq || cw.state == outsideCsiCode { + nw, err = cw.w.Write(p[first:len(p)]) + r += nw + } + + return r, err +} diff --git a/conf.go b/conf.go index 9d610d1..b26f26b 100644 --- a/conf.go +++ b/conf.go @@ -16,10 +16,13 @@ package main import ( "encoding/json" + "io/ioutil" "os" + + "gopkg.in/yaml.v2" ) -const CONF_VER = 0 +const ConfVer = 0 var defaultConf = `{ "version": 0, @@ -50,20 +53,20 @@ var conf struct { Install bool } // Indicates whether execute "go install" before "go build". - GoInstall bool `json:"go_install"` - WatchExt []string `json:"watch_ext"` + GoInstall bool `json:"go_install" yaml:"go_install"` + WatchExt []string `json:"watch_ext" yaml:"watch_ext"` DirStruct struct { - WatchAll bool `json:"watch_all"` + WatchAll bool `json:"watch_all" yaml:"watch_all"` Controllers string Models string Others []string // Other directories. - } `json:"dir_structure"` - CmdArgs []string `json:"cmd_args"` + } `json:"dir_structure" yaml:"dir_structure"` + CmdArgs []string `json:"cmd_args" yaml:"cmd_args"` Envs []string Bale struct { Import string Dirs []string - IngExt []string `json:"ignore_ext"` + IngExt []string `json:"ignore_ext" yaml:"ignore_ext"` } Database struct { Driver string @@ -73,14 +76,9 @@ var conf struct { // loadConfig loads customized configuration. func loadConfig() error { + foundConf := false f, err := os.Open("bee.json") - if err != nil { - // Use default. - err = json.Unmarshal([]byte(defaultConf), &conf) - if err != nil { - return err - } - } else { + if err == nil { defer f.Close() ColorLog("[INFO] Detected bee.json\n") d := json.NewDecoder(f) @@ -88,10 +86,26 @@ func loadConfig() error { if err != nil { return err } + foundConf = true + } + byml, erryml := ioutil.ReadFile("Beefile") + if erryml == nil { + ColorLog("[INFO] Detected Beefile\n") + err = yaml.Unmarshal(byml, &conf) + if err != nil { + return err + } + foundConf = true + } + if !foundConf { + // Use default. + err = json.Unmarshal([]byte(defaultConf), &conf) + if err != nil { + return err + } } - // Check format version. - if conf.Version != CONF_VER { + if conf.Version != ConfVer { ColorLog("[WARN] Your bee.json is out-of-date, please update!\n") ColorLog("[HINT] Compare bee.json under bee source code path and yours\n") } diff --git a/fix.go b/fix.go index b4dbd39..e4adde6 100644 --- a/fix.go +++ b/fix.go @@ -8,6 +8,7 @@ import ( "path/filepath" "regexp" "strings" + "fmt" ) var cmdFix = &Command{ @@ -25,9 +26,12 @@ func init() { } func runFix(cmd *Command, args []string) int { + ShowShortVersionBanner() + + ColorLog("[INFO] Upgrading the application...\n") dir, err := os.Getwd() if err != nil { - ColorLog("GetCurrent Path:%s\n", err) + ColorLog("[ERRO] GetCurrent Path:%s\n", err) } filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if info.IsDir() { @@ -42,13 +46,14 @@ func runFix(cmd *Command, args []string) int { if strings.HasSuffix(info.Name(), ".exe") { return nil } - ColorLog("%s\n", path) err = fixFile(path) + fmt.Println("\tfix\t", path) if err != nil { - ColorLog("fixFile:%s\n", err) + ColorLog("[ERRO] Could not fix file: %s\n", err) } return err }) + ColorLog("[INFO] Upgrade done!\n") return 0 } diff --git a/g.go b/g.go index e0eb265..bc4b123 100644 --- a/g.go +++ b/g.go @@ -14,7 +14,10 @@ package main -import "os" +import ( + "os" + "strings" +) var cmdGenerate = &Command{ UsageLine: "generate [Command]", @@ -32,7 +35,7 @@ bee generate model [modelname] [-fields=""] -fields: a list of table fields. Format: field:type, ... bee generate controller [controllerfile] - generate RESTFul controllers + generate RESTful controllers bee generate view [viewpath] generate CRUD view in viewpath @@ -40,7 +43,7 @@ bee generate view [viewpath] bee generate migration [migrationfile] [-fields=""] generate migration file for making database schema update -fields: a list of table fields. Format: field:type, ... - + bee generate docs generate swagger doc file @@ -74,19 +77,21 @@ func init() { } func generateCode(cmd *Command, args []string) int { - curpath, _ := os.Getwd() + ShowShortVersionBanner() + + currpath, _ := os.Getwd() if len(args) < 1 { ColorLog("[ERRO] command is missing\n") os.Exit(2) } - gopath := os.Getenv("GOPATH") - Debugf("gopath:%s", gopath) - if gopath == "" { - ColorLog("[ERRO] $GOPATH not found\n") - ColorLog("[HINT] Set $GOPATH in your environment vairables\n") + gps := GetGOPATHs() + if len(gps) == 0 { + ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty") os.Exit(2) } + gopath := gps[0] + Debugf("GOPATH: %s", gopath) gcmd := args[0] switch gcmd { @@ -119,10 +124,9 @@ func generateCode(cmd *Command, args []string) int { os.Exit(2) } sname := args[1] - ColorLog("[INFO] Using '%s' as scaffold name\n", sname) - generateScaffold(sname, fields.String(), curpath, driver.String(), conn.String()) + generateScaffold(sname, fields.String(), currpath, driver.String(), conn.String()) case "docs": - generateDocs(curpath) + generateDocs(currpath) case "appcode": // load config err := loadConfig() @@ -153,7 +157,7 @@ func generateCode(cmd *Command, args []string) int { ColorLog("[INFO] Using '%s' as 'conn'\n", conn) ColorLog("[INFO] Using '%s' as 'tables'\n", tables) ColorLog("[INFO] Using '%s' as 'level'\n", level) - generateAppcode(driver.String(), conn.String(), level.String(), tables.String(), curpath) + generateAppcode(driver.String(), conn.String(), level.String(), tables.String(), currpath) case "migration": if len(args) < 2 { ColorLog("[ERRO] Wrong number of arguments\n") @@ -166,14 +170,15 @@ func generateCode(cmd *Command, args []string) int { upsql := "" downsql := "" if fields != "" { - upsql = `m.SQL("CREATE TABLE ` + mname + "(" + generateSQLFromFields(fields.String()) + `)");` - downsql = `m.SQL("DROP TABLE ` + "`" + mname + "`" + `")` + dbMigrator := newDBDriver() + upsql = dbMigrator.generateCreateUp(mname) + downsql = dbMigrator.generateCreateDown(mname) } - generateMigration(mname, upsql, downsql, curpath) + generateMigration(mname, upsql, downsql, currpath) case "controller": if len(args) == 2 { cname := args[1] - generateController(cname, curpath) + generateController(cname, currpath) } else { ColorLog("[ERRO] Wrong number of arguments\n") ColorLog("[HINT] Usage: bee generate controller [controllername]\n") @@ -192,20 +197,19 @@ func generateCode(cmd *Command, args []string) int { os.Exit(2) } sname := args[1] - ColorLog("[INFO] Using '%s' as model name\n", sname) - generateModel(sname, fields.String(), curpath) + generateModel(sname, fields.String(), currpath) case "view": if len(args) == 2 { cname := args[1] - generateView(cname, curpath) + generateView(cname, currpath) } else { ColorLog("[ERRO] Wrong number of arguments\n") ColorLog("[HINT] Usage: bee generate view [viewpath]\n") os.Exit(2) } default: - ColorLog("[ERRO] command is missing\n") + ColorLog("[ERRO] Command is missing\n") } - ColorLog("[SUCC] generate successfully created!\n") + ColorLog("[SUCC] %s successfully generated!\n", strings.Title(gcmd)) return 0 } diff --git a/g_appcode.go b/g_appcode.go index 7860912..76d78ad 100644 --- a/g_appcode.go +++ b/g_appcode.go @@ -29,9 +29,9 @@ import ( ) const ( - O_MODEL byte = 1 << iota - O_CONTROLLER - O_ROUTER + OModel byte = 1 << iota + OController + ORouter ) // DbTransformer has method to reverse engineer a database schema to restful api code @@ -259,11 +259,11 @@ func generateAppcode(driver, connStr, level, tables, currpath string) { var mode byte switch level { case "1": - mode = O_MODEL + mode = OModel case "2": - mode = O_MODEL | O_CONTROLLER + mode = OModel | OController case "3": - mode = O_MODEL | O_CONTROLLER | O_ROUTER + mode = OModel | OController | ORouter default: ColorLog("[ERRO] Invalid 'level' option: %s\n", level) ColorLog("[HINT] Level must be either 1, 2 or 3\n") @@ -292,7 +292,7 @@ func generateAppcode(driver, connStr, level, tables, currpath string) { // Generate takes table, column and foreign key information from database connection // and generate corresponding golang source files -func gen(dbms, connStr string, mode byte, selectedTableNames map[string]bool, currpath string) { +func gen(dbms, connStr string, mode byte, selectedTableNames map[string]bool, apppath string) { db, err := sql.Open(dbms, connStr) if err != nil { ColorLog("[ERRO] Could not connect to %s database: %s, %s\n", dbms, connStr, err) @@ -304,11 +304,11 @@ func gen(dbms, connStr string, mode byte, selectedTableNames map[string]bool, cu tableNames := trans.GetTableNames(db) tables := getTableObjects(tableNames, db, trans) mvcPath := new(MvcPath) - mvcPath.ModelPath = path.Join(currpath, "models") - mvcPath.ControllerPath = path.Join(currpath, "controllers") - mvcPath.RouterPath = path.Join(currpath, "routers") + mvcPath.ModelPath = path.Join(apppath, "models") + mvcPath.ControllerPath = path.Join(apppath, "controllers") + mvcPath.RouterPath = path.Join(apppath, "routers") createPaths(mode, mvcPath) - pkgPath := getPackagePath(currpath) + pkgPath := getPackagePath(apppath) writeSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames) } else { ColorLog("[ERRO] Generating app code from %s database is not supported yet.\n", dbms) @@ -499,16 +499,15 @@ func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[strin } } -// getGoDataType maps an SQL data type to Golang data type +// GetGoDataType maps an SQL data type to Golang data type func (*MysqlDB) GetGoDataType(sqlType string) (goType string) { var typeMapping = map[string]string{} typeMapping = typeMappingMysql if v, ok := typeMapping[sqlType]; ok { return v - } else { - ColorLog("[ERRO] data type (%s) not found!\n", sqlType) - os.Exit(2) } + ColorLog("[ERRO] data type (%s) not found!\n", sqlType) + os.Exit(2) return goType } @@ -689,25 +688,26 @@ func (postgresDB *PostgresDB) GetColumns(db *sql.DB, table *Table, blackList map table.Columns = append(table.Columns, col) } } + +// GetGoDataType returns the Go type from the mapped Postgres type func (*PostgresDB) GetGoDataType(sqlType string) (goType string) { if v, ok := typeMappingPostgres[sqlType]; ok { return v - } else { - ColorLog("[ERRO] data type (%s) not found!\n", sqlType) - os.Exit(2) } + ColorLog("[ERRO] data type (%s) not found!\n", sqlType) + os.Exit(2) return goType } // deleteAndRecreatePaths removes several directories completely func createPaths(mode byte, paths *MvcPath) { - if (mode & O_MODEL) == O_MODEL { + if (mode & OModel) == OModel { os.Mkdir(paths.ModelPath, 0777) } - if (mode & O_CONTROLLER) == O_CONTROLLER { + if (mode & OController) == OController { os.Mkdir(paths.ControllerPath, 0777) } - if (mode & O_ROUTER) == O_ROUTER { + if (mode & ORouter) == ORouter { os.Mkdir(paths.RouterPath, 0777) } } @@ -716,15 +716,15 @@ func createPaths(mode byte, paths *MvcPath) { // It will wipe the following directories and recreate them:./models, ./controllers, ./routers // Newly geneated files will be inside these folders. func writeSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) { - if (O_MODEL & mode) == O_MODEL { + if (OModel & mode) == OModel { ColorLog("[INFO] Creating model files...\n") writeModelFiles(tables, paths.ModelPath, selectedTables) } - if (O_CONTROLLER & mode) == O_CONTROLLER { + if (OController & mode) == OController { ColorLog("[INFO] Creating controller files...\n") writeControllerFiles(tables, paths.ControllerPath, selectedTables, pkgPath) } - if (O_ROUTER & mode) == O_ROUTER { + if (ORouter & mode) == ORouter { ColorLog("[INFO] Creating router files...\n") writeRouterFile(tables, paths.RouterPath, selectedTables, pkgPath) } @@ -732,6 +732,8 @@ func writeSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath // writeModelFiles generates model files func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bool) { + w := NewColorWriter(os.Stdout) + for _, tb := range tables { // if selectedTables map is not nil and this table is not selected, ignore it if selectedTables != nil { @@ -744,7 +746,7 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo var f *os.File var err error if isExist(fpath) { - ColorLog("[WARN] %v is exist, do you want to overwrite it? Yes or No?\n", fpath) + ColorLog("[WARN] '%v' already exists. Do you want to overwrite it? [Yes|No] ", fpath) if askForConfirmation() { f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) if err != nil { @@ -752,7 +754,7 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo continue } } else { - ColorLog("[WARN] skip create file\n") + ColorLog("[WARN] Skipped create file '%s'\n", fpath) continue } } else { @@ -764,9 +766,9 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo } template := "" if tb.Pk == "" { - template = STRUCT_MODEL_TPL + template = StructModelTPL } else { - template = MODEL_TPL + template = ModelTPL } fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1) fileStr = strings.Replace(fileStr, "{{modelName}}", camelCase(tb.Name), -1) @@ -784,14 +786,16 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo ColorLog("[ERRO] Could not write model file to %s\n", fpath) os.Exit(2) } - f.Close() - ColorLog("[INFO] model => %s\n", fpath) + CloseFile(f) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") formatSourceCode(fpath) } } // writeControllerFiles generates controller files func writeControllerFiles(tables []*Table, cPath string, selectedTables map[string]bool, pkgPath string) { + w := NewColorWriter(os.Stdout) + for _, tb := range tables { // if selectedTables map is not nil and this table is not selected, ignore it if selectedTables != nil { @@ -807,7 +811,7 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri var f *os.File var err error if isExist(fpath) { - ColorLog("[WARN] %v is exist, do you want to overwrite it? Yes or No?\n", fpath) + ColorLog("[WARN] '%v' already exists. Do you want to overwrite it? [Yes|No] ", fpath) if askForConfirmation() { f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) if err != nil { @@ -815,7 +819,7 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri continue } } else { - ColorLog("[WARN] skip create file\n") + ColorLog("[WARN] Skipped create file '%s'\n", fpath) continue } } else { @@ -825,20 +829,22 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri continue } } - fileStr := strings.Replace(CTRL_TPL, "{{ctrlName}}", camelCase(tb.Name), -1) + fileStr := strings.Replace(CtrlTPL, "{{ctrlName}}", camelCase(tb.Name), -1) fileStr = strings.Replace(fileStr, "{{pkgPath}}", pkgPath, -1) if _, err := f.WriteString(fileStr); err != nil { ColorLog("[ERRO] Could not write controller file to %s\n", fpath) os.Exit(2) } - f.Close() - ColorLog("[INFO] controller => %s\n", fpath) + CloseFile(f) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") formatSourceCode(fpath) } } // writeRouterFile generates router file func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bool, pkgPath string) { + w := NewColorWriter(os.Stdout) + var nameSpaces []string for _, tb := range tables { // if selectedTables map is not nil and this table is not selected, ignore it @@ -850,19 +856,19 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo if tb.Pk == "" { continue } - // add name spaces - nameSpace := strings.Replace(NAMESPACE_TPL, "{{nameSpace}}", tb.Name, -1) + // add namespaces + nameSpace := strings.Replace(NamespaceTPL, "{{nameSpace}}", tb.Name, -1) nameSpace = strings.Replace(nameSpace, "{{ctrlName}}", camelCase(tb.Name), -1) nameSpaces = append(nameSpaces, nameSpace) } // add export controller fpath := path.Join(rPath, "router.go") - routerStr := strings.Replace(ROUTER_TPL, "{{nameSpaces}}", strings.Join(nameSpaces, ""), 1) + routerStr := strings.Replace(RouterTPL, "{{nameSpaces}}", strings.Join(nameSpaces, ""), 1) routerStr = strings.Replace(routerStr, "{{pkgPath}}", pkgPath, 1) var f *os.File var err error if isExist(fpath) { - ColorLog("[WARN] %v is exist, do you want to overwrite it? Yes or No?\n", fpath) + ColorLog("[WARN] '%v' already exists. Do you want to overwrite it? [Yes|No] ", fpath) if askForConfirmation() { f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) if err != nil { @@ -870,7 +876,7 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo return } } else { - ColorLog("[WARN] skip create file\n") + ColorLog("[WARN] Skipped create file '%s'\n", fpath) return } } else { @@ -881,11 +887,11 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo } } if _, err := f.WriteString(routerStr); err != nil { - ColorLog("[ERRO] Could not write router file to %s\n", fpath) + ColorLog("[ERRO] Could not write router file to '%s'\n", fpath) os.Exit(2) } - f.Close() - ColorLog("[INFO] router => %s\n", fpath) + CloseFile(f) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") formatSourceCode(fpath) } @@ -968,7 +974,7 @@ func getPackagePath(curpath string) (packpath string) { gopath := os.Getenv("GOPATH") Debugf("gopath:%s", gopath) if gopath == "" { - ColorLog("[ERRO] you should set GOPATH in the env") + ColorLog("[ERRO] You should set GOPATH in the env") os.Exit(2) } @@ -1001,12 +1007,12 @@ func getPackagePath(curpath string) (packpath string) { } const ( - STRUCT_MODEL_TPL = `package models + StructModelTPL = `package models {{importTimePkg}} {{modelStruct}} ` - MODEL_TPL = `package models + ModelTPL = `package models import ( "errors" @@ -1099,7 +1105,7 @@ func GetAll{{modelName}}(query map[string]string, fields []string, sortby []stri var l []{{modelName}} qs = qs.OrderBy(sortFields...) - if _, err := qs.Limit(limit, offset).All(&l, fields...); err == nil { + if _, err = qs.Limit(limit, offset).All(&l, fields...); err == nil { if len(fields) == 0 { for _, v := range l { ml = append(ml, v) @@ -1150,7 +1156,7 @@ func Delete{{modelName}}(id int) (err error) { return } ` - CTRL_TPL = `package controllers + CtrlTPL = `package controllers import ( "{{pkgPath}}/models" @@ -1256,7 +1262,7 @@ func (c *{{ctrlName}}Controller) GetAll() { // query: k:v,k:v if v := c.GetString("query"); v != "" { for _, cond := range strings.Split(v, ",") { - kv := strings.Split(cond, ":") + kv := strings.SplitN(cond, ":", 2) if len(kv) != 2 { c.Data["json"] = errors.New("Error: invalid query key/value pair") c.ServeJSON() @@ -1316,7 +1322,7 @@ func (c *{{ctrlName}}Controller) Delete() { c.ServeJSON() } ` - ROUTER_TPL = `// @APIVersion 1.0.0 + RouterTPL = `// @APIVersion 1.0.0 // @Title beego Test API // @Description beego has a very cool tools to autogenerate documents for your API // @Contact astaxie@gmail.com @@ -1338,7 +1344,7 @@ func init() { beego.AddNamespace(ns) } ` - NAMESPACE_TPL = ` + NamespaceTPL = ` beego.NSNamespace("/{{nameSpace}}", beego.NSInclude( &controllers.{{ctrlName}}Controller{}, diff --git a/g_controllers.go b/g_controllers.go index 49b5fd9..79ed554 100644 --- a/g_controllers.go +++ b/g_controllers.go @@ -15,6 +15,7 @@ package main import ( + "fmt" "os" "path" "strings" @@ -23,44 +24,53 @@ import ( // article // cms/article // -func generateController(cname, crupath string) { +func generateController(cname, currpath string) { + w := NewColorWriter(os.Stdout) + p, f := path.Split(cname) controllerName := strings.Title(f) packageName := "controllers" + if p != "" { i := strings.LastIndex(p[:len(p)-1], "/") packageName = p[i+1 : len(p)-1] } + ColorLog("[INFO] Using '%s' as controller name\n", controllerName) ColorLog("[INFO] Using '%s' as package name\n", packageName) - fp := path.Join(crupath, "controllers", p) + + fp := path.Join(currpath, "controllers", p) if _, err := os.Stat(fp); os.IsNotExist(err) { - // create controller directory + // Create the controller's directory if err := os.MkdirAll(fp, 0777); err != nil { ColorLog("[ERRO] Could not create controllers directory: %s\n", err) os.Exit(2) } } + fpath := path.Join(fp, strings.ToLower(controllerName)+".go") if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { - defer f.Close() - modelPath := path.Join(crupath, "models", strings.ToLower(controllerName)+".go") + defer CloseFile(f) + + modelPath := path.Join(currpath, "models", strings.ToLower(controllerName)+".go") + var content string if _, err := os.Stat(modelPath); err == nil { ColorLog("[INFO] Using matching model '%s'\n", controllerName) content = strings.Replace(controllerModelTpl, "{{packageName}}", packageName, -1) - pkgPath := getPackagePath(crupath) + pkgPath := getPackagePath(currpath) content = strings.Replace(content, "{{pkgPath}}", pkgPath, -1) } else { content = strings.Replace(controllerTpl, "{{packageName}}", packageName, -1) } + content = strings.Replace(content, "{{controllerName}}", controllerName, -1) f.WriteString(content) - // gofmt generated source code + + // Run 'gofmt' on the generated source code formatSourceCode(fpath) - ColorLog("[INFO] controller file generated: %s\n", fpath) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") } else { - // error creating file ColorLog("[ERRO] Could not create controller file: %s\n", err) os.Exit(2) } @@ -85,7 +95,7 @@ func (c *{{controllerName}}Controller) URLMapping() { c.Mapping("Delete", c.Delete) } -// @Title Post +// @Title Create // @Description create {{controllerName}} // @Param body body models.{{controllerName}} true "body for {{controllerName}} content" // @Success 201 {object} models.{{controllerName}} @@ -95,7 +105,7 @@ func (c *{{controllerName}}Controller) Post() { } -// @Title Get +// @Title GetOne // @Description get {{controllerName}} by id // @Param id path string true "The key for staticblock" // @Success 200 {object} models.{{controllerName}} @@ -105,7 +115,7 @@ func (c *{{controllerName}}Controller) GetOne() { } -// @Title Get All +// @Title GetAll // @Description get {{controllerName}} // @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..." // @Param fields query string false "Fields returned. e.g. col1,col2 ..." @@ -245,7 +255,7 @@ func (c *{{controllerName}}Controller) GetAll() { // query: k:v,k:v if v := c.GetString("query"); v != "" { for _, cond := range strings.Split(v, ",") { - kv := strings.Split(cond, ":") + kv := strings.SplitN(cond, ":", 2) if len(kv) != 2 { c.Data["json"] = errors.New("Error: invalid query key/value pair") c.ServeJSON() diff --git a/g_docs.go b/g_docs.go index 229540a..6c1e870 100644 --- a/g_docs.go +++ b/g_docs.go @@ -35,63 +35,6 @@ import ( "github.com/astaxie/beego/utils" ) -var globalDocsTemplate = `package docs - -import ( - "encoding/json" - "strings" - - "github.com/astaxie/beego" - "github.com/astaxie/beego/swagger" -) - -const ( - Rootinfo string = {{.rootinfo}} - Subapi string = {{.subapi}} - BasePath string= "{{.version}}" -) - -var rootapi swagger.ResourceListing -var apilist map[string]*swagger.APIDeclaration - -func init() { - if beego.BConfig.WebConfig.EnableDocs { - err := json.Unmarshal([]byte(Rootinfo), &rootapi) - if err != nil { - beego.Error(err) - } - err = json.Unmarshal([]byte(Subapi), &apilist) - if err != nil { - beego.Error(err) - } - beego.GlobalDocAPI["Root"] = rootapi - for k, v := range apilist { - for i, a := range v.APIs { - a.Path = urlReplace(k + a.Path) - v.APIs[i] = a - } - v.BasePath = BasePath - beego.GlobalDocAPI[strings.Trim(k, "/")] = v - } - } -} - - -func urlReplace(src string) string { - pt := strings.Split(src, "/") - for i, p := range pt { - if len(p) > 0 { - if p[0] == ':' { - pt[i] = "{" + p[1:] + "}" - } else if p[0] == '?' && p[1] == ':' { - pt[i] = "{" + p[2:] + "}" - } - } - } - return strings.Join(pt, "/") -} -` - const ( ajson = "application/json" axml = "application/xml" @@ -99,21 +42,19 @@ const ( ahtml = "text/html" ) -var pkgCache map[string]bool //pkg:controller:function:comments comments: key:value +var pkgCache map[string]struct{} //pkg:controller:function:comments comments: key:value var controllerComments map[string]string var importlist map[string]string -var apilist map[string]*swagger.APIDeclaration -var controllerList map[string][]swagger.API -var modelsList map[string]map[string]swagger.Model -var rootapi swagger.ResourceListing +var controllerList map[string]map[string]*swagger.Item //controllername Paths items +var modelsList map[string]map[string]swagger.Schema +var rootapi swagger.Swagger func init() { - pkgCache = make(map[string]bool) + pkgCache = make(map[string]struct{}) controllerComments = make(map[string]string) importlist = make(map[string]string) - apilist = make(map[string]*swagger.APIDeclaration) - controllerList = make(map[string][]swagger.API) - modelsList = make(map[string]map[string]swagger.Model) + controllerList = make(map[string]map[string]*swagger.Item) + modelsList = make(map[string]map[string]swagger.Schema) } func generateDocs(curpath string) { @@ -126,30 +67,31 @@ func generateDocs(curpath string) { os.Exit(2) } - rootapi.Info = swagger.Information{} - rootapi.SwaggerVersion = swagger.SwaggerVersion + rootapi.Infos = swagger.Information{} + rootapi.SwaggerVersion = "2.0" //analysis API comments if f.Comments != nil { for _, c := range f.Comments { for _, s := range strings.Split(c.Text(), "\n") { if strings.HasPrefix(s, "@APIVersion") { - rootapi.APIVersion = strings.TrimSpace(s[len("@APIVersion"):]) + rootapi.Infos.Version = strings.TrimSpace(s[len("@APIVersion"):]) } else if strings.HasPrefix(s, "@Title") { - rootapi.Info.Title = strings.TrimSpace(s[len("@Title"):]) + rootapi.Infos.Title = strings.TrimSpace(s[len("@Title"):]) } else if strings.HasPrefix(s, "@Description") { - rootapi.Info.Description = strings.TrimSpace(s[len("@Description"):]) + rootapi.Infos.Description = strings.TrimSpace(s[len("@Description"):]) } else if strings.HasPrefix(s, "@TermsOfServiceUrl") { - rootapi.Info.TermsOfServiceURL = strings.TrimSpace(s[len("@TermsOfServiceUrl"):]) + rootapi.Infos.TermsOfService = strings.TrimSpace(s[len("@TermsOfServiceUrl"):]) } else if strings.HasPrefix(s, "@Contact") { - rootapi.Info.Contact = strings.TrimSpace(s[len("@Contact"):]) + rootapi.Infos.Contact.EMail = strings.TrimSpace(s[len("@Contact"):]) } else if strings.HasPrefix(s, "@License") { - rootapi.Info.License = strings.TrimSpace(s[len("@License"):]) + rootapi.Infos.License.Name = strings.TrimSpace(s[len("@License"):]) } else if strings.HasPrefix(s, "@LicenseUrl") { - rootapi.Info.LicenseURL = strings.TrimSpace(s[len("@LicenseUrl"):]) + rootapi.Infos.License.URL = strings.TrimSpace(s[len("@LicenseUrl"):]) } } } } + // analisys controller package for _, im := range f.Imports { localName := "" if im.Name != nil { @@ -161,33 +103,46 @@ func generateDocs(curpath string) { switch specDecl := d.(type) { case *ast.FuncDecl: for _, l := range specDecl.Body.List { - switch smtp := l.(type) { + switch stmt := l.(type) { case *ast.AssignStmt: - for _, l := range smtp.Rhs { + for _, l := range stmt.Rhs { if v, ok := l.(*ast.CallExpr); ok { - f, params := analisysNewNamespace(v) - globalDocsTemplate = strings.Replace(globalDocsTemplate, "{{.version}}", f, -1) + // analisys NewNamespace, it will return version and the subfunction + if selName := v.Fun.(*ast.SelectorExpr).Sel.String(); selName != "NewNamespace" { + continue + } + version, params := analisysNewNamespace(v) + if rootapi.BasePath == "" && version != "" { + rootapi.BasePath = version + } for _, p := range params { switch pp := p.(type) { case *ast.CallExpr: + controllerName := "" if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" { s, params := analisysNewNamespace(pp) - subapi := swagger.APIRef{Path: s} - controllerName := "" for _, sp := range params { switch pp := sp.(type) { case *ast.CallExpr: if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" { controllerName = analisysNSInclude(s, pp) + if v, ok := controllerComments[controllerName]; ok { + rootapi.Tags = append(rootapi.Tags, swagger.Tag{ + Name: strings.Trim(s, "/"), + Description: v, + }) + } } } } - if v, ok := controllerComments[controllerName]; ok { - subapi.Description = v - } - rootapi.APIs = append(rootapi.APIs, subapi) } else if selname == "NSInclude" { - analisysNSInclude(f, pp) + controllerName = analisysNSInclude("", pp) + if v, ok := controllerComments[controllerName]; ok { + rootapi.Tags = append(rootapi.Tags, swagger.Tag{ + Name: controllerName, // if the NSInclude has no prefix, we use the controllername as the tag + Description: v, + }) + } } } } @@ -198,25 +153,23 @@ func generateDocs(curpath string) { } } } - apiinfo, err := json.Marshal(rootapi) - if err != nil { - panic(err) - } - subapi, err := json.Marshal(apilist) - if err != nil { - panic(err) - } - os.Mkdir(path.Join(curpath, "docs"), 0755) - fd, err := os.Create(path.Join(curpath, "docs", "docs.go")) + os.Mkdir(path.Join(curpath, "swagger"), 0755) + fd, err := os.Create(path.Join(curpath, "swagger", "swagger.json")) if err != nil { panic(err) } defer fd.Close() - a := strings.Replace(globalDocsTemplate, "{{.rootinfo}}", "`"+string(apiinfo)+"`", -1) - a = strings.Replace(a, "{{.subapi}}", "`"+string(subapi)+"`", -1) - fd.WriteString(a) + dt, err := json.MarshalIndent(rootapi, "", " ") + if err != nil { + panic(err) + } + _, err = fd.Write(dt) + if err != nil { + panic(err) + } } +// return version and the others params func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) { for i, p := range ce.Args { if i == 0 { @@ -233,32 +186,49 @@ func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) { func analisysNSInclude(baseurl string, ce *ast.CallExpr) string { cname := "" - a := &swagger.APIDeclaration{} - a.APIVersion = rootapi.APIVersion - a.SwaggerVersion = swagger.SwaggerVersion - a.ResourcePath = baseurl - a.Produces = []string{"application/json", "application/xml", "text/plain", "text/html"} - a.APIs = make([]swagger.API, 0) - a.Models = make(map[string]swagger.Model) for _, p := range ce.Args { x := p.(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.SelectorExpr) if v, ok := importlist[fmt.Sprint(x.X)]; ok { cname = v + x.Sel.Name } if apis, ok := controllerList[cname]; ok { - if len(a.APIs) > 0 { - a.APIs = append(a.APIs, apis...) - } else { - a.APIs = apis - } - } - if models, ok := modelsList[cname]; ok { - for _, m := range models { - a.Models[m.ID] = m + for rt, item := range apis { + tag := "" + if baseurl != "" { + rt = baseurl + rt + tag = strings.Trim(baseurl, "/") + } else { + tag = cname + } + if item.Get != nil { + item.Get.Tags = []string{tag} + } + if item.Post != nil { + item.Post.Tags = []string{tag} + } + if item.Put != nil { + item.Put.Tags = []string{tag} + } + if item.Patch != nil { + item.Patch.Tags = []string{tag} + } + if item.Head != nil { + item.Head.Tags = []string{tag} + } + if item.Delete != nil { + item.Delete.Tags = []string{tag} + } + if item.Options != nil { + item.Options.Tags = []string{tag} + } + if len(rootapi.Paths) == 0 { + rootapi.Paths = make(map[string]*swagger.Item) + } + rt = urlReplace(rt) + rootapi.Paths[rt] = item } } } - apilist[baseurl] = a return cname } @@ -267,15 +237,15 @@ func analisyscontrollerPkg(localName, pkgpath string) { if isSystemPackage(pkgpath) { return } + if pkgpath == "github.com/astaxie/beego" { + return + } if localName != "" { importlist[localName] = pkgpath } else { pps := strings.Split(pkgpath, "/") importlist[pps[len(pps)-1]] = pkgpath } - if pkgpath == "github.com/astaxie/beego" { - return - } gopath := os.Getenv("GOPATH") if gopath == "" { panic("please set gopath") @@ -294,6 +264,7 @@ func analisyscontrollerPkg(localName, pkgpath string) { if _, ok := pkgCache[pkgpath]; ok { return } + pkgCache[pkgpath] = struct{}{} } else { ColorLog("[ERRO] the %s pkg not exist in gopath\n", pkgpath) os.Exit(1) @@ -315,16 +286,20 @@ func analisyscontrollerPkg(localName, pkgpath string) { case *ast.FuncDecl: if specDecl.Recv != nil && len(specDecl.Recv.List) > 0 { if t, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr); ok { + // parse controller method parserComments(specDecl.Doc, specDecl.Name.String(), fmt.Sprint(t.X), pkgpath) } } case *ast.GenDecl: - if specDecl.Tok.String() == "type" { + if specDecl.Tok == token.TYPE { for _, s := range specDecl.Specs { switch tp := s.(*ast.TypeSpec).Type.(type) { case *ast.StructType: _ = tp.Struct - controllerComments[pkgpath+s.(*ast.TypeSpec).Name.String()] = specDecl.Doc.Text() + //parse controller definition comments + if strings.TrimSpace(specDecl.Doc.Text()) != "" { + controllerComments[pkgpath+s.(*ast.TypeSpec).Name.String()] = specDecl.Doc.Text() + } } } } @@ -355,8 +330,11 @@ func isSystemPackage(pkgpath string) bool { // parse the func comments func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error { - innerapi := swagger.API{} - opts := swagger.Operation{} + var routerPath string + var HTTPMethod string + opts := swagger.Operation{ + Responses: make(map[string]swagger.Response), + } if comments != nil && comments.List != nil { for _, c := range comments.List { t := strings.TrimSpace(strings.TrimLeft(c.Text, "//")) @@ -366,20 +344,20 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat if len(e1) < 1 { return errors.New("you should has router infomation") } - innerapi.Path = e1[0] + routerPath = e1[0] if len(e1) == 2 && e1[1] != "" { e1 = strings.SplitN(e1[1], " ", 2) - opts.HTTPMethod = strings.ToUpper(strings.Trim(e1[0], "[]")) + HTTPMethod = strings.ToUpper(strings.Trim(e1[0], "[]")) } else { - opts.HTTPMethod = "GET" + HTTPMethod = "GET" } } else if strings.HasPrefix(t, "@Title") { - opts.Nickname = strings.TrimSpace(t[len("@Title"):]) + opts.OperationID = controllerName + "." + strings.TrimSpace(t[len("@Title"):]) } else if strings.HasPrefix(t, "@Description") { opts.Summary = strings.TrimSpace(t[len("@Description"):]) } else if strings.HasPrefix(t, "@Success") { ss := strings.TrimSpace(t[len("@Success"):]) - rs := swagger.ResponseMessage{} + rs := swagger.Response{} st := make([]string, 3) j := 0 var tmp []rune @@ -393,7 +371,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat if j == 0 || j == 1 { st[j] = string(tmp) tmp = make([]rune, 0) - j += 1 + j++ start = false if j == 1 { continue @@ -411,7 +389,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat if len(tmp) > 0 && st[2] == "" { st[2] = strings.TrimSpace(string(tmp)) } - rs.Message = st[2] + rs.Description = st[2] if st[1] == "{object}" { if st[2] == "" { panic(controllerName + " " + funcName + " has no object") @@ -419,16 +397,34 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat cmpath, m, mod, realTypes := getModel(st[2]) //ll := strings.Split(st[2], ".") //opts.Type = ll[len(ll)-1] - rs.ResponseModel = m + rs.Schema = &swagger.Schema{ + Ref: "#/definitions/" + m, + } if _, ok := modelsList[pkgpath+controllerName]; !ok { - modelsList[pkgpath+controllerName] = make(map[string]swagger.Model, 0) + modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0) } modelsList[pkgpath+controllerName][st[2]] = mod appendModels(cmpath, pkgpath, controllerName, realTypes) + } else if st[1] == "{array}" { + rs.Schema = &swagger.Schema{} + rs.Schema.Type = "array" + if sType, ok := basicTypes[st[2]]; ok { + typeFormat := strings.Split(sType, ":") + rs.Schema.Type = typeFormat[0] + rs.Schema.Format = typeFormat[1] + } else { + cmpath, m, mod, realTypes := getModel(st[2]) + rs.Schema.Items = &swagger.Propertie{ + Ref: "#/definitions/" + m, + } + if _, ok := modelsList[pkgpath+controllerName]; !ok { + modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0) + } + modelsList[pkgpath+controllerName][st[2]] = mod + appendModels(cmpath, pkgpath, controllerName, realTypes) + } } - - rs.Code, _ = strconv.Atoi(st[0]) - opts.ResponseMessages = append(opts.ResponseMessages, rs) + opts.Responses[st[0]] = rs } else if strings.HasPrefix(t, "@Param") { para := swagger.Parameter{} p := getparams(strings.TrimSpace(t[len("@Param "):])) @@ -436,25 +432,61 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat panic(controllerName + "_" + funcName + "'s comments @Param at least should has 4 params") } para.Name = p[0] - para.ParamType = p[1] + switch p[1] { + case "query": + fallthrough + case "header": + fallthrough + case "path": + fallthrough + case "formData": + fallthrough + case "body": + break + default: + fmt.Fprintf(os.Stderr, "[%s.%s] Unknow param location: %s, Possible values are `query`, `header`, `path`, `formData` or `body`.\n", controllerName, funcName, p[1]) + } + para.In = p[1] pp := strings.Split(p[2], ".") - para.DataType = pp[len(pp)-1] + typ := pp[len(pp)-1] + if len(pp) >= 2 { + cmpath, m, mod, realTypes := getModel(p[2]) + para.Schema = &swagger.Schema{ + Ref: "#/definitions/" + m, + } + if _, ok := modelsList[pkgpath+controllerName]; !ok { + modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0) + } + modelsList[pkgpath+controllerName][typ] = mod + appendModels(cmpath, pkgpath, controllerName, realTypes) + } else { + if typ == "string" || typ == "number" || typ == "integer" || typ == "boolean" || + typ == "array" || typ == "file" { + para.Type = typ + } else if sType, ok := basicTypes[typ]; ok { + typeFormat := strings.Split(sType, ":") + para.Type = typeFormat[0] + para.Format = typeFormat[1] + } else { + fmt.Fprintf(os.Stderr, "[%s.%s] Unknow param type: %s\n", controllerName, funcName, typ) + } + } if len(p) > 4 { para.Required, _ = strconv.ParseBool(p[3]) - para.Description = p[4] + para.Description = strings.Trim(p[4], `" `) } else { - para.Description = p[3] + para.Description = strings.Trim(p[3], `" `) } opts.Parameters = append(opts.Parameters, para) } else if strings.HasPrefix(t, "@Failure") { - rs := swagger.ResponseMessage{} + rs := swagger.Response{} st := strings.TrimSpace(t[len("@Failure"):]) var cd []rune var start bool for i, s := range st { if unicode.IsSpace(s) { if start { - rs.Message = strings.TrimSpace(st[i+1:]) + rs.Description = strings.TrimSpace(st[i+1:]) break } else { continue @@ -463,10 +495,9 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat start = true cd = append(cd, s) } - rs.Code, _ = strconv.Atoi(string(cd)) - opts.ResponseMessages = append(opts.ResponseMessages, rs) - } else if strings.HasPrefix(t, "@Type") { - opts.Type = strings.TrimSpace(t[len("@Type"):]) + opts.Responses[string(cd)] = rs + } else if strings.HasPrefix(t, "@Deprecated") { + opts.Deprecated, _ = strconv.ParseBool(strings.TrimSpace(t[len("@Deprecated"):])) } else if strings.HasPrefix(t, "@Accept") { accepts := strings.Split(strings.TrimSpace(strings.TrimSpace(t[len("@Accept"):])), ",") for _, a := range accepts { @@ -488,14 +519,35 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat } } } - innerapi.Operations = append(innerapi.Operations, opts) - if innerapi.Path != "" { - if _, ok := controllerList[pkgpath+controllerName]; ok { - controllerList[pkgpath+controllerName] = append(controllerList[pkgpath+controllerName], innerapi) + if routerPath != "" { + var item *swagger.Item + if itemList, ok := controllerList[pkgpath+controllerName]; ok { + if it, ok := itemList[routerPath]; !ok { + item = &swagger.Item{} + } else { + item = it + } } else { - controllerList[pkgpath+controllerName] = make([]swagger.API, 1) - controllerList[pkgpath+controllerName][0] = innerapi + controllerList[pkgpath+controllerName] = make(map[string]*swagger.Item) + item = &swagger.Item{} } + switch HTTPMethod { + case "GET": + item.Get = &opts + case "POST": + item.Post = &opts + case "PUT": + item.Put = &opts + case "PATCH": + item.Patch = &opts + case "DELETE": + item.Delete = &opts + case "HEAD": + item.Head = &opts + case "OPTIONS": + item.Options = &opts + } + controllerList[pkgpath+controllerName][routerPath] = item } return nil } @@ -531,7 +583,7 @@ func getparams(str string) []string { return r } -func getModel(str string) (pkgpath, objectname string, m swagger.Model, realTypes []string) { +func getModel(str string) (pkgpath, objectname string, m swagger.Schema, realTypes []string) { strs := strings.Split(str, ".") objectname = strs[len(strs)-1] pkgpath = strings.Join(strs[:len(strs)-1], "/") @@ -547,7 +599,7 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Model, realType ColorLog("[ERRO] the model %s parser.ParseDir error\n", str) os.Exit(1) } - + m.Type = "object" for _, pkg := range astPkgs { for _, fl := range pkg.Files { for k, d := range fl.Scope.Objects { @@ -564,24 +616,36 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Model, realType if !ok { continue } - m.ID = k + m.Title = k if st.Fields.List != nil { - m.Properties = make(map[string]swagger.ModelProperty) + m.Properties = make(map[string]swagger.Propertie) for _, field := range st.Fields.List { - isSlice, realType := typeAnalyser(field) + isSlice, realType, sType := typeAnalyser(field) realTypes = append(realTypes, realType) - mp := swagger.ModelProperty{} + mp := swagger.Propertie{} // add type slice if isSlice { + mp.Type = "array" if isBasicType(realType) { - mp.Type = "[]" + realType + typeFormat := strings.Split(sType, ":") + mp.Items = &swagger.Propertie{ + Type: typeFormat[0], + Format: typeFormat[1], + } + } else { - mp.Type = "array" - mp.Items = make(map[string]string) - mp.Items["$ref"] = realType + mp.Items = &swagger.Propertie{ + Ref: "#/definitions/" + realType, + } } } else { - mp.Type = realType + if isBasicType(realType) { + typeFormat := strings.Split(sType, ":") + mp.Type = typeFormat[0] + mp.Format = typeFormat[1] + } else if sType == "object" { + mp.Ref = "#/definitions/" + realType + } } // dont add property if anonymous field @@ -633,62 +697,67 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Model, realType } } } - return } } } } - if m.ID == "" { - ColorLog("can't find the object: %v", str) + if m.Title == "" { + ColorLog("can't find the object: %s", str) os.Exit(1) } + if len(rootapi.Definitions) == 0 { + rootapi.Definitions = make(map[string]swagger.Schema) + } + rootapi.Definitions[objectname] = m return } -func typeAnalyser(f *ast.Field) (isSlice bool, realType string) { +func typeAnalyser(f *ast.Field) (isSlice bool, realType, swaggerType string) { if arr, ok := f.Type.(*ast.ArrayType); ok { if isBasicType(fmt.Sprint(arr.Elt)) { - return false, fmt.Sprintf("[]%v", arr.Elt) + return false, fmt.Sprintf("[]%v", arr.Elt), basicTypes[fmt.Sprint(arr.Elt)] } if mp, ok := arr.Elt.(*ast.MapType); ok { - return false, fmt.Sprintf("map[%v][%v]", mp.Key, mp.Value) + return false, fmt.Sprintf("map[%v][%v]", mp.Key, mp.Value), "object" } if star, ok := arr.Elt.(*ast.StarExpr); ok { - return true, fmt.Sprint(star.X) - } else { - return true, fmt.Sprint(arr.Elt) + return true, fmt.Sprint(star.X), "object" } - } else { - switch t := f.Type.(type) { - case *ast.StarExpr: - return false, fmt.Sprint(t.X) - } - return false, fmt.Sprint(f.Type) + return true, fmt.Sprint(arr.Elt), "object" } + switch t := f.Type.(type) { + case *ast.StarExpr: + return false, fmt.Sprint(t.X), "object" + case *ast.MapType: + return false, fmt.Sprint(t.Value), "object" + } + if k, ok := basicTypes[fmt.Sprint(f.Type)]; ok { + return false, fmt.Sprint(f.Type), k + } + return false, fmt.Sprint(f.Type), "object" } func isBasicType(Type string) bool { - for _, v := range basicTypes { - if v == Type { - return true - } + if _, ok := basicTypes[Type]; ok { + return true } return false } // refer to builtin.go -var basicTypes = []string{ - "bool", - "uint", "uint8", "uint16", "uint32", "uint64", - "int", "int8", "int16", "int32", "int64", - "float32", "float64", - "string", - "complex64", "complex128", - "byte", "rune", "uintptr", +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 { +func grepJSONTag(tag string) string { r, _ := regexp.Compile(`json:"([^"]*)"`) matches := r.FindAllStringSubmatch(tag, -1) if len(matches) > 0 { @@ -718,3 +787,17 @@ func appendModels(cmpath, pkgpath, controllerName string, realTypes []string) { } } } + +func urlReplace(src string) string { + pt := strings.Split(src, "/") + for i, p := range pt { + if len(p) > 0 { + if p[0] == ':' { + pt[i] = "{" + p[1:] + "}" + } else if p[0] == '?' && p[1] == ':' { + pt[i] = "{" + p[2:] + "}" + } + } + } + return strings.Join(pt, "/") +} diff --git a/g_hproseappcode.go b/g_hproseappcode.go index 86e3d02..2d95aec 100644 --- a/g_hproseappcode.go +++ b/g_hproseappcode.go @@ -19,6 +19,7 @@ package main import ( "database/sql" + "fmt" "os" "path" "strings" @@ -31,11 +32,11 @@ func generateHproseAppcode(driver, connStr, level, tables, currpath string) { var mode byte switch level { case "1": - mode = O_MODEL + mode = OModel case "2": - mode = O_MODEL | O_CONTROLLER + mode = OModel | OController case "3": - mode = O_MODEL | O_CONTROLLER | O_ROUTER + mode = OModel | OController | ORouter default: ColorLog("[ERRO] Invalid 'level' option: %s\n", level) ColorLog("[HINT] Level must be either 1, 2 or 3\n") @@ -90,7 +91,7 @@ func genHprose(dbms, connStr string, mode byte, selectedTableNames map[string]bo // It will wipe the following directories and recreate them:./models, ./controllers, ./routers // Newly geneated files will be inside these folders. func writeHproseSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) { - if (O_MODEL & mode) == O_MODEL { + if (OModel & mode) == OModel { ColorLog("[INFO] Creating model files...\n") writeHproseModelFiles(tables, paths.ModelPath, selectedTables) } @@ -98,6 +99,8 @@ func writeHproseSourceFiles(pkgPath string, tables []*Table, mode byte, paths *M // writeHproseModelFiles generates model files func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[string]bool) { + w := NewColorWriter(os.Stdout) + for _, tb := range tables { // if selectedTables map is not nil and this table is not selected, ignore it if selectedTables != nil { @@ -110,7 +113,7 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str var f *os.File var err error if isExist(fpath) { - ColorLog("[WARN] %v is exist, do you want to overwrite it? Yes or No?\n", fpath) + ColorLog("[WARN] '%v' already exists. Do you want to overwrite it? [Yes|No] ", fpath) if askForConfirmation() { f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) if err != nil { @@ -118,7 +121,7 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str continue } } else { - ColorLog("[WARN] skip create file\n") + ColorLog("[WARN] Skipped create file '%s'\n", fpath) continue } } else { @@ -130,10 +133,10 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str } template := "" if tb.Pk == "" { - template = HPROSE_STRUCT_MODEL_TPL + template = HproseStructModelTPL } else { - template = HPROSE_MODEL_TPL - hproseAddFunctions = append(hproseAddFunctions, strings.Replace(HPROSE_ADDFUNCTION, "{{modelName}}", camelCase(tb.Name), -1)) + template = HproseModelTPL + hproseAddFunctions = append(hproseAddFunctions, strings.Replace(HproseAddFunction, "{{modelName}}", camelCase(tb.Name), -1)) } fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1) fileStr = strings.Replace(fileStr, "{{modelName}}", camelCase(tb.Name), -1) @@ -147,17 +150,17 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str fileStr = strings.Replace(fileStr, "{{timePkg}}", timePkg, -1) fileStr = strings.Replace(fileStr, "{{importTimePkg}}", importTimePkg, -1) if _, err := f.WriteString(fileStr); err != nil { - ColorLog("[ERRO] Could not write model file to %s\n", fpath) + ColorLog("[ERRO] Could not write model file to '%s'\n", fpath) os.Exit(2) } - f.Close() - ColorLog("[INFO] model => %s\n", fpath) + CloseFile(f) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") formatSourceCode(fpath) } } const ( - HPROSE_ADDFUNCTION = ` + HproseAddFunction = ` // publish about {{modelName}} function service.AddFunction("Add{{modelName}}", models.Add{{modelName}}) service.AddFunction("Get{{modelName}}ById", models.Get{{modelName}}ById) @@ -166,12 +169,12 @@ const ( service.AddFunction("Delete{{modelName}}", models.Delete{{modelName}}) ` - HPROSE_STRUCT_MODEL_TPL = `package models + HproseStructModelTPL = `package models {{importTimePkg}} {{modelStruct}} ` - HPROSE_MODEL_TPL = `package models + HproseModelTPL = `package models import ( "errors" @@ -260,7 +263,7 @@ func GetAll{{modelName}}(query map[string]string, fields []string, sortby []stri var l []{{modelName}} qs = qs.OrderBy(sortFields...) - if _, err := qs.Limit(limit, offset).All(&l, fields...); err == nil { + if _, err = qs.Limit(limit, offset).All(&l, fields...); err == nil { if len(fields) == 0 { for _, v := range l { ml = append(ml, v) diff --git a/g_migration.go b/g_migration.go index d0d296d..c0485ef 100644 --- a/g_migration.go +++ b/g_migration.go @@ -23,15 +23,170 @@ import ( ) const ( - M_PATH = "migrations" - M_DATE_FORMAT = "20060102_150405" + MPath = "migrations" + MDateFormat = "20060102_150405" + DBPath = "database" ) +type DBDriver interface { + generateCreateUp(tableName string) string + generateCreateDown(tableName string) string +} + +type mysqlDriver struct{} + +func (m mysqlDriver) generateCreateUp(tableName string) string { + upsql := `m.SQL("CREATE TABLE ` + tableName + "(" + m.generateSQLFromFields(fields.String()) + `)");` + return upsql +} + +func (m mysqlDriver) generateCreateDown(tableName string) string { + downsql := `m.SQL("DROP TABLE ` + "`" + tableName + "`" + `")` + return downsql +} + +func (m mysqlDriver) generateSQLFromFields(fields string) string { + sql, tags := "", "" + fds := strings.Split(fields, ",") + for i, v := range fds { + kv := strings.SplitN(v, ":", 2) + if len(kv) != 2 { + ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n") + return "" + } + typ, tag := m.getSQLType(kv[1]) + if typ == "" { + ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n") + return "" + } + if i == 0 && strings.ToLower(kv[0]) != "id" { + sql += "`id` int(11) NOT NULL AUTO_INCREMENT," + tags = tags + "PRIMARY KEY (`id`)," + } + sql += "`" + snakeString(kv[0]) + "` " + typ + "," + if tag != "" { + tags = tags + fmt.Sprintf(tag, "`"+snakeString(kv[0])+"`") + "," + } + } + sql = strings.TrimRight(sql+tags, ",") + return sql +} + +func (m mysqlDriver) getSQLType(ktype string) (tp, tag string) { + kv := strings.SplitN(ktype, ":", 2) + switch kv[0] { + case "string": + if len(kv) == 2 { + return "varchar(" + kv[1] + ") NOT NULL", "" + } + return "varchar(128) NOT NULL", "" + case "text": + return "longtext NOT NULL", "" + case "auto": + return "int(11) NOT NULL AUTO_INCREMENT", "" + case "pk": + return "int(11) NOT NULL", "PRIMARY KEY (%s)" + case "datetime": + return "datetime NOT NULL", "" + case "int", "int8", "int16", "int32", "int64": + fallthrough + case "uint", "uint8", "uint16", "uint32", "uint64": + return "int(11) DEFAULT NULL", "" + case "bool": + return "tinyint(1) NOT NULL", "" + case "float32", "float64": + return "float NOT NULL", "" + case "float": + return "float NOT NULL", "" + } + return "", "" +} + +type postgresqlDriver struct{} + +func (m postgresqlDriver) generateCreateUp(tableName string) string { + upsql := `m.SQL("CREATE TABLE ` + tableName + "(" + m.generateSQLFromFields(fields.String()) + `)");` + return upsql +} + +func (m postgresqlDriver) generateCreateDown(tableName string) string { + downsql := `m.SQL("DROP TABLE ` + tableName + `")` + return downsql +} + +func (m postgresqlDriver) generateSQLFromFields(fields string) string { + sql, tags := "", "" + fds := strings.Split(fields, ",") + for i, v := range fds { + kv := strings.SplitN(v, ":", 2) + if len(kv) != 2 { + ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n") + return "" + } + typ, tag := m.getSQLType(kv[1]) + if typ == "" { + ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n") + return "" + } + if i == 0 && strings.ToLower(kv[0]) != "id" { + sql += "id interger serial primary key," + } + sql += snakeString(kv[0]) + " " + typ + "," + if tag != "" { + tags = tags + fmt.Sprintf(tag, snakeString(kv[0])) + "," + } + } + if tags != "" { + sql = strings.TrimRight(sql+" "+tags, ",") + } else { + sql = strings.TrimRight(sql, ",") + } + return sql +} + +func (m postgresqlDriver) getSQLType(ktype string) (tp, tag string) { + kv := strings.SplitN(ktype, ":", 2) + switch kv[0] { + case "string": + if len(kv) == 2 { + return "char(" + kv[1] + ") NOT NULL", "" + } + return "TEXT NOT NULL", "" + case "text": + return "TEXT NOT NULL", "" + case "auto", "pk": + return "serial primary key", "" + case "datetime": + return "TIMESTAMP WITHOUT TIME ZONE NOT NULL", "" + case "int", "int8", "int16", "int32", "int64": + fallthrough + case "uint", "uint8", "uint16", "uint32", "uint64": + return "integer DEFAULT NULL", "" + case "bool": + return "boolean NOT NULL", "" + case "float32", "float64", "float": + return "numeric NOT NULL", "" + } + return "", "" +} + +func newDBDriver() DBDriver { + switch driver { + case "mysql": + return mysqlDriver{} + case "postgres": + return postgresqlDriver{} + default: + panic("driver not supported") + } +} + // generateMigration generates migration file template for database schema update. // The generated file template consists of an up() method for updating schema and // a down() method for reverting the update. func generateMigration(mname, upsql, downsql, curpath string) { - migrationFilePath := path.Join(curpath, "database", M_PATH) + w := NewColorWriter(os.Stdout) + migrationFilePath := path.Join(curpath, DBPath, MPath) if _, err := os.Stat(migrationFilePath); os.IsNotExist(err) { // create migrations directory if err := os.MkdirAll(migrationFilePath, 0777); err != nil { @@ -40,26 +195,25 @@ func generateMigration(mname, upsql, downsql, curpath string) { } } // create file - today := time.Now().Format(M_DATE_FORMAT) + today := time.Now().Format(MDateFormat) fpath := path.Join(migrationFilePath, fmt.Sprintf("%s_%s.go", today, mname)) if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { - defer f.Close() - content := strings.Replace(MIGRATION_TPL, "{{StructName}}", camelCase(mname)+"_"+today, -1) + defer CloseFile(f) + content := strings.Replace(MigrationTPL, "{{StructName}}", camelCase(mname)+"_"+today, -1) content = strings.Replace(content, "{{CurrTime}}", today, -1) content = strings.Replace(content, "{{UpSQL}}", upsql, -1) content = strings.Replace(content, "{{DownSQL}}", downsql, -1) f.WriteString(content) - // gofmt generated source code + // Run 'gofmt' on the generated source code formatSourceCode(fpath) - ColorLog("[INFO] Migration file generated: %s\n", fpath) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") } else { - // error creating file ColorLog("[ERRO] Could not create migration file: %s\n", err) os.Exit(2) } } -const MIGRATION_TPL = `package main +const MigrationTPL = `package main import ( "github.com/astaxie/beego/migration" diff --git a/g_model.go b/g_model.go index 859164f..ae23731 100644 --- a/g_model.go +++ b/g_model.go @@ -1,13 +1,30 @@ +// Copyright 2013 bee authors +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + package main import ( "errors" + "fmt" "os" "path" "strings" ) -func generateModel(mname, fields, crupath string) { +func generateModel(mname, fields, currpath string) { + w := NewColorWriter(os.Stdout) + p, f := path.Split(mname) modelName := strings.Title(f) packageName := "models" @@ -15,24 +32,28 @@ func generateModel(mname, fields, crupath string) { i := strings.LastIndex(p[:len(p)-1], "/") packageName = p[i+1 : len(p)-1] } - modelStruct, err, hastime := getStruct(modelName, fields) + + modelStruct, hastime, err := getStruct(modelName, fields) if err != nil { - ColorLog("[ERRO] Could not genrate models struct: %s\n", err) + ColorLog("[ERRO] Could not generate the model struct: %s\n", err) os.Exit(2) } + ColorLog("[INFO] Using '%s' as model name\n", modelName) ColorLog("[INFO] Using '%s' as package name\n", packageName) - fp := path.Join(crupath, "models", p) + + fp := path.Join(currpath, "models", p) if _, err := os.Stat(fp); os.IsNotExist(err) { - // create controller directory + // Create the model's directory if err := os.MkdirAll(fp, 0777); err != nil { - ColorLog("[ERRO] Could not create models directory: %s\n", err) + ColorLog("[ERRO] Could not create the model directory: %s\n", err) os.Exit(2) } } + fpath := path.Join(fp, strings.ToLower(modelName)+".go") if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { - defer f.Close() + defer CloseFile(f) content := strings.Replace(modelTpl, "{{packageName}}", packageName, -1) content = strings.Replace(content, "{{modelName}}", modelName, -1) content = strings.Replace(content, "{{modelStruct}}", modelStruct, -1) @@ -42,42 +63,45 @@ func generateModel(mname, fields, crupath string) { content = strings.Replace(content, "{{timePkg}}", "", -1) } f.WriteString(content) - // gofmt generated source code + // Run 'gofmt' on the generated source code formatSourceCode(fpath) - ColorLog("[INFO] model file generated: %s\n", fpath) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") } else { - // error creating file ColorLog("[ERRO] Could not create model file: %s\n", err) os.Exit(2) } } -func getStruct(structname, fields string) (string, error, bool) { +func getStruct(structname, fields string) (string, bool, error) { if fields == "" { - return "", errors.New("fields can't empty"), false + return "", false, errors.New("fields cannot be empty") } + hastime := false structStr := "type " + structname + " struct{\n" fds := strings.Split(fields, ",") for i, v := range fds { kv := strings.SplitN(v, ":", 2) if len(kv) != 2 { - return "", errors.New("the filds format is wrong. should key:type,key:type " + v), false + return "", false, errors.New("the fields format is wrong. Should be key:type,key:type " + v) } + typ, tag, hastimeinner := getType(kv[1]) if typ == "" { - return "", errors.New("the filds format is wrong. should key:type,key:type " + v), false + return "", false, errors.New("the fields format is wrong. Should be key:type,key:type " + v) } + if i == 0 && strings.ToLower(kv[0]) != "id" { structStr = structStr + "Id int64 `orm:\"auto\"`\n" } + if hastimeinner { hastime = true } structStr = structStr + camelString(kv[0]) + " " + typ + " " + tag + "\n" } structStr += "}\n" - return structStr, nil, hastime + return structStr, hastime, nil } // fields support type @@ -88,9 +112,8 @@ func getType(ktype string) (kt, tag string, hasTime bool) { case "string": if len(kv) == 2 { return "string", "`orm:\"size(" + kv[1] + ")\"`", false - } else { - return "string", "`orm:\"size(128)\"`", false } + return "string", "`orm:\"size(128)\"`", false case "text": return "string", "`orm:\"type(longtext)\"`", false case "auto": @@ -202,7 +225,7 @@ func GetAll{{modelName}}(query map[string]string, fields []string, sortby []stri var l []{{modelName}} qs = qs.OrderBy(sortFields...) - if _, err := qs.Limit(limit, offset).All(&l, fields...); err == nil { + if _, err = qs.Limit(limit, offset).All(&l, fields...); err == nil { if len(fields) == 0 { for _, v := range l { ml = append(ml, v) diff --git a/g_scaffold.go b/g_scaffold.go index a9df9d8..cccda16 100644 --- a/g_scaffold.go +++ b/g_scaffold.go @@ -1,101 +1,48 @@ package main -import ( - "fmt" - "strings" -) +import "strings" -func generateScaffold(sname, fields, crupath, driver, conn string) { - // generate model - ColorLog("[INFO] Do you want me to create a %v model? [yes|no]] ", sname) +func generateScaffold(sname, fields, currpath, driver, conn string) { + ColorLog("[INFO] Do you want to create a '%v' model? [Yes|No] ", sname) + + // Generate the model if askForConfirmation() { - generateModel(sname, fields, crupath) + generateModel(sname, fields, currpath) } - // generate controller - ColorLog("[INFO] Do you want me to create a %v controller? [yes|no]] ", sname) + // Generate the controller + ColorLog("[INFO] Do you want to create a '%v' controller? [Yes|No] ", sname) if askForConfirmation() { - generateController(sname, crupath) + generateController(sname, currpath) } - // generate view - ColorLog("[INFO] Do you want me to create views for this %v resource? [yes|no]] ", sname) + + // Generate the views + ColorLog("[INFO] Do you want to create views for this '%v' resource? [Yes|No] ", sname) if askForConfirmation() { - generateView(sname, crupath) + generateView(sname, currpath) } - // generate migration - ColorLog("[INFO] Do you want me to create a %v migration and schema for this resource? [yes|no]] ", sname) + + // Generate a migration + ColorLog("[INFO] Do you want to create a '%v' migration and schema for this resource? [Yes|No] ", sname) if askForConfirmation() { upsql := "" downsql := "" if fields != "" { - upsql = `m.SQL("CREATE TABLE ` + sname + "(" + generateSQLFromFields(fields) + `)");` - downsql = `m.SQL("DROP TABLE ` + "`" + sname + "`" + `")` + dbMigrator := newDBDriver() + upsql = dbMigrator.generateCreateUp(sname) + downsql = dbMigrator.generateCreateDown(sname) + //todo remove + //if driver == "" { + // downsql = strings.Replace(downsql, "`", "", -1) + //} } - generateMigration(sname, upsql, downsql, crupath) + generateMigration(sname, upsql, downsql, currpath) } - // run migration - ColorLog("[INFO] Do you want to go ahead and migrate the database? [yes|no]] ") + + // Run the migration + ColorLog("[INFO] Do you want to migrate the database? [Yes|No] ") if askForConfirmation() { - migrateUpdate(crupath, driver, conn) + migrateUpdate(currpath, driver, conn) } ColorLog("[INFO] All done! Don't forget to add beego.Router(\"/%v\" ,&controllers.%vController{}) to routers/route.go\n", sname, strings.Title(sname)) } - -func generateSQLFromFields(fields string) string { - sql := "" - tags := "" - fds := strings.Split(fields, ",") - for i, v := range fds { - kv := strings.SplitN(v, ":", 2) - if len(kv) != 2 { - ColorLog("[ERRO] the fields format is wrong. should key:type,key:type " + v + "\n") - return "" - } - typ, tag := getSqlType(kv[1]) - if typ == "" { - ColorLog("[ERRO] the fields format is wrong. should key:type,key:type " + v + "\n") - return "" - } - if i == 0 && strings.ToLower(kv[0]) != "id" { - sql = sql + "`id` int(11) NOT NULL AUTO_INCREMENT," - tags = tags + "PRIMARY KEY (`id`)," - } - sql = sql + "`" + snakeString(kv[0]) + "` " + typ + "," - if tag != "" { - tags = tags + fmt.Sprintf(tag, "`"+snakeString(kv[0])+"`") + "," - } - } - sql = strings.TrimRight(sql+tags, ",") - return sql -} - -func getSqlType(ktype string) (tp, tag string) { - kv := strings.SplitN(ktype, ":", 2) - switch kv[0] { - case "string": - if len(kv) == 2 { - return "varchar(" + kv[1] + ") NOT NULL", "" - } else { - return "varchar(128) NOT NULL", "" - } - case "text": - return "longtext NOT NULL", "" - case "auto": - return "int(11) NOT NULL AUTO_INCREMENT", "" - case "pk": - return "int(11) NOT NULL", "PRIMARY KEY (%s)" - case "datetime": - return "datetime NOT NULL", "" - case "int", "int8", "int16", "int32", "int64": - fallthrough - case "uint", "uint8", "uint16", "uint32", "uint64": - return "int(11) DEFAULT NULL", "" - case "bool": - return "tinyint(1) NOT NULL", "" - case "float32", "float64": - return "float NOT NULL", "" - case "float": - return "float NOT NULL", "" - } - return "", "" -} diff --git a/g_views.go b/g_views.go index 413c86f..c078223 100644 --- a/g_views.go +++ b/g_views.go @@ -1,47 +1,74 @@ +// Copyright 2013 bee authors +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + package main import ( + "fmt" "os" "path" ) // recipe // admin/recipe -func generateView(vpath, crupath string) { - absvpath := path.Join(crupath, "views", vpath) - os.MkdirAll(absvpath, os.ModePerm) - cfile := path.Join(absvpath, "index.tpl") +func generateView(viewpath, currpath string) { + w := NewColorWriter(os.Stdout) + + ColorLog("[INFO] Generating view...\n") + + absViewPath := path.Join(currpath, "views", viewpath) + err := os.MkdirAll(absViewPath, os.ModePerm) + if err != nil { + ColorLog("[ERRO] Could not create '%s' view: %s\n", viewpath, err) + os.Exit(2) + } + + cfile := path.Join(absViewPath, "index.tpl") if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { - defer f.Close() + defer CloseFile(f) f.WriteString(cfile) - ColorLog("[INFO] Created: %v\n", cfile) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m") } else { ColorLog("[ERRO] Could not create view file: %s\n", err) os.Exit(2) } - cfile = path.Join(absvpath, "show.tpl") + + cfile = path.Join(absViewPath, "show.tpl") if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { - defer f.Close() + defer CloseFile(f) f.WriteString(cfile) - ColorLog("[INFO] Created: %v\n", cfile) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m") } else { ColorLog("[ERRO] Could not create view file: %s\n", err) os.Exit(2) } - cfile = path.Join(absvpath, "create.tpl") + + cfile = path.Join(absViewPath, "create.tpl") if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { - defer f.Close() + defer CloseFile(f) f.WriteString(cfile) - ColorLog("[INFO] Created: %v\n", cfile) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m") } else { ColorLog("[ERRO] Could not create view file: %s\n", err) os.Exit(2) } - cfile = path.Join(absvpath, "edit.tpl") + + cfile = path.Join(absViewPath, "edit.tpl") if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { - defer f.Close() + defer CloseFile(f) f.WriteString(cfile) - ColorLog("[INFO] Created: %v\n", cfile) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m") } else { ColorLog("[ERRO] Could not create view file: %s\n", err) os.Exit(2) diff --git a/hproseapp.go b/hproseapp.go index 5167c31..e6f7e1c 100644 --- a/hproseapp.go +++ b/hproseapp.go @@ -28,7 +28,7 @@ var cmdHproseapp = &Command{ // CustomFlags: true, UsageLine: "hprose [appname]", Short: "create an rpc application use hprose base on beego framework", - Long: ` + Long: ` create an rpc application use hprose base on beego framework bee hprose [appname] [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test] @@ -37,7 +37,7 @@ bee hprose [appname] [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:330 -conn: the connection string used by the driver, the default is '' e.g. for mysql: root:@tcp(127.0.0.1:3306)/test e.g. for postgres: postgres://postgres:postgres@127.0.0.1:5432/postgres - + if conn is empty will create a example rpc application. otherwise generate rpc application use hprose based on an existing database. In the current path, will create a folder named [appname] @@ -255,6 +255,10 @@ func init() { } func createhprose(cmd *Command, args []string) int { + ShowShortVersionBanner() + + w := NewColorWriter(os.Stdout) + curpath, _ := os.Getwd() if len(args) > 1 { cmd.Flag.Parse(args[1:]) @@ -269,12 +273,15 @@ func createhprose(cmd *Command, args []string) int { } if conn == "" { } + + ColorLog("[INFO] Creating Hprose application...\n") + os.MkdirAll(apppath, 0755) - fmt.Println("create app folder:", apppath) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath, "\x1b[0m") os.Mkdir(path.Join(apppath, "conf"), 0755) - fmt.Println("create conf:", path.Join(apppath, "conf")) - fmt.Println("create conf app.conf:", path.Join(apppath, "conf", "app.conf")) - writetofile(path.Join(apppath, "conf", "app.conf"), + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf"), "\x1b[0m") + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m") + WriteToFile(path.Join(apppath, "conf", "app.conf"), strings.Replace(hproseconf, "{{.Appname}}", args[0], -1)) if conn != "" { @@ -282,7 +289,7 @@ func createhprose(cmd *Command, args []string) int { ColorLog("[INFO] Using '%s' as 'conn'\n", conn) ColorLog("[INFO] Using '%s' as 'tables'\n", tables) generateHproseAppcode(string(driver), string(conn), "1", string(tables), path.Join(curpath, args[0])) - fmt.Println("create main.go:", path.Join(apppath, "main.go")) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m") maingoContent := strings.Replace(hproseMainconngo, "{{.Appname}}", packpath, -1) maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(driver), -1) maingoContent = strings.Replace(maingoContent, "{{HproseFunctionList}}", strings.Join(hproseAddFunctions, ""), -1) @@ -291,7 +298,7 @@ func createhprose(cmd *Command, args []string) int { } else if driver == "postgres" { maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1) } - writetofile(path.Join(apppath, "main.go"), + WriteToFile(path.Join(apppath, "main.go"), strings.Replace( maingoContent, "{{.conn}}", @@ -301,17 +308,18 @@ func createhprose(cmd *Command, args []string) int { ) } else { os.Mkdir(path.Join(apppath, "models"), 0755) - fmt.Println("create models:", path.Join(apppath, "models")) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models"), "\x1b[0m") - fmt.Println("create models object.go:", path.Join(apppath, "models", "object.go")) - writetofile(path.Join(apppath, "models", "object.go"), apiModels) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "object.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "models", "object.go"), apiModels) - fmt.Println("create models user.go:", path.Join(apppath, "models", "user.go")) - writetofile(path.Join(apppath, "models", "user.go"), apiModels2) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "user.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "models", "user.go"), apiModels2) - fmt.Println("create main.go:", path.Join(apppath, "main.go")) - writetofile(path.Join(apppath, "main.go"), + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "main.go"), strings.Replace(hproseMaingo, "{{.Appname}}", packpath, -1)) } + ColorLog("[SUCC] New Hprose application successfully created!\n") return 0 } diff --git a/migrate.go b/migrate.go index 173c5b0..a32cc8e 100644 --- a/migrate.go +++ b/migrate.go @@ -62,15 +62,18 @@ func init() { // runMigration is the entry point for starting a migration func runMigration(cmd *Command, args []string) int { - crupath, _ := os.Getwd() + ShowShortVersionBanner() - gopath := os.Getenv("GOPATH") - Debugf("gopath:%s", gopath) - if gopath == "" { - ColorLog("[ERRO] $GOPATH not found\n") - ColorLog("[HINT] Set $GOPATH in your environment vairables\n") + currpath, _ := os.Getwd() + + gps := GetGOPATHs() + if len(gps) == 0 { + ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty") os.Exit(2) } + gopath := gps[0] + Debugf("GOPATH: %s", gopath) + // load config err := loadConfig() if err != nil { @@ -98,19 +101,19 @@ func runMigration(cmd *Command, args []string) int { if len(args) == 0 { // run all outstanding migrations ColorLog("[INFO] Running all outstanding migrations\n") - migrateUpdate(crupath, driverStr, connStr) + migrateUpdate(currpath, driverStr, connStr) } else { mcmd := args[0] switch mcmd { case "rollback": ColorLog("[INFO] Rolling back the last migration operation\n") - migrateRollback(crupath, driverStr, connStr) + migrateRollback(currpath, driverStr, connStr) case "reset": ColorLog("[INFO] Reseting all migrations\n") - migrateReset(crupath, driverStr, connStr) + migrateReset(currpath, driverStr, connStr) case "refresh": ColorLog("[INFO] Refreshing all migrations\n") - migrateRefresh(crupath, driverStr, connStr) + migrateRefresh(currpath, driverStr, connStr) default: ColorLog("[ERRO] Command is missing\n") os.Exit(2) @@ -121,28 +124,28 @@ func runMigration(cmd *Command, args []string) int { } // migrateUpdate does the schema update -func migrateUpdate(crupath, driver, connStr string) { - migrate("upgrade", crupath, driver, connStr) +func migrateUpdate(currpath, driver, connStr string) { + migrate("upgrade", currpath, driver, connStr) } // migrateRollback rolls back the latest migration -func migrateRollback(crupath, driver, connStr string) { - migrate("rollback", crupath, driver, connStr) +func migrateRollback(currpath, driver, connStr string) { + migrate("rollback", currpath, driver, connStr) } // migrateReset rolls back all migrations -func migrateReset(crupath, driver, connStr string) { - migrate("reset", crupath, driver, connStr) +func migrateReset(currpath, driver, connStr string) { + migrate("reset", currpath, driver, connStr) } // migrationRefresh rolls back all migrations and start over again -func migrateRefresh(crupath, driver, connStr string) { - migrate("refresh", crupath, driver, connStr) +func migrateRefresh(currpath, driver, connStr string) { + migrate("refresh", currpath, driver, connStr) } // migrate generates source code, build it, and invoke the binary who does the actual migration -func migrate(goal, crupath, driver, connStr string) { - dir := path.Join(crupath, "database", "migrations") +func migrate(goal, currpath, driver, connStr string) { + dir := path.Join(currpath, "database", "migrations") binary := "m" source := binary + ".go" // connect to database @@ -165,23 +168,23 @@ func migrate(goal, crupath, driver, connStr string) { // checkForSchemaUpdateTable checks the existence of migrations table. // It checks for the proper table structures and creates the table using MYSQL_MIGRATION_DDL if it does not exist. func checkForSchemaUpdateTable(db *sql.DB, driver string) { - showTableSql := showMigrationsTableSql(driver) - if rows, err := db.Query(showTableSql); err != nil { + showTableSQL := showMigrationsTableSQL(driver) + if rows, err := db.Query(showTableSQL); err != nil { ColorLog("[ERRO] Could not show migrations table: %s\n", err) os.Exit(2) } else if !rows.Next() { // no migrations table, create anew - createTableSql := createMigrationsTableSql(driver) + createTableSQL := createMigrationsTableSQL(driver) ColorLog("[INFO] Creating 'migrations' table...\n") - if _, err := db.Query(createTableSql); err != nil { + if _, err := db.Query(createTableSQL); err != nil { ColorLog("[ERRO] Could not create migrations table: %s\n", err) os.Exit(2) } } // checking that migrations table schema are expected - selectTableSql := selectMigrationsTableSql(driver) - if rows, err := db.Query(selectTableSql); err != nil { + selectTableSQL := selectMigrationsTableSQL(driver) + if rows, err := db.Query(selectTableSQL); err != nil { ColorLog("[ERRO] Could not show columns of migrations table: %s\n", err) os.Exit(2) } else { @@ -217,7 +220,7 @@ func checkForSchemaUpdateTable(db *sql.DB, driver string) { } } -func showMigrationsTableSql(driver string) string { +func showMigrationsTableSQL(driver string) string { switch driver { case "mysql": return "SHOW TABLES LIKE 'migrations'" @@ -228,18 +231,18 @@ func showMigrationsTableSql(driver string) string { } } -func createMigrationsTableSql(driver string) string { +func createMigrationsTableSQL(driver string) string { switch driver { case "mysql": - return MYSQL_MIGRATION_DDL + return MYSQLMigrationDDL case "postgres": - return POSTGRES_MIGRATION_DDL + return POSTGRESMigrationDDL default: - return MYSQL_MIGRATION_DDL + return MYSQLMigrationDDL } } -func selectMigrationsTableSql(driver string) string { +func selectMigrationsTableSQL(driver string) string { switch driver { case "mysql": return "DESC migrations" @@ -288,7 +291,7 @@ func writeMigrationSourceFile(dir, source, driver, connStr string, latestTime in ColorLog("[ERRO] Could not create file: %s\n", err) os.Exit(2) } else { - content := strings.Replace(MIGRATION_MAIN_TPL, "{{DBDriver}}", driver, -1) + content := strings.Replace(MigrationMainTPL, "{{DBDriver}}", driver, -1) content = strings.Replace(content, "{{ConnStr}}", connStr, -1) content = strings.Replace(content, "{{LatestTime}}", strconv.FormatInt(latestTime, 10), -1) content = strings.Replace(content, "{{LatestName}}", latestName, -1) @@ -297,7 +300,7 @@ func writeMigrationSourceFile(dir, source, driver, connStr string, latestTime in ColorLog("[ERRO] Could not write to file: %s\n", err) os.Exit(2) } - f.Close() + CloseFile(f) } } @@ -367,7 +370,7 @@ func formatShellOutput(o string) { } const ( - MIGRATION_MAIN_TPL = `package main + MigrationMainTPL = `package main import( "os" @@ -406,7 +409,7 @@ func main(){ } ` - MYSQL_MIGRATION_DDL = ` + MYSQLMigrationDDL = ` CREATE TABLE migrations ( id_migration int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key', name varchar(255) DEFAULT NULL COMMENT 'migration name, unique', @@ -418,7 +421,7 @@ CREATE TABLE migrations ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ` - POSTGRES_MIGRATION_DDL = ` + POSTGRESMigrationDDL = ` CREATE TYPE migrations_status AS ENUM('update', 'rollback'); CREATE TABLE migrations ( diff --git a/new.go b/new.go index bba644d..e0ba92f 100644 --- a/new.go +++ b/new.go @@ -55,100 +55,67 @@ func init() { } func createApp(cmd *Command, args []string) int { - curpath, _ := os.Getwd() + ShowShortVersionBanner() + w := NewColorWriter(os.Stdout) if len(args) != 1 { ColorLog("[ERRO] Argument [appname] is missing\n") os.Exit(2) } - - gopath := os.Getenv("GOPATH") - Debugf("gopath:%s", gopath) - if gopath == "" { - ColorLog("[ERRO] $GOPATH not found\n") - ColorLog("[HINT] Set $GOPATH in your environment vairables\n") + apppath, packpath, err := checkEnv(args[0]) + if err != nil { + fmt.Println(err) os.Exit(2) } - haspath := false - appsrcpath := "" - - wgopath := path.SplitList(gopath) - for _, wg := range wgopath { - - wg = path.Join(wg, "src") - - if strings.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) { - haspath = true - appsrcpath = wg - break - } - - wg, _ = path.EvalSymlinks(wg) - - if strings.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) { - haspath = true - appsrcpath = wg - break - } - - } - - if !haspath { - ColorLog("[ERRO] Unable to create an application outside of $GOPATH%ssrc(%s%ssrc)\n", string(path.Separator), gopath, string(path.Separator)) - ColorLog("[HINT] Change your work directory by `cd ($GOPATH%ssrc)`\n", string(path.Separator)) - os.Exit(2) - } - - apppath := path.Join(curpath, args[0]) if isExist(apppath) { ColorLog("[ERRO] Path (%s) already exists\n", apppath) - ColorLog("[WARN] Do you want to overwrite it? [yes|no]]") + ColorLog("[WARN] Do you want to overwrite it? [Yes|No] ") if !askForConfirmation() { os.Exit(2) } } - fmt.Println("[INFO] Creating application...") + ColorLog("[INFO] Creating application...\n") os.MkdirAll(apppath, 0755) - fmt.Println(apppath + string(path.Separator)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath+string(path.Separator), "\x1b[0m") os.Mkdir(path.Join(apppath, "conf"), 0755) - fmt.Println(path.Join(apppath, "conf") + string(path.Separator)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf")+string(path.Separator), "\x1b[0m") os.Mkdir(path.Join(apppath, "controllers"), 0755) - fmt.Println(path.Join(apppath, "controllers") + string(path.Separator)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers")+string(path.Separator), "\x1b[0m") os.Mkdir(path.Join(apppath, "models"), 0755) - fmt.Println(path.Join(apppath, "models") + string(path.Separator)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models")+string(path.Separator), "\x1b[0m") os.Mkdir(path.Join(apppath, "routers"), 0755) - fmt.Println(path.Join(apppath, "routers") + string(path.Separator)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers")+string(path.Separator), "\x1b[0m") os.Mkdir(path.Join(apppath, "tests"), 0755) - fmt.Println(path.Join(apppath, "tests") + string(path.Separator)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests")+string(path.Separator), "\x1b[0m") os.Mkdir(path.Join(apppath, "static"), 0755) - fmt.Println(path.Join(apppath, "static") + string(path.Separator)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static")+string(path.Separator), "\x1b[0m") os.Mkdir(path.Join(apppath, "static", "js"), 0755) - fmt.Println(path.Join(apppath, "static", "js") + string(path.Separator)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "js")+string(path.Separator), "\x1b[0m") os.Mkdir(path.Join(apppath, "static", "css"), 0755) - fmt.Println(path.Join(apppath, "static", "css") + string(path.Separator)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "css")+string(path.Separator), "\x1b[0m") os.Mkdir(path.Join(apppath, "static", "img"), 0755) - fmt.Println(path.Join(apppath, "static", "img") + string(path.Separator)) - fmt.Println(path.Join(apppath, "views") + string(path.Separator)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "img")+string(path.Separator), "\x1b[0m") + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "views")+string(path.Separator), "\x1b[0m") os.Mkdir(path.Join(apppath, "views"), 0755) - fmt.Println(path.Join(apppath, "conf", "app.conf")) - writetofile(path.Join(apppath, "conf", "app.conf"), strings.Replace(appconf, "{{.Appname}}", args[0], -1)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m") + WriteToFile(path.Join(apppath, "conf", "app.conf"), strings.Replace(appconf, "{{.Appname}}", path.Base(args[0]), -1)) - fmt.Println(path.Join(apppath, "controllers", "default.go")) - writetofile(path.Join(apppath, "controllers", "default.go"), controllers) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "default.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "controllers", "default.go"), controllers) - fmt.Println(path.Join(apppath, "views", "index.tpl")) - writetofile(path.Join(apppath, "views", "index.tpl"), indextpl) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "views", "index.tpl"), "\x1b[0m") + WriteToFile(path.Join(apppath, "views", "index.tpl"), indextpl) - fmt.Println(path.Join(apppath, "routers", "router.go")) - writetofile(path.Join(apppath, "routers", "router.go"), strings.Replace(router, "{{.Appname}}", strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/"), -1)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers", "router.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "routers", "router.go"), strings.Replace(router, "{{.Appname}}", packpath, -1)) - fmt.Println(path.Join(apppath, "tests", "default_test.go")) - writetofile(path.Join(apppath, "tests", "default_test.go"), strings.Replace(test, "{{.Appname}}", strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/"), -1)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests", "default_test.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "tests", "default_test.go"), strings.Replace(test, "{{.Appname}}", packpath, -1)) - fmt.Println(path.Join(apppath, "main.go")) - writetofile(path.Join(apppath, "main.go"), strings.Replace(maingo, "{{.Appname}}", strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/"), -1)) + fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m") + WriteToFile(path.Join(apppath, "main.go"), strings.Replace(maingo, "{{.Appname}}", packpath, -1)) ColorLog("[SUCC] New application successfully created!\n") return 0 @@ -336,11 +303,12 @@ var indextpl = ` ` -func writetofile(filename, content string) { +// WriteToFile creates a file and writes content to it +func WriteToFile(filename, content string) { f, err := os.Create(filename) + defer CloseFile(f) if err != nil { panic(err) } - defer f.Close() f.WriteString(content) } diff --git a/pack.go b/pack.go index 9d66088..bdf3581 100644 --- a/pack.go +++ b/pack.go @@ -72,6 +72,7 @@ var ( buildEnvs ListOpts verbose bool format string + w io.Writer ) type ListOpts []string @@ -101,6 +102,7 @@ func init() { fs.BoolVar(&verbose, "v", false, "verbose") cmdPack.Flag = *fs cmdPack.Run = packApp + w = NewColorWriter(os.Stdout) } func exitPrint(con string) { @@ -242,13 +244,12 @@ func (wft *walkFileTree) walkLeaf(fpath string, fi os.FileInfo, err error) error if added, err := wft.wak.compress(name, fpath, fi); added { if verbose { - fmt.Printf("Compressed: %s\n", name) + fmt.Fprintf(w, "\t%s%scompressed%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", name, "\x1b[0m") } wft.allfiles[name] = true return err - } else { - return err } + return err } func (wft *walkFileTree) iterDirectory(fpath string, fi os.FileInfo) error { @@ -339,7 +340,7 @@ func (wft *tarWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) { if err != nil { return false, err } - defer fr.Close() + defer CloseFile(fr) _, err = io.Copy(tw, fr) if err != nil { return false, err @@ -375,7 +376,7 @@ func (wft *zipWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) { if err != nil { return false, err } - defer fr.Close() + defer CloseFile(fr) _, err = io.Copy(w, fr) if err != nil { return false, err @@ -397,10 +398,10 @@ func (wft *zipWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) { func packDirectory(excludePrefix []string, excludeSuffix []string, excludeRegexp []*regexp.Regexp, includePath ...string) (err error) { - fmt.Printf("exclude relpath prefix: %s\n", strings.Join(excludePrefix, ":")) - fmt.Printf("exclude relpath suffix: %s\n", strings.Join(excludeSuffix, ":")) + ColorLog("Excluding relpath prefix: %s\n", strings.Join(excludePrefix, ":")) + ColorLog("Excluding relpath suffix: %s\n", strings.Join(excludeSuffix, ":")) if len(excludeRegexp) > 0 { - fmt.Printf("exclude filename regex: `%s`\n", strings.Join(excludeR, "`, `")) + ColorLog("Excluding filename regex: `%s`\n", strings.Join(excludeR, "`, `")) } w, err := os.OpenFile(outputP, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) @@ -472,6 +473,8 @@ func isBeegoProject(thePath string) bool { } func packApp(cmd *Command, args []string) int { + ShowShortVersionBanner() + curPath, _ := os.Getwd() thePath := "" @@ -493,17 +496,17 @@ func packApp(cmd *Command, args []string) int { thePath, err := path.Abs(appPath) if err != nil { - exitPrint(fmt.Sprintf("wrong app path: %s", thePath)) + exitPrint(fmt.Sprintf("Wrong app path: %s", thePath)) } if stat, err := os.Stat(thePath); os.IsNotExist(err) || stat.IsDir() == false { - exitPrint(fmt.Sprintf("not exist app path: %s", thePath)) + exitPrint(fmt.Sprintf("App path does not exist: %s", thePath)) } if isBeegoProject(thePath) == false { - exitPrint(fmt.Sprintf("not support non beego project")) + exitPrint(fmt.Sprintf("Bee does not support non Beego project")) } - fmt.Printf("app path: %s\n", thePath) + ColorLog("Packaging application: %s\n", thePath) appName := path.Base(thePath) @@ -523,8 +526,7 @@ func packApp(cmd *Command, args []string) int { os.Mkdir(tmpdir, 0700) if build { - fmt.Println("build", appName) - + ColorLog("Building application...\n") var envs []string for _, env := range buildEnvs { parts := strings.SplitN(env, "=", 2) @@ -546,7 +548,7 @@ func packApp(cmd *Command, args []string) int { os.Setenv("GOOS", goos) os.Setenv("GOARCH", goarch) - fmt.Println("GOOS", goos, "GOARCH", goarch) + ColorLog("Env: GOOS=%s GOARCH=%s\n", goos, goarch) binPath := path.Join(tmpdir, appName) if goos == "windows" { @@ -559,7 +561,7 @@ func packApp(cmd *Command, args []string) int { } if verbose { - fmt.Println("go ", strings.Join(args, " ")) + fmt.Fprintf(w, "\t%s%s+ go %s%s%s\n", "\x1b[32m", "\x1b[1m", strings.Join(args, " "), "\x1b[21m", "\x1b[0m") } execmd := exec.Command("go", args...) @@ -572,7 +574,7 @@ func packApp(cmd *Command, args []string) int { exitPrint(err.Error()) } - fmt.Println("build success") + ColorLog("Build successful\n") } switch format { @@ -624,6 +626,6 @@ func packApp(cmd *Command, args []string) int { exitPrint(err.Error()) } - fmt.Printf("file write to `%s`\n", outputP) + ColorLog("Writing to output: `%s`\n", outputP) return 0 } diff --git a/run.go b/run.go index 3d4da70..b2f94da 100644 --- a/run.go +++ b/run.go @@ -17,16 +17,14 @@ package main import ( "fmt" "io/ioutil" - "log" "os" - "os/exec" path "path/filepath" "runtime" "strings" ) var cmdRun = &Command{ - UsageLine: "run [appname] [watchall] [-main=*.go] [-downdoc=true] [-gendoc=true] [-e=Godeps -e=folderToExclude] [-tags=goBuildTags]", + UsageLine: "run [appname] [watchall] [-main=*.go] [-downdoc=true] [-gendoc=true] [-vendor=true] [-e=folderToExclude] [-tags=goBuildTags]", Short: "run the app and start a Web server for development", Long: ` Run command will supervise the file system of the beego project using inotify, @@ -35,16 +33,25 @@ it will recompile and restart the app after any modifications. `, } -var mainFiles ListOpts - -var downdoc docValue -var gendoc docValue - -// The flags list of the paths excluded from watching -var excludedPaths strFlags - -// Pass through to -tags arg of "go build" -var buildTags string +var ( + mainFiles ListOpts + downdoc docValue + gendoc docValue + // The flags list of the paths excluded from watching + excludedPaths strFlags + // Pass through to -tags arg of "go build" + buildTags string + // Application path + currpath string + // Application name + appname string + // Channel to signal an Exit + exit chan bool + // Flag to watch the vendor folder + vendorWatch bool + // Current user workspace + currentGoPath string +) func init() { cmdRun.Run = runApp @@ -52,58 +59,59 @@ func init() { cmdRun.Flag.Var(&gendoc, "gendoc", "auto generate the docs") cmdRun.Flag.Var(&downdoc, "downdoc", "auto download swagger file when not exist") cmdRun.Flag.Var(&excludedPaths, "e", "Excluded paths[].") + cmdRun.Flag.BoolVar(&vendorWatch, "vendor", false, "Watch vendor folder") cmdRun.Flag.StringVar(&buildTags, "tags", "", "Build tags (https://golang.org/pkg/go/build/)") + exit = make(chan bool) } -var appname string - func runApp(cmd *Command, args []string) int { - fmt.Println("bee :" + version) - fmt.Println("beego :" + getbeegoVersion()) - goversion, err := exec.Command("go", "version").Output() - if err != nil { - log.Fatal(err) - } - fmt.Println("Go :" + string(goversion)) - - exit := make(chan bool) - crupath, _ := os.Getwd() + ShowShortVersionBanner() if len(args) == 0 || args[0] == "watchall" { - appname = path.Base(crupath) - ColorLog("[INFO] Uses '%s' as 'appname'\n", appname) + currpath, _ = os.Getwd() + + if found, _gopath, _ := SearchGOPATHs(currpath); found { + appname = path.Base(currpath) + currentGoPath = _gopath + } else { + exitPrint(fmt.Sprintf("Bee does not support non Beego project: %s", currpath)) + } + ColorLog("[INFO] Using '%s' as 'appname'\n", appname) } else { - appname = args[0] - ColorLog("[INFO] Uses '%s' as 'appname'\n", appname) - if strings.HasSuffix(appname, ".go") && isExist(path.Join(crupath, appname)) { - ColorLog("[WARN] The appname has conflic with crupath's file, do you want to build appname as %s\n", appname) + // Check if passed Bee application path/name exists in the GOPATH(s) + if found, _gopath, _path := SearchGOPATHs(args[0]); found { + currpath = _path + currentGoPath = _gopath + appname = path.Base(currpath) + } else { + panic(fmt.Sprintf("No Beego application '%s' found in your GOPATH", args[0])) + } + + ColorLog("[INFO] Using '%s' as 'appname'\n", appname) + + if strings.HasSuffix(appname, ".go") && isExist(currpath) { + ColorLog("[WARN] The appname is in conflict with currpath's file, do you want to build appname as %s\n", appname) ColorLog("[INFO] Do you want to overwrite it? [yes|no]] ") if !askForConfirmation() { return 0 } } } - Debugf("current path:%s\n", crupath) - err = loadConfig() + Debugf("current path:%s\n", currpath) + + err := loadConfig() if err != nil { ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err) } var paths []string - - readAppDirectories(crupath, &paths) + readAppDirectories(currpath, &paths) // Because monitor files has some issues, we watch current directory // and ignore non-go files. - gps := GetGOPATHs() - if len(gps) == 0 { - ColorLog("[ERRO] Fail to start[ %s ]\n", "$GOPATH is not set or empty") - os.Exit(2) - } - gopath := gps[0] for _, p := range conf.DirStruct.Others { - paths = append(paths, strings.Replace(p, "$GOPATH", gopath, -1)) + paths = append(paths, strings.Replace(p, "$GOPATH", currentGoPath, -1)) } files := []string{} @@ -112,7 +120,14 @@ func runApp(cmd *Command, args []string) int { files = append(files, arg) } } - + if downdoc == "true" { + if _, err := os.Stat(path.Join(currpath, "swagger", "index.html")); err != nil { + if os.IsNotExist(err) { + downloadFromURL(swaggerlink, "swagger.zip") + unzipAndDelete("swagger.zip") + } + } + } if gendoc == "true" { NewWatcher(paths, files, true) Autobuild(files, true) @@ -120,14 +135,7 @@ func runApp(cmd *Command, args []string) int { NewWatcher(paths, files, false) Autobuild(files, false) } - if downdoc == "true" { - if _, err := os.Stat(path.Join(crupath, "swagger")); err != nil { - if os.IsNotExist(err) { - downloadFromUrl(swaggerlink, "swagger.zip") - unzipAndDelete("swagger.zip", "swagger") - } - } - } + for { select { case <-exit: @@ -147,6 +155,13 @@ func readAppDirectories(directory string, paths *[]string) { if strings.HasSuffix(fileInfo.Name(), "docs") { continue } + if strings.HasSuffix(fileInfo.Name(), "swagger") { + continue + } + + if !vendorWatch && strings.HasSuffix(fileInfo.Name(), "vendor") { + continue + } if isExcluded(path.Join(directory, fileInfo.Name())) { continue @@ -166,7 +181,6 @@ func readAppDirectories(directory string, paths *[]string) { useDirectory = true } } - return } diff --git a/rundocs.go b/rundocs.go index 13b0a9a..35eef7e 100644 --- a/rundocs.go +++ b/rundocs.go @@ -20,7 +20,7 @@ import ( "log" "net/http" "os" - "path/filepath" + "strings" ) var cmdRundocs = &Command{ @@ -33,8 +33,9 @@ var cmdRundocs = &Command{ `, } -const ( - swaggerlink = "https://github.com/beego/swagger/archive/v1.zip" +var ( + swaggerVersion = "2" + swaggerlink = "https://github.com/beego/swagger/archive/v" + swaggerVersion + ".zip" ) type docValue string @@ -59,8 +60,8 @@ func init() { func runDocs(cmd *Command, args []string) int { if isDownload == "true" { - downloadFromUrl(swaggerlink, "swagger.zip") - err := unzipAndDelete("swagger.zip", "swagger") + downloadFromURL(swaggerlink, "swagger.zip") + err := unzipAndDelete("swagger.zip") if err != nil { fmt.Println("has err exet unzipAndDelete", err) } @@ -77,40 +78,50 @@ func runDocs(cmd *Command, args []string) int { return 0 } -func downloadFromUrl(url, fileName string) { - fmt.Println("Downloading", url, "to", fileName) - - output, err := os.Create(fileName) - if err != nil { - fmt.Println("Error while creating", fileName, "-", err) +func downloadFromURL(url, fileName string) { + var down bool + if fd, err := os.Stat(fileName); err != nil && os.IsNotExist(err) { + down = true + } else if fd.Size() == int64(0) { + down = true + } else { + ColorLog("[%s] Filename %s already exist\n", INFO, fileName) return } - defer output.Close() + if down { + ColorLog("[%s]Downloading %s to %s\n", SUCC, url, fileName) + output, err := os.Create(fileName) + if err != nil { + ColorLog("[%s]Error while creating %s: %s\n", ERRO, fileName, err) + return + } + defer output.Close() - response, err := http.Get(url) - if err != nil { - fmt.Println("Error while downloading", url, "-", err) - return + response, err := http.Get(url) + if err != nil { + ColorLog("[%s]Error while downloading %s:%s\n", ERRO, url, err) + return + } + defer response.Body.Close() + + n, err := io.Copy(output, response.Body) + if err != nil { + ColorLog("[%s]Error while downloading %s:%s\n", ERRO, url, err) + return + } + ColorLog("[%s] %d bytes downloaded.\n", SUCC, n) } - defer response.Body.Close() - - n, err := io.Copy(output, response.Body) - if err != nil { - fmt.Println("Error while downloading", url, "-", err) - return - } - - fmt.Println(n, "bytes downloaded.") } -func unzipAndDelete(src, dest string) error { - fmt.Println("start to unzip file from " + src + " to " + dest) +func unzipAndDelete(src string) error { + ColorLog("[%s]start to unzip file from %s\n", INFO, src) r, err := zip.OpenReader(src) if err != nil { return err } defer r.Close() + rp := strings.NewReplacer("swagger-"+swaggerVersion, "swagger") for _, f := range r.File { rc, err := f.Open() if err != nil { @@ -118,12 +129,12 @@ func unzipAndDelete(src, dest string) error { } defer rc.Close() - path := filepath.Join(dest, f.Name) + fname := rp.Replace(f.Name) if f.FileInfo().IsDir() { - os.MkdirAll(path, f.Mode()) + os.MkdirAll(fname, f.Mode()) } else { f, err := os.OpenFile( - path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) if err != nil { return err } @@ -135,11 +146,6 @@ func unzipAndDelete(src, dest string) error { } } } - - fmt.Println("Start delete src file " + src) - err = os.RemoveAll(src) - if err != nil { - return err - } - return nil + ColorLog("[%s]Start delete src file %s\n", INFO, src) + return os.RemoveAll(src) } diff --git a/util.go b/util.go index 0f314df..fd2e1ce 100644 --- a/util.go +++ b/util.go @@ -15,13 +15,14 @@ package main import ( - "fmt" "log" "os" "path/filepath" "runtime" "strings" "time" + "path" + "fmt" ) // Go is a basic promise implementation: it wraps calls a function in a goroutine @@ -173,6 +174,34 @@ func GetGOPATHs() []string { return paths } +func SearchGOPATHs(app string) (bool, string, string) { + gps := GetGOPATHs() + if len(gps) == 0 { + ColorLog("[ERRO] Fail to start [ %s ]\n", "GOPATH environment variable is not set or empty") + os.Exit(2) + } + + // Lookup the application inside the user workspace(s) + for _, gopath := range gps { + var currentPath string + + if !strings.Contains(app, "src") { + gopathsrc := path.Join(gopath, "src") + currentPath = path.Join(gopathsrc, app) + } else { + currentPath = app + } + + if isExist(currentPath) { + if !isBeegoProject(currentPath) { + continue + } + return true, gopath, currentPath + } + } + return false, "", "" +} + // askForConfirmation uses Scanln to parse user input. A user must type in "yes" or "no" and // then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as // confirmations. If the input is not recognized, it will ask again. The function does not return @@ -258,3 +287,11 @@ func (s *strFlags) Set(value string) error { *s = append(*s, value) return nil } + +// CloseFile attempts to close the passed file +// or panics with the actual error +func CloseFile(f *os.File) { + if err := f.Close(); err != nil { + panic(err) + } +} diff --git a/version.go b/version.go index 23c461f..d9442aa 100644 --- a/version.go +++ b/version.go @@ -2,53 +2,81 @@ package main import ( "bufio" + "bytes" "fmt" "io" - "log" "os" - "os/exec" path "path/filepath" "regexp" ) var cmdVersion = &Command{ UsageLine: "version", - Short: "show the Bee, Beego and Go version", + Short: "prints the current Bee version", Long: ` -show the Bee, Beego and Go version - -bee version - bee :1.2.3 - beego :1.4.2 - Go :go version go1.3.3 linux/amd64 +Prints the current Bee, Beego and Go version alongside the platform information `, } +const verboseVersionBanner string = `%s%s______ +| ___ \ +| |_/ / ___ ___ +| ___ \ / _ \ / _ \ +| |_/ /| __/| __/ +\____/ \___| \___| v{{ .BeeVersion }}%s +%s%s +├── Beego : {{ .BeegoVersion }} +├── GoVersion : {{ .GoVersion }} +├── GOOS : {{ .GOOS }} +├── GOARCH : {{ .GOARCH }} +├── NumCPU : {{ .NumCPU }} +├── GOPATH : {{ .GOPATH }} +├── GOROOT : {{ .GOROOT }} +├── Compiler : {{ .Compiler }} +└── Date : {{ Now "Monday, 2 Jan 2006" }}%s +` + +const shortVersionBanner = `%s%s______ +| ___ \ +| |_/ / ___ ___ +| ___ \ / _ \ / _ \ +| |_/ /| __/| __/ +\____/ \___| \___| v{{ .BeeVersion }}%s +` + func init() { cmdVersion.Run = versionCmd } func versionCmd(cmd *Command, args []string) int { - fmt.Println("bee :" + version) - fmt.Println("beego :" + getbeegoVersion()) - //fmt.Println("Go :" + runtime.Version()) - goversion, err := exec.Command("go", "version").Output() - if err != nil { - log.Fatal(err) - } - fmt.Println("Go :" + string(goversion)) + ShowVerboseVersionBanner() return 0 } -func getbeegoVersion() string { +// ShowVerboseVersionBanner prints the verbose version banner +func ShowVerboseVersionBanner() { + w := NewColorWriter(os.Stdout) + coloredBanner := fmt.Sprintf(verboseVersionBanner, "\x1b[35m", "\x1b[1m", "\x1b[0m", + "\x1b[32m", "\x1b[1m", "\x1b[0m") + InitBanner(w, bytes.NewBufferString(coloredBanner)) +} + +// ShowShortVersionBanner prints the short version banner +func ShowShortVersionBanner() { + w := NewColorWriter(os.Stdout) + coloredBanner := fmt.Sprintf(shortVersionBanner, "\x1b[35m", "\x1b[1m", "\x1b[0m") + InitBanner(w, bytes.NewBufferString(coloredBanner)) +} + +func getBeegoVersion() string { gopath := os.Getenv("GOPATH") re, err := regexp.Compile(`VERSION = "([0-9.]+)"`) if err != nil { return "" } if gopath == "" { - err = fmt.Errorf("you should set GOPATH in the env") + err = fmt.Errorf("You should set GOPATH env variable") return "" } wgopath := path.SplitList(gopath) @@ -60,11 +88,11 @@ func getbeegoVersion() string { if os.IsNotExist(err) { continue } - ColorLog("[ERRO] get beego.go has error\n") + ColorLog("[ERRO] Get `beego.go` has error\n") } fd, err := os.Open(filename) if err != nil { - ColorLog("[ERRO] open beego.go has error\n") + ColorLog("[ERRO] Open `beego.go` has error\n") continue } reader := bufio.NewReader(fd) @@ -84,5 +112,5 @@ func getbeegoVersion() string { } } - return "you don't install beego,install first: github.com/astaxie/beego" + return "Beego not installed. Please install it first: https://github.com/astaxie/beego" } diff --git a/watch.go b/watch.go index 7d966bd..efde48a 100644 --- a/watch.go +++ b/watch.go @@ -17,14 +17,14 @@ package main import ( "bytes" "fmt" + "github.com/howeyc/fsnotify" "os" "os/exec" + "regexp" "runtime" "strings" "sync" "time" - - "github.com/howeyc/fsnotify" ) var ( @@ -47,8 +47,8 @@ func NewWatcher(paths []string, files []string, isgenerate bool) { case e := <-watcher.Event: isbuild := true - // Skip TMP files for Sublime Text. - if checkTMPFile(e.Name) { + // Skip ignored files + if shouldIgnoreFile(e.Name) { continue } if !checkIfWatchExt(e.Name) { @@ -121,8 +121,8 @@ func Autobuild(files []string, isgenerate bool) { defer state.Unlock() ColorLog("[INFO] Start building...\n") - path, _ := os.Getwd() - os.Chdir(path) + + os.Chdir(currpath) cmdName := "go" if conf.Gopm.Enable { @@ -230,15 +230,30 @@ func Start(appname string) { started <- true } -// checkTMPFile returns true if the event was for TMP files. -func checkTMPFile(name string) bool { - if strings.HasSuffix(strings.ToLower(name), ".tmp") { - return true +// Should ignore filenames generated by +// Emacs, Vim or SublimeText +func shouldIgnoreFile(filename string) bool { + for _, regex := range ignoredFilesRegExps { + r, err := regexp.Compile(regex) + if err != nil { + panic("Could not compile the regex: " + regex) + } + if r.MatchString(filename) { + return true + } else { + continue + } } return false } var watchExts = []string{".go"} +var ignoredFilesRegExps = []string{ + `.#(\w+).go`, + `.(\w+).go.swp`, + `(\w+).go~`, + `(\w+).tmp`, +} // checkIfWatchExt returns true if the name HasSuffix . func checkIfWatchExt(name string) bool {