1
0
mirror of https://github.com/beego/bee.git synced 2024-11-29 22:01:28 +00:00

Merge pull request #2 from beego/master

master update
This commit is contained in:
Sergey Lanzman 2016-08-24 21:55:10 +03:00 committed by GitHub
commit 2669a1bad6
31 changed files with 1745 additions and 786 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@
# Folders # Folders
_obj _obj
_test _test
.idea
# Architecture specific extensions/prefixes # Architecture specific extensions/prefixes
*.[568vq] *.[568vq]

15
Beefile Normal file
View File

@ -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"

20
Makefile Normal file
View File

@ -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 ./...

View File

@ -199,17 +199,17 @@ usage: bee migrate [Command]
bee migrate [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"] bee migrate [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"]
run all outstanding migrations 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 -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"] bee migrate rollback [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"]
rollback the last migration operation 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 -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"] bee migrate reset [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"]
rollback all migrations 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 -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"] bee migrate refresh [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"]

151
apiapp.go
View File

@ -69,7 +69,6 @@ EnableDocs = true
var apiMaingo = `package main var apiMaingo = `package main
import ( import (
_ "{{.Appname}}/docs"
_ "{{.Appname}}/routers" _ "{{.Appname}}/routers"
"github.com/astaxie/beego" "github.com/astaxie/beego"
@ -87,7 +86,6 @@ func main() {
var apiMainconngo = `package main var apiMainconngo = `package main
import ( import (
_ "{{.Appname}}/docs"
_ "{{.Appname}}/routers" _ "{{.Appname}}/routers"
"github.com/astaxie/beego" "github.com/astaxie/beego"
@ -298,7 +296,7 @@ type ObjectController struct {
beego.Controller beego.Controller
} }
// @Title create // @Title Create
// @Description create object // @Description create object
// @Param body body models.Object true "The object content" // @Param body body models.Object true "The object content"
// @Success 200 {string} models.Object.Id // @Success 200 {string} models.Object.Id
@ -342,7 +340,7 @@ func (o *ObjectController) GetAll() {
o.ServeJSON() o.ServeJSON()
} }
// @Title update // @Title Update
// @Description update the object // @Description update the object
// @Param objectId path string true "The objectid you want to update" // @Param objectId path string true "The objectid you want to update"
// @Param body body models.Object true "The body" // @Param body body models.Object true "The body"
@ -363,7 +361,7 @@ func (o *ObjectController) Put() {
o.ServeJSON() o.ServeJSON()
} }
// @Title delete // @Title Delete
// @Description delete the object // @Description delete the object
// @Param objectId path string true "The objectId you want to delete" // @Param objectId path string true "The objectId you want to delete"
// @Success 200 {string} delete success! // @Success 200 {string} delete success!
@ -391,7 +389,7 @@ type UserController struct {
beego.Controller beego.Controller
} }
// @Title createUser // @Title CreateUser
// @Description create users // @Description create users
// @Param body body models.User true "body for user content" // @Param body body models.User true "body for user content"
// @Success 200 {int} models.User.Id // @Success 200 {int} models.User.Id
@ -405,7 +403,7 @@ func (u *UserController) Post() {
u.ServeJSON() u.ServeJSON()
} }
// @Title Get // @Title GetAll
// @Description get all Users // @Description get all Users
// @Success 200 {object} models.User // @Success 200 {object} models.User
// @router / [get] // @router / [get]
@ -434,7 +432,7 @@ func (u *UserController) Get() {
u.ServeJSON() u.ServeJSON()
} }
// @Title update // @Title Update
// @Description update the user // @Description update the user
// @Param uid path string true "The uid you want to update" // @Param uid path string true "The uid you want to update"
// @Param body body models.User true "body for user content" // @Param body body models.User true "body for user content"
@ -456,7 +454,7 @@ func (u *UserController) Put() {
u.ServeJSON() u.ServeJSON()
} }
// @Title delete // @Title Delete
// @Description delete the user // @Description delete the user
// @Param uid path string true "The uid you want to delete" // @Param uid path string true "The uid you want to delete"
// @Success 200 {string} delete success! // @Success 200 {string} delete success!
@ -469,7 +467,7 @@ func (u *UserController) Delete() {
u.ServeJSON() u.ServeJSON()
} }
// @Title login // @Title Login
// @Description Logs user into the system // @Description Logs user into the system
// @Param username query string true "The username for login" // @Param username query string true "The username for login"
// @Param password query string true "The password for login" // @Param password query string true "The password for login"
@ -546,14 +544,19 @@ func init() {
} }
func createapi(cmd *Command, args []string) int { func createapi(cmd *Command, args []string) int {
curpath, _ := os.Getwd() ShowShortVersionBanner()
w := NewColorWriter(os.Stdout)
if len(args) < 1 { if len(args) < 1 {
ColorLog("[ERRO] Argument [appname] is missing\n") ColorLog("[ERRO] Argument [appname] is missing\n")
os.Exit(2) os.Exit(2)
} }
if len(args) > 1 { if len(args) > 1 {
cmd.Flag.Parse(args[1:]) cmd.Flag.Parse(args[1:])
} }
apppath, packpath, err := checkEnv(args[0]) apppath, packpath, err := checkEnv(args[0])
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -564,23 +567,23 @@ func createapi(cmd *Command, args []string) int {
} }
if conn == "" { 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")) ColorLog("[INFO] Creating API...\n")
writetofile(path.Join(apppath, "conf", "app.conf"),
strings.Replace(apiconf, "{{.Appname}}", args[0], -1)) 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 != "" { 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(apiMainconngo, "{{.Appname}}", packpath, -1)
maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(driver), -1) maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(driver), -1)
if driver == "mysql" { if driver == "mysql" {
@ -588,7 +591,7 @@ func createapi(cmd *Command, args []string) int {
} else if driver == "postgres" { } else if driver == "postgres" {
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1) maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1)
} }
writetofile(path.Join(apppath, "main.go"), WriteToFile(path.Join(apppath, "main.go"),
strings.Replace( strings.Replace(
maingoContent, maingoContent,
"{{.conn}}", "{{.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 'driver'\n", driver)
ColorLog("[INFO] Using '%s' as 'conn'\n", conn) ColorLog("[INFO] Using '%s' as 'conn'\n", conn)
ColorLog("[INFO] Using '%s' as 'tables'\n", tables) 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 { } else {
os.Mkdir(path.Join(apppath, "models"), 0755) 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) 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")) 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"), WriteToFile(path.Join(apppath, "controllers", "object.go"),
strings.Replace(apiControllers, "{{.Appname}}", packpath, -1)) strings.Replace(apiControllers, "{{.Appname}}", packpath, -1))
fmt.Println("create controllers user.go:", 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"), WriteToFile(path.Join(apppath, "controllers", "user.go"),
strings.Replace(apiControllers2, "{{.Appname}}", packpath, -1)) strings.Replace(apiControllers2, "{{.Appname}}", packpath, -1))
fmt.Println("create tests default.go:", 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"), WriteToFile(path.Join(apppath, "tests", "default_test.go"),
strings.Replace(apiTests, "{{.Appname}}", packpath, -1)) strings.Replace(apiTests, "{{.Appname}}", packpath, -1))
fmt.Println("create routers router.go:", 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"), WriteToFile(path.Join(apppath, "routers", "router.go"),
strings.Replace(apirouter, "{{.Appname}}", packpath, -1)) strings.Replace(apirouter, "{{.Appname}}", packpath, -1))
fmt.Println("create models object.go:", path.Join(apppath, "models", "object.go")) 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) WriteToFile(path.Join(apppath, "models", "object.go"), apiModels)
fmt.Println("create models user.go:", path.Join(apppath, "models", "user.go")) 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) WriteToFile(path.Join(apppath, "models", "user.go"), apiModels2)
fmt.Println("create docs doc.go:", path.Join(apppath, "docs", "doc.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, "docs", "doc.go"), "package docs") WriteToFile(path.Join(apppath, "main.go"),
fmt.Println("create main.go:", path.Join(apppath, "main.go"))
writetofile(path.Join(apppath, "main.go"),
strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1)) strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1))
} }
ColorLog("[SUCC] New API successfully created!\n")
return 0 return 0
} }
func checkEnv(appname string) (apppath, packpath string, err error) { func checkEnv(appname string) (apppath, packpath string, err error) {
curpath, err := os.Getwd() gps := GetGOPATHs()
if err != nil { if len(gps) == 0 {
return ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty")
os.Exit(2)
} }
currpath, _ := os.Getwd()
gopath := os.Getenv("GOPATH") currpath = path.Join(currpath, appname)
Debugf("gopath:%s", gopath) for _, gpath := range gps {
if gopath == "" { gsrcpath := path.Join(gpath, "src")
err = fmt.Errorf("you should set GOPATH in the env") if strings.HasPrefix(currpath, gsrcpath) {
return return currpath, currpath[len(gsrcpath)+1:], nil
}
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
} }
} }
if !haspath { // In case of multiple paths in the GOPATH, by default
err = fmt.Errorf("can't create application outside of GOPATH `%s`\n"+ // we use the first path
"you first should `cd $GOPATH%ssrc` then use create\n", gopath, string(path.Separator)) gopath := gps[0]
return 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)
apppath = path.Join(curpath, appname)
gosrcpath := path.Join(gopath, "src")
apppath = path.Join(gosrcpath, appname)
if _, e := os.Stat(apppath); os.IsNotExist(e) == false { 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 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 return
} }

16
bale.go
View File

@ -46,6 +46,8 @@ func init() {
} }
func runBale(cmd *Command, args []string) int { func runBale(cmd *Command, args []string) int {
ShowShortVersionBanner()
err := loadConfig() err := loadConfig()
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err) 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) ColorLog("[WARN] Skipped directory( %s )\n", p)
continue continue
} }
ColorLog("[INFO] Packing directory( %s )\n", p) ColorLog("[INFO] Packaging directory( %s )\n", p)
filepath.Walk(p, walkFn) filepath.Walk(p, walkFn)
} }
// Generate auto-uncompress function. // Generate auto-uncompress function.
buf := new(bytes.Buffer) 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\t\""),
strings.Join(resFiles, ",\n\t\tbale.R"))) strings.Join(resFiles, ",\n\t\tbale.R")))
@ -88,7 +90,7 @@ func runBale(cmd *Command, args []string) int {
} }
const ( const (
_BALE_HEADER = `package main BaleHeader = `package main
import( import(
"os" "os"
@ -176,7 +178,7 @@ func walkFn(resPath string, info os.FileInfo, err error) error {
defer fw.Close() defer fw.Close()
// Write header. // Write header.
fmt.Fprintf(fw, _HEADER, resPath) fmt.Fprintf(fw, Header, resPath)
// Copy and compress data. // Copy and compress data.
gz := gzip.NewWriter(&ByteWriter{Writer: fw}) gz := gzip.NewWriter(&ByteWriter{Writer: fw})
@ -184,7 +186,7 @@ func walkFn(resPath string, info os.FileInfo, err error) error {
gz.Close() gz.Close()
// Write footer. // Write footer.
fmt.Fprint(fw, _FOOTER) fmt.Fprint(fw, Footer)
resFiles = append(resFiles, resPath) resFiles = append(resFiles, resPath)
return nil return nil
@ -200,7 +202,7 @@ func filterSuffix(name string) bool {
} }
const ( const (
_HEADER = `package bale Header = `package bale
import( import(
"bytes" "bytes"
@ -210,7 +212,7 @@ import(
func R%s() []byte { func R%s() []byte {
gz, err := gzip.NewReader(bytes.NewBuffer([]byte{` gz, err := gzip.NewReader(bytes.NewBuffer([]byte{`
_FOOTER = ` Footer = `
})) }))
if err != nil { if err != nil {

73
banner.go Normal file
View File

@ -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)
}
}

2
bee.go
View File

@ -25,7 +25,7 @@ import (
"strings" "strings"
) )
const version = "1.4.1" const version = "1.5.0"
type Command struct { type Command struct {
// Run runs the command. // Run runs the command.

51
color.go Normal file
View File

@ -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
}

28
colorwriter.go Normal file
View File

@ -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)
}

427
colorwriter_windows.go Normal file
View File

@ -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
}

46
conf.go
View File

@ -16,10 +16,13 @@ package main
import ( import (
"encoding/json" "encoding/json"
"io/ioutil"
"os" "os"
"gopkg.in/yaml.v2"
) )
const CONF_VER = 0 const ConfVer = 0
var defaultConf = `{ var defaultConf = `{
"version": 0, "version": 0,
@ -50,20 +53,20 @@ var conf struct {
Install bool Install bool
} }
// Indicates whether execute "go install" before "go build". // Indicates whether execute "go install" before "go build".
GoInstall bool `json:"go_install"` GoInstall bool `json:"go_install" yaml:"go_install"`
WatchExt []string `json:"watch_ext"` WatchExt []string `json:"watch_ext" yaml:"watch_ext"`
DirStruct struct { DirStruct struct {
WatchAll bool `json:"watch_all"` WatchAll bool `json:"watch_all" yaml:"watch_all"`
Controllers string Controllers string
Models string Models string
Others []string // Other directories. Others []string // Other directories.
} `json:"dir_structure"` } `json:"dir_structure" yaml:"dir_structure"`
CmdArgs []string `json:"cmd_args"` CmdArgs []string `json:"cmd_args" yaml:"cmd_args"`
Envs []string Envs []string
Bale struct { Bale struct {
Import string Import string
Dirs []string Dirs []string
IngExt []string `json:"ignore_ext"` IngExt []string `json:"ignore_ext" yaml:"ignore_ext"`
} }
Database struct { Database struct {
Driver string Driver string
@ -73,14 +76,9 @@ var conf struct {
// loadConfig loads customized configuration. // loadConfig loads customized configuration.
func loadConfig() error { func loadConfig() error {
foundConf := false
f, err := os.Open("bee.json") f, err := os.Open("bee.json")
if err != nil { if err == nil {
// Use default.
err = json.Unmarshal([]byte(defaultConf), &conf)
if err != nil {
return err
}
} else {
defer f.Close() defer f.Close()
ColorLog("[INFO] Detected bee.json\n") ColorLog("[INFO] Detected bee.json\n")
d := json.NewDecoder(f) d := json.NewDecoder(f)
@ -88,10 +86,26 @@ func loadConfig() error {
if err != nil { if err != nil {
return err 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. // 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("[WARN] Your bee.json is out-of-date, please update!\n")
ColorLog("[HINT] Compare bee.json under bee source code path and yours\n") ColorLog("[HINT] Compare bee.json under bee source code path and yours\n")
} }

11
fix.go
View File

@ -8,6 +8,7 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"fmt"
) )
var cmdFix = &Command{ var cmdFix = &Command{
@ -25,9 +26,12 @@ func init() {
} }
func runFix(cmd *Command, args []string) int { func runFix(cmd *Command, args []string) int {
ShowShortVersionBanner()
ColorLog("[INFO] Upgrading the application...\n")
dir, err := os.Getwd() dir, err := os.Getwd()
if err != nil { 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 { filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if info.IsDir() { if info.IsDir() {
@ -42,13 +46,14 @@ func runFix(cmd *Command, args []string) int {
if strings.HasSuffix(info.Name(), ".exe") { if strings.HasSuffix(info.Name(), ".exe") {
return nil return nil
} }
ColorLog("%s\n", path)
err = fixFile(path) err = fixFile(path)
fmt.Println("\tfix\t", path)
if err != nil { if err != nil {
ColorLog("fixFile:%s\n", err) ColorLog("[ERRO] Could not fix file: %s\n", err)
} }
return err return err
}) })
ColorLog("[INFO] Upgrade done!\n")
return 0 return 0
} }

46
g.go
View File

@ -14,7 +14,10 @@
package main package main
import "os" import (
"os"
"strings"
)
var cmdGenerate = &Command{ var cmdGenerate = &Command{
UsageLine: "generate [Command]", UsageLine: "generate [Command]",
@ -32,7 +35,7 @@ bee generate model [modelname] [-fields=""]
-fields: a list of table fields. Format: field:type, ... -fields: a list of table fields. Format: field:type, ...
bee generate controller [controllerfile] bee generate controller [controllerfile]
generate RESTFul controllers generate RESTful controllers
bee generate view [viewpath] bee generate view [viewpath]
generate CRUD view in viewpath generate CRUD view in viewpath
@ -74,19 +77,21 @@ func init() {
} }
func generateCode(cmd *Command, args []string) int { func generateCode(cmd *Command, args []string) int {
curpath, _ := os.Getwd() ShowShortVersionBanner()
currpath, _ := os.Getwd()
if len(args) < 1 { if len(args) < 1 {
ColorLog("[ERRO] command is missing\n") ColorLog("[ERRO] command is missing\n")
os.Exit(2) os.Exit(2)
} }
gopath := os.Getenv("GOPATH") gps := GetGOPATHs()
Debugf("gopath:%s", gopath) if len(gps) == 0 {
if gopath == "" { ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty")
ColorLog("[ERRO] $GOPATH not found\n")
ColorLog("[HINT] Set $GOPATH in your environment vairables\n")
os.Exit(2) os.Exit(2)
} }
gopath := gps[0]
Debugf("GOPATH: %s", gopath)
gcmd := args[0] gcmd := args[0]
switch gcmd { switch gcmd {
@ -119,10 +124,9 @@ func generateCode(cmd *Command, args []string) int {
os.Exit(2) os.Exit(2)
} }
sname := args[1] sname := args[1]
ColorLog("[INFO] Using '%s' as scaffold name\n", sname) generateScaffold(sname, fields.String(), currpath, driver.String(), conn.String())
generateScaffold(sname, fields.String(), curpath, driver.String(), conn.String())
case "docs": case "docs":
generateDocs(curpath) generateDocs(currpath)
case "appcode": case "appcode":
// load config // load config
err := loadConfig() 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 'conn'\n", conn)
ColorLog("[INFO] Using '%s' as 'tables'\n", tables) ColorLog("[INFO] Using '%s' as 'tables'\n", tables)
ColorLog("[INFO] Using '%s' as 'level'\n", level) 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": case "migration":
if len(args) < 2 { if len(args) < 2 {
ColorLog("[ERRO] Wrong number of arguments\n") ColorLog("[ERRO] Wrong number of arguments\n")
@ -166,14 +170,15 @@ func generateCode(cmd *Command, args []string) int {
upsql := "" upsql := ""
downsql := "" downsql := ""
if fields != "" { if fields != "" {
upsql = `m.SQL("CREATE TABLE ` + mname + "(" + generateSQLFromFields(fields.String()) + `)");` dbMigrator := newDBDriver()
downsql = `m.SQL("DROP TABLE ` + "`" + mname + "`" + `")` upsql = dbMigrator.generateCreateUp(mname)
downsql = dbMigrator.generateCreateDown(mname)
} }
generateMigration(mname, upsql, downsql, curpath) generateMigration(mname, upsql, downsql, currpath)
case "controller": case "controller":
if len(args) == 2 { if len(args) == 2 {
cname := args[1] cname := args[1]
generateController(cname, curpath) generateController(cname, currpath)
} else { } else {
ColorLog("[ERRO] Wrong number of arguments\n") ColorLog("[ERRO] Wrong number of arguments\n")
ColorLog("[HINT] Usage: bee generate controller [controllername]\n") ColorLog("[HINT] Usage: bee generate controller [controllername]\n")
@ -192,20 +197,19 @@ func generateCode(cmd *Command, args []string) int {
os.Exit(2) os.Exit(2)
} }
sname := args[1] sname := args[1]
ColorLog("[INFO] Using '%s' as model name\n", sname) generateModel(sname, fields.String(), currpath)
generateModel(sname, fields.String(), curpath)
case "view": case "view":
if len(args) == 2 { if len(args) == 2 {
cname := args[1] cname := args[1]
generateView(cname, curpath) generateView(cname, currpath)
} else { } else {
ColorLog("[ERRO] Wrong number of arguments\n") ColorLog("[ERRO] Wrong number of arguments\n")
ColorLog("[HINT] Usage: bee generate view [viewpath]\n") ColorLog("[HINT] Usage: bee generate view [viewpath]\n")
os.Exit(2) os.Exit(2)
} }
default: 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 return 0
} }

View File

@ -29,9 +29,9 @@ import (
) )
const ( const (
O_MODEL byte = 1 << iota OModel byte = 1 << iota
O_CONTROLLER OController
O_ROUTER ORouter
) )
// DbTransformer has method to reverse engineer a database schema to restful api code // 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 var mode byte
switch level { switch level {
case "1": case "1":
mode = O_MODEL mode = OModel
case "2": case "2":
mode = O_MODEL | O_CONTROLLER mode = OModel | OController
case "3": case "3":
mode = O_MODEL | O_CONTROLLER | O_ROUTER mode = OModel | OController | ORouter
default: default:
ColorLog("[ERRO] Invalid 'level' option: %s\n", level) ColorLog("[ERRO] Invalid 'level' option: %s\n", level)
ColorLog("[HINT] Level must be either 1, 2 or 3\n") 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 // Generate takes table, column and foreign key information from database connection
// and generate corresponding golang source files // 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) db, err := sql.Open(dbms, connStr)
if err != nil { if err != nil {
ColorLog("[ERRO] Could not connect to %s database: %s, %s\n", dbms, connStr, err) 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) tableNames := trans.GetTableNames(db)
tables := getTableObjects(tableNames, db, trans) tables := getTableObjects(tableNames, db, trans)
mvcPath := new(MvcPath) mvcPath := new(MvcPath)
mvcPath.ModelPath = path.Join(currpath, "models") mvcPath.ModelPath = path.Join(apppath, "models")
mvcPath.ControllerPath = path.Join(currpath, "controllers") mvcPath.ControllerPath = path.Join(apppath, "controllers")
mvcPath.RouterPath = path.Join(currpath, "routers") mvcPath.RouterPath = path.Join(apppath, "routers")
createPaths(mode, mvcPath) createPaths(mode, mvcPath)
pkgPath := getPackagePath(currpath) pkgPath := getPackagePath(apppath)
writeSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames) writeSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames)
} else { } else {
ColorLog("[ERRO] Generating app code from %s database is not supported yet.\n", dbms) 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) { func (*MysqlDB) GetGoDataType(sqlType string) (goType string) {
var typeMapping = map[string]string{} var typeMapping = map[string]string{}
typeMapping = typeMappingMysql typeMapping = typeMappingMysql
if v, ok := typeMapping[sqlType]; ok { if v, ok := typeMapping[sqlType]; ok {
return v return v
} else { }
ColorLog("[ERRO] data type (%s) not found!\n", sqlType) ColorLog("[ERRO] data type (%s) not found!\n", sqlType)
os.Exit(2) os.Exit(2)
}
return goType return goType
} }
@ -689,25 +688,26 @@ func (postgresDB *PostgresDB) GetColumns(db *sql.DB, table *Table, blackList map
table.Columns = append(table.Columns, col) table.Columns = append(table.Columns, col)
} }
} }
// GetGoDataType returns the Go type from the mapped Postgres type
func (*PostgresDB) GetGoDataType(sqlType string) (goType string) { func (*PostgresDB) GetGoDataType(sqlType string) (goType string) {
if v, ok := typeMappingPostgres[sqlType]; ok { if v, ok := typeMappingPostgres[sqlType]; ok {
return v return v
} else { }
ColorLog("[ERRO] data type (%s) not found!\n", sqlType) ColorLog("[ERRO] data type (%s) not found!\n", sqlType)
os.Exit(2) os.Exit(2)
}
return goType return goType
} }
// deleteAndRecreatePaths removes several directories completely // deleteAndRecreatePaths removes several directories completely
func createPaths(mode byte, paths *MvcPath) { func createPaths(mode byte, paths *MvcPath) {
if (mode & O_MODEL) == O_MODEL { if (mode & OModel) == OModel {
os.Mkdir(paths.ModelPath, 0777) os.Mkdir(paths.ModelPath, 0777)
} }
if (mode & O_CONTROLLER) == O_CONTROLLER { if (mode & OController) == OController {
os.Mkdir(paths.ControllerPath, 0777) os.Mkdir(paths.ControllerPath, 0777)
} }
if (mode & O_ROUTER) == O_ROUTER { if (mode & ORouter) == ORouter {
os.Mkdir(paths.RouterPath, 0777) 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 // It will wipe the following directories and recreate them:./models, ./controllers, ./routers
// Newly geneated files will be inside these folders. // Newly geneated files will be inside these folders.
func writeSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) { 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") ColorLog("[INFO] Creating model files...\n")
writeModelFiles(tables, paths.ModelPath, selectedTables) writeModelFiles(tables, paths.ModelPath, selectedTables)
} }
if (O_CONTROLLER & mode) == O_CONTROLLER { if (OController & mode) == OController {
ColorLog("[INFO] Creating controller files...\n") ColorLog("[INFO] Creating controller files...\n")
writeControllerFiles(tables, paths.ControllerPath, selectedTables, pkgPath) writeControllerFiles(tables, paths.ControllerPath, selectedTables, pkgPath)
} }
if (O_ROUTER & mode) == O_ROUTER { if (ORouter & mode) == ORouter {
ColorLog("[INFO] Creating router files...\n") ColorLog("[INFO] Creating router files...\n")
writeRouterFile(tables, paths.RouterPath, selectedTables, pkgPath) writeRouterFile(tables, paths.RouterPath, selectedTables, pkgPath)
} }
@ -732,6 +732,8 @@ func writeSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath
// writeModelFiles generates model files // writeModelFiles generates model files
func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bool) { func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bool) {
w := NewColorWriter(os.Stdout)
for _, tb := range tables { for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it // if selectedTables map is not nil and this table is not selected, ignore it
if selectedTables != nil { if selectedTables != nil {
@ -744,7 +746,7 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
var f *os.File var f *os.File
var err error var err error
if isExist(fpath) { 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() { if askForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil { if err != nil {
@ -752,7 +754,7 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
continue continue
} }
} else { } else {
ColorLog("[WARN] skip create file\n") ColorLog("[WARN] Skipped create file '%s'\n", fpath)
continue continue
} }
} else { } else {
@ -764,9 +766,9 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
} }
template := "" template := ""
if tb.Pk == "" { if tb.Pk == "" {
template = STRUCT_MODEL_TPL template = StructModelTPL
} else { } else {
template = MODEL_TPL template = ModelTPL
} }
fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1) fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1)
fileStr = strings.Replace(fileStr, "{{modelName}}", camelCase(tb.Name), -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) ColorLog("[ERRO] Could not write model file to %s\n", fpath)
os.Exit(2) os.Exit(2)
} }
f.Close() CloseFile(f)
ColorLog("[INFO] model => %s\n", fpath) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath) formatSourceCode(fpath)
} }
} }
// writeControllerFiles generates controller files // writeControllerFiles generates controller files
func writeControllerFiles(tables []*Table, cPath string, selectedTables map[string]bool, pkgPath string) { func writeControllerFiles(tables []*Table, cPath string, selectedTables map[string]bool, pkgPath string) {
w := NewColorWriter(os.Stdout)
for _, tb := range tables { for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it // if selectedTables map is not nil and this table is not selected, ignore it
if selectedTables != nil { if selectedTables != nil {
@ -807,7 +811,7 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri
var f *os.File var f *os.File
var err error var err error
if isExist(fpath) { 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() { if askForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil { if err != nil {
@ -815,7 +819,7 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri
continue continue
} }
} else { } else {
ColorLog("[WARN] skip create file\n") ColorLog("[WARN] Skipped create file '%s'\n", fpath)
continue continue
} }
} else { } else {
@ -825,20 +829,22 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri
continue 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) fileStr = strings.Replace(fileStr, "{{pkgPath}}", pkgPath, -1)
if _, err := f.WriteString(fileStr); err != nil { if _, err := f.WriteString(fileStr); err != nil {
ColorLog("[ERRO] Could not write controller file to %s\n", fpath) ColorLog("[ERRO] Could not write controller file to %s\n", fpath)
os.Exit(2) os.Exit(2)
} }
f.Close() CloseFile(f)
ColorLog("[INFO] controller => %s\n", fpath) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath) formatSourceCode(fpath)
} }
} }
// writeRouterFile generates router file // writeRouterFile generates router file
func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bool, pkgPath string) { func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bool, pkgPath string) {
w := NewColorWriter(os.Stdout)
var nameSpaces []string var nameSpaces []string
for _, tb := range tables { for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it // 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 == "" { if tb.Pk == "" {
continue continue
} }
// add name spaces // add namespaces
nameSpace := strings.Replace(NAMESPACE_TPL, "{{nameSpace}}", tb.Name, -1) nameSpace := strings.Replace(NamespaceTPL, "{{nameSpace}}", tb.Name, -1)
nameSpace = strings.Replace(nameSpace, "{{ctrlName}}", camelCase(tb.Name), -1) nameSpace = strings.Replace(nameSpace, "{{ctrlName}}", camelCase(tb.Name), -1)
nameSpaces = append(nameSpaces, nameSpace) nameSpaces = append(nameSpaces, nameSpace)
} }
// add export controller // add export controller
fpath := path.Join(rPath, "router.go") 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) routerStr = strings.Replace(routerStr, "{{pkgPath}}", pkgPath, 1)
var f *os.File var f *os.File
var err error var err error
if isExist(fpath) { 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() { if askForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil { if err != nil {
@ -870,7 +876,7 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo
return return
} }
} else { } else {
ColorLog("[WARN] skip create file\n") ColorLog("[WARN] Skipped create file '%s'\n", fpath)
return return
} }
} else { } else {
@ -881,11 +887,11 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo
} }
} }
if _, err := f.WriteString(routerStr); err != nil { 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) os.Exit(2)
} }
f.Close() CloseFile(f)
ColorLog("[INFO] router => %s\n", fpath) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath) formatSourceCode(fpath)
} }
@ -968,7 +974,7 @@ func getPackagePath(curpath string) (packpath string) {
gopath := os.Getenv("GOPATH") gopath := os.Getenv("GOPATH")
Debugf("gopath:%s", gopath) Debugf("gopath:%s", gopath)
if gopath == "" { if gopath == "" {
ColorLog("[ERRO] you should set GOPATH in the env") ColorLog("[ERRO] You should set GOPATH in the env")
os.Exit(2) os.Exit(2)
} }
@ -1001,12 +1007,12 @@ func getPackagePath(curpath string) (packpath string) {
} }
const ( const (
STRUCT_MODEL_TPL = `package models StructModelTPL = `package models
{{importTimePkg}} {{importTimePkg}}
{{modelStruct}} {{modelStruct}}
` `
MODEL_TPL = `package models ModelTPL = `package models
import ( import (
"errors" "errors"
@ -1099,7 +1105,7 @@ func GetAll{{modelName}}(query map[string]string, fields []string, sortby []stri
var l []{{modelName}} var l []{{modelName}}
qs = qs.OrderBy(sortFields...) 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 { if len(fields) == 0 {
for _, v := range l { for _, v := range l {
ml = append(ml, v) ml = append(ml, v)
@ -1150,7 +1156,7 @@ func Delete{{modelName}}(id int) (err error) {
return return
} }
` `
CTRL_TPL = `package controllers CtrlTPL = `package controllers
import ( import (
"{{pkgPath}}/models" "{{pkgPath}}/models"
@ -1256,7 +1262,7 @@ func (c *{{ctrlName}}Controller) GetAll() {
// query: k:v,k:v // query: k:v,k:v
if v := c.GetString("query"); v != "" { if v := c.GetString("query"); v != "" {
for _, cond := range strings.Split(v, ",") { for _, cond := range strings.Split(v, ",") {
kv := strings.Split(cond, ":") kv := strings.SplitN(cond, ":", 2)
if len(kv) != 2 { if len(kv) != 2 {
c.Data["json"] = errors.New("Error: invalid query key/value pair") c.Data["json"] = errors.New("Error: invalid query key/value pair")
c.ServeJSON() c.ServeJSON()
@ -1316,7 +1322,7 @@ func (c *{{ctrlName}}Controller) Delete() {
c.ServeJSON() c.ServeJSON()
} }
` `
ROUTER_TPL = `// @APIVersion 1.0.0 RouterTPL = `// @APIVersion 1.0.0
// @Title beego Test API // @Title beego Test API
// @Description beego has a very cool tools to autogenerate documents for your API // @Description beego has a very cool tools to autogenerate documents for your API
// @Contact astaxie@gmail.com // @Contact astaxie@gmail.com
@ -1338,7 +1344,7 @@ func init() {
beego.AddNamespace(ns) beego.AddNamespace(ns)
} }
` `
NAMESPACE_TPL = ` NamespaceTPL = `
beego.NSNamespace("/{{nameSpace}}", beego.NSNamespace("/{{nameSpace}}",
beego.NSInclude( beego.NSInclude(
&controllers.{{ctrlName}}Controller{}, &controllers.{{ctrlName}}Controller{},

View File

@ -15,6 +15,7 @@
package main package main
import ( import (
"fmt"
"os" "os"
"path" "path"
"strings" "strings"
@ -23,44 +24,53 @@ import (
// article // article
// cms/article // cms/article
// //
func generateController(cname, crupath string) { func generateController(cname, currpath string) {
w := NewColorWriter(os.Stdout)
p, f := path.Split(cname) p, f := path.Split(cname)
controllerName := strings.Title(f) controllerName := strings.Title(f)
packageName := "controllers" packageName := "controllers"
if p != "" { if p != "" {
i := strings.LastIndex(p[:len(p)-1], "/") i := strings.LastIndex(p[:len(p)-1], "/")
packageName = p[i+1 : len(p)-1] packageName = p[i+1 : len(p)-1]
} }
ColorLog("[INFO] Using '%s' as controller name\n", controllerName) ColorLog("[INFO] Using '%s' as controller name\n", controllerName)
ColorLog("[INFO] Using '%s' as package name\n", packageName) 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) { if _, err := os.Stat(fp); os.IsNotExist(err) {
// create controller directory // Create the controller's directory
if err := os.MkdirAll(fp, 0777); err != nil { if err := os.MkdirAll(fp, 0777); err != nil {
ColorLog("[ERRO] Could not create controllers directory: %s\n", err) ColorLog("[ERRO] Could not create controllers directory: %s\n", err)
os.Exit(2) os.Exit(2)
} }
} }
fpath := path.Join(fp, strings.ToLower(controllerName)+".go") 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 { if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer f.Close() defer CloseFile(f)
modelPath := path.Join(crupath, "models", strings.ToLower(controllerName)+".go")
modelPath := path.Join(currpath, "models", strings.ToLower(controllerName)+".go")
var content string var content string
if _, err := os.Stat(modelPath); err == nil { if _, err := os.Stat(modelPath); err == nil {
ColorLog("[INFO] Using matching model '%s'\n", controllerName) ColorLog("[INFO] Using matching model '%s'\n", controllerName)
content = strings.Replace(controllerModelTpl, "{{packageName}}", packageName, -1) content = strings.Replace(controllerModelTpl, "{{packageName}}", packageName, -1)
pkgPath := getPackagePath(crupath) pkgPath := getPackagePath(currpath)
content = strings.Replace(content, "{{pkgPath}}", pkgPath, -1) content = strings.Replace(content, "{{pkgPath}}", pkgPath, -1)
} else { } else {
content = strings.Replace(controllerTpl, "{{packageName}}", packageName, -1) content = strings.Replace(controllerTpl, "{{packageName}}", packageName, -1)
} }
content = strings.Replace(content, "{{controllerName}}", controllerName, -1) content = strings.Replace(content, "{{controllerName}}", controllerName, -1)
f.WriteString(content) f.WriteString(content)
// gofmt generated source code
// Run 'gofmt' on the generated source code
formatSourceCode(fpath) 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 { } else {
// error creating file
ColorLog("[ERRO] Could not create controller file: %s\n", err) ColorLog("[ERRO] Could not create controller file: %s\n", err)
os.Exit(2) os.Exit(2)
} }
@ -85,7 +95,7 @@ func (c *{{controllerName}}Controller) URLMapping() {
c.Mapping("Delete", c.Delete) c.Mapping("Delete", c.Delete)
} }
// @Title Post // @Title Create
// @Description create {{controllerName}} // @Description create {{controllerName}}
// @Param body body models.{{controllerName}} true "body for {{controllerName}} content" // @Param body body models.{{controllerName}} true "body for {{controllerName}} content"
// @Success 201 {object} models.{{controllerName}} // @Success 201 {object} models.{{controllerName}}
@ -95,7 +105,7 @@ func (c *{{controllerName}}Controller) Post() {
} }
// @Title Get // @Title GetOne
// @Description get {{controllerName}} by id // @Description get {{controllerName}} by id
// @Param id path string true "The key for staticblock" // @Param id path string true "The key for staticblock"
// @Success 200 {object} models.{{controllerName}} // @Success 200 {object} models.{{controllerName}}
@ -105,7 +115,7 @@ func (c *{{controllerName}}Controller) GetOne() {
} }
// @Title Get All // @Title GetAll
// @Description get {{controllerName}} // @Description get {{controllerName}}
// @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..." // @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..."
// @Param fields query string false "Fields returned. e.g. col1,col2 ..." // @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 // query: k:v,k:v
if v := c.GetString("query"); v != "" { if v := c.GetString("query"); v != "" {
for _, cond := range strings.Split(v, ",") { for _, cond := range strings.Split(v, ",") {
kv := strings.Split(cond, ":") kv := strings.SplitN(cond, ":", 2)
if len(kv) != 2 { if len(kv) != 2 {
c.Data["json"] = errors.New("Error: invalid query key/value pair") c.Data["json"] = errors.New("Error: invalid query key/value pair")
c.ServeJSON() c.ServeJSON()

451
g_docs.go
View File

@ -35,63 +35,6 @@ import (
"github.com/astaxie/beego/utils" "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 ( const (
ajson = "application/json" ajson = "application/json"
axml = "application/xml" axml = "application/xml"
@ -99,21 +42,19 @@ const (
ahtml = "text/html" 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 controllerComments map[string]string
var importlist map[string]string var importlist map[string]string
var apilist map[string]*swagger.APIDeclaration var controllerList map[string]map[string]*swagger.Item //controllername Paths items
var controllerList map[string][]swagger.API var modelsList map[string]map[string]swagger.Schema
var modelsList map[string]map[string]swagger.Model var rootapi swagger.Swagger
var rootapi swagger.ResourceListing
func init() { func init() {
pkgCache = make(map[string]bool) pkgCache = make(map[string]struct{})
controllerComments = make(map[string]string) controllerComments = make(map[string]string)
importlist = make(map[string]string) importlist = make(map[string]string)
apilist = make(map[string]*swagger.APIDeclaration) controllerList = make(map[string]map[string]*swagger.Item)
controllerList = make(map[string][]swagger.API) modelsList = make(map[string]map[string]swagger.Schema)
modelsList = make(map[string]map[string]swagger.Model)
} }
func generateDocs(curpath string) { func generateDocs(curpath string) {
@ -126,30 +67,31 @@ func generateDocs(curpath string) {
os.Exit(2) os.Exit(2)
} }
rootapi.Info = swagger.Information{} rootapi.Infos = swagger.Information{}
rootapi.SwaggerVersion = swagger.SwaggerVersion rootapi.SwaggerVersion = "2.0"
//analysis API comments //analysis API comments
if f.Comments != nil { if f.Comments != nil {
for _, c := range f.Comments { for _, c := range f.Comments {
for _, s := range strings.Split(c.Text(), "\n") { for _, s := range strings.Split(c.Text(), "\n") {
if strings.HasPrefix(s, "@APIVersion") { 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") { } 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") { } 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") { } 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") { } 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") { } 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") { } 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 { for _, im := range f.Imports {
localName := "" localName := ""
if im.Name != nil { if im.Name != nil {
@ -161,33 +103,46 @@ func generateDocs(curpath string) {
switch specDecl := d.(type) { switch specDecl := d.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
for _, l := range specDecl.Body.List { for _, l := range specDecl.Body.List {
switch smtp := l.(type) { switch stmt := l.(type) {
case *ast.AssignStmt: case *ast.AssignStmt:
for _, l := range smtp.Rhs { for _, l := range stmt.Rhs {
if v, ok := l.(*ast.CallExpr); ok { if v, ok := l.(*ast.CallExpr); ok {
f, params := analisysNewNamespace(v) // analisys NewNamespace, it will return version and the subfunction
globalDocsTemplate = strings.Replace(globalDocsTemplate, "{{.version}}", f, -1) 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 { for _, p := range params {
switch pp := p.(type) { switch pp := p.(type) {
case *ast.CallExpr: case *ast.CallExpr:
controllerName := ""
if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" { if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" {
s, params := analisysNewNamespace(pp) s, params := analisysNewNamespace(pp)
subapi := swagger.APIRef{Path: s}
controllerName := ""
for _, sp := range params { for _, sp := range params {
switch pp := sp.(type) { switch pp := sp.(type) {
case *ast.CallExpr: case *ast.CallExpr:
if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" { if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" {
controllerName = analisysNSInclude(s, pp) controllerName = analisysNSInclude(s, pp)
}
}
}
if v, ok := controllerComments[controllerName]; ok { if v, ok := controllerComments[controllerName]; ok {
subapi.Description = v rootapi.Tags = append(rootapi.Tags, swagger.Tag{
Name: strings.Trim(s, "/"),
Description: v,
})
}
}
}
} }
rootapi.APIs = append(rootapi.APIs, subapi)
} else if selname == "NSInclude" { } 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) os.Mkdir(path.Join(curpath, "swagger"), 0755)
if err != nil { fd, err := os.Create(path.Join(curpath, "swagger", "swagger.json"))
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"))
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer fd.Close() defer fd.Close()
a := strings.Replace(globalDocsTemplate, "{{.rootinfo}}", "`"+string(apiinfo)+"`", -1) dt, err := json.MarshalIndent(rootapi, "", " ")
a = strings.Replace(a, "{{.subapi}}", "`"+string(subapi)+"`", -1) if err != nil {
fd.WriteString(a) 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) { func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) {
for i, p := range ce.Args { for i, p := range ce.Args {
if i == 0 { 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 { func analisysNSInclude(baseurl string, ce *ast.CallExpr) string {
cname := "" 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 { for _, p := range ce.Args {
x := p.(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.SelectorExpr) x := p.(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.SelectorExpr)
if v, ok := importlist[fmt.Sprint(x.X)]; ok { if v, ok := importlist[fmt.Sprint(x.X)]; ok {
cname = v + x.Sel.Name cname = v + x.Sel.Name
} }
if apis, ok := controllerList[cname]; ok { if apis, ok := controllerList[cname]; ok {
if len(a.APIs) > 0 { for rt, item := range apis {
a.APIs = append(a.APIs, apis...) tag := ""
if baseurl != "" {
rt = baseurl + rt
tag = strings.Trim(baseurl, "/")
} else { } else {
a.APIs = apis tag = cname
} }
if item.Get != nil {
item.Get.Tags = []string{tag}
} }
if models, ok := modelsList[cname]; ok { if item.Post != nil {
for _, m := range models { item.Post.Tags = []string{tag}
a.Models[m.ID] = m }
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 return cname
} }
@ -267,15 +237,15 @@ func analisyscontrollerPkg(localName, pkgpath string) {
if isSystemPackage(pkgpath) { if isSystemPackage(pkgpath) {
return return
} }
if pkgpath == "github.com/astaxie/beego" {
return
}
if localName != "" { if localName != "" {
importlist[localName] = pkgpath importlist[localName] = pkgpath
} else { } else {
pps := strings.Split(pkgpath, "/") pps := strings.Split(pkgpath, "/")
importlist[pps[len(pps)-1]] = pkgpath importlist[pps[len(pps)-1]] = pkgpath
} }
if pkgpath == "github.com/astaxie/beego" {
return
}
gopath := os.Getenv("GOPATH") gopath := os.Getenv("GOPATH")
if gopath == "" { if gopath == "" {
panic("please set gopath") panic("please set gopath")
@ -294,6 +264,7 @@ func analisyscontrollerPkg(localName, pkgpath string) {
if _, ok := pkgCache[pkgpath]; ok { if _, ok := pkgCache[pkgpath]; ok {
return return
} }
pkgCache[pkgpath] = struct{}{}
} else { } else {
ColorLog("[ERRO] the %s pkg not exist in gopath\n", pkgpath) ColorLog("[ERRO] the %s pkg not exist in gopath\n", pkgpath)
os.Exit(1) os.Exit(1)
@ -315,15 +286,18 @@ func analisyscontrollerPkg(localName, pkgpath string) {
case *ast.FuncDecl: case *ast.FuncDecl:
if specDecl.Recv != nil && len(specDecl.Recv.List) > 0 { if specDecl.Recv != nil && len(specDecl.Recv.List) > 0 {
if t, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr); ok { 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) parserComments(specDecl.Doc, specDecl.Name.String(), fmt.Sprint(t.X), pkgpath)
} }
} }
case *ast.GenDecl: case *ast.GenDecl:
if specDecl.Tok.String() == "type" { if specDecl.Tok == token.TYPE {
for _, s := range specDecl.Specs { for _, s := range specDecl.Specs {
switch tp := s.(*ast.TypeSpec).Type.(type) { switch tp := s.(*ast.TypeSpec).Type.(type) {
case *ast.StructType: case *ast.StructType:
_ = tp.Struct _ = tp.Struct
//parse controller definition comments
if strings.TrimSpace(specDecl.Doc.Text()) != "" {
controllerComments[pkgpath+s.(*ast.TypeSpec).Name.String()] = specDecl.Doc.Text() controllerComments[pkgpath+s.(*ast.TypeSpec).Name.String()] = specDecl.Doc.Text()
} }
} }
@ -332,6 +306,7 @@ func analisyscontrollerPkg(localName, pkgpath string) {
} }
} }
} }
}
} }
func isSystemPackage(pkgpath string) bool { func isSystemPackage(pkgpath string) bool {
@ -355,8 +330,11 @@ func isSystemPackage(pkgpath string) bool {
// parse the func comments // parse the func comments
func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error { func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error {
innerapi := swagger.API{} var routerPath string
opts := swagger.Operation{} var HTTPMethod string
opts := swagger.Operation{
Responses: make(map[string]swagger.Response),
}
if comments != nil && comments.List != nil { if comments != nil && comments.List != nil {
for _, c := range comments.List { for _, c := range comments.List {
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//")) t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
@ -366,20 +344,20 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
if len(e1) < 1 { if len(e1) < 1 {
return errors.New("you should has router infomation") return errors.New("you should has router infomation")
} }
innerapi.Path = e1[0] routerPath = e1[0]
if len(e1) == 2 && e1[1] != "" { if len(e1) == 2 && e1[1] != "" {
e1 = strings.SplitN(e1[1], " ", 2) e1 = strings.SplitN(e1[1], " ", 2)
opts.HTTPMethod = strings.ToUpper(strings.Trim(e1[0], "[]")) HTTPMethod = strings.ToUpper(strings.Trim(e1[0], "[]"))
} else { } else {
opts.HTTPMethod = "GET" HTTPMethod = "GET"
} }
} else if strings.HasPrefix(t, "@Title") { } 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") { } else if strings.HasPrefix(t, "@Description") {
opts.Summary = strings.TrimSpace(t[len("@Description"):]) opts.Summary = strings.TrimSpace(t[len("@Description"):])
} else if strings.HasPrefix(t, "@Success") { } else if strings.HasPrefix(t, "@Success") {
ss := strings.TrimSpace(t[len("@Success"):]) ss := strings.TrimSpace(t[len("@Success"):])
rs := swagger.ResponseMessage{} rs := swagger.Response{}
st := make([]string, 3) st := make([]string, 3)
j := 0 j := 0
var tmp []rune var tmp []rune
@ -393,7 +371,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
if j == 0 || j == 1 { if j == 0 || j == 1 {
st[j] = string(tmp) st[j] = string(tmp)
tmp = make([]rune, 0) tmp = make([]rune, 0)
j += 1 j++
start = false start = false
if j == 1 { if j == 1 {
continue continue
@ -411,7 +389,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
if len(tmp) > 0 && st[2] == "" { if len(tmp) > 0 && st[2] == "" {
st[2] = strings.TrimSpace(string(tmp)) st[2] = strings.TrimSpace(string(tmp))
} }
rs.Message = st[2] rs.Description = st[2]
if st[1] == "{object}" { if st[1] == "{object}" {
if st[2] == "" { if st[2] == "" {
panic(controllerName + " " + funcName + " has no object") 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]) cmpath, m, mod, realTypes := getModel(st[2])
//ll := strings.Split(st[2], ".") //ll := strings.Split(st[2], ".")
//opts.Type = ll[len(ll)-1] //opts.Type = ll[len(ll)-1]
rs.ResponseModel = m rs.Schema = &swagger.Schema{
Ref: "#/definitions/" + m,
}
if _, ok := modelsList[pkgpath+controllerName]; !ok { 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 modelsList[pkgpath+controllerName][st[2]] = mod
appendModels(cmpath, pkgpath, controllerName, realTypes) appendModels(cmpath, pkgpath, controllerName, realTypes)
} }
}
rs.Code, _ = strconv.Atoi(st[0]) opts.Responses[st[0]] = rs
opts.ResponseMessages = append(opts.ResponseMessages, rs)
} else if strings.HasPrefix(t, "@Param") { } else if strings.HasPrefix(t, "@Param") {
para := swagger.Parameter{} para := swagger.Parameter{}
p := getparams(strings.TrimSpace(t[len("@Param "):])) 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") panic(controllerName + "_" + funcName + "'s comments @Param at least should has 4 params")
} }
para.Name = p[0] 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], ".") 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 { if len(p) > 4 {
para.Required, _ = strconv.ParseBool(p[3]) para.Required, _ = strconv.ParseBool(p[3])
para.Description = p[4] para.Description = strings.Trim(p[4], `" `)
} else { } else {
para.Description = p[3] para.Description = strings.Trim(p[3], `" `)
} }
opts.Parameters = append(opts.Parameters, para) opts.Parameters = append(opts.Parameters, para)
} else if strings.HasPrefix(t, "@Failure") { } else if strings.HasPrefix(t, "@Failure") {
rs := swagger.ResponseMessage{} rs := swagger.Response{}
st := strings.TrimSpace(t[len("@Failure"):]) st := strings.TrimSpace(t[len("@Failure"):])
var cd []rune var cd []rune
var start bool var start bool
for i, s := range st { for i, s := range st {
if unicode.IsSpace(s) { if unicode.IsSpace(s) {
if start { if start {
rs.Message = strings.TrimSpace(st[i+1:]) rs.Description = strings.TrimSpace(st[i+1:])
break break
} else { } else {
continue continue
@ -463,10 +495,9 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
start = true start = true
cd = append(cd, s) cd = append(cd, s)
} }
rs.Code, _ = strconv.Atoi(string(cd)) opts.Responses[string(cd)] = rs
opts.ResponseMessages = append(opts.ResponseMessages, rs) } else if strings.HasPrefix(t, "@Deprecated") {
} else if strings.HasPrefix(t, "@Type") { opts.Deprecated, _ = strconv.ParseBool(strings.TrimSpace(t[len("@Deprecated"):]))
opts.Type = strings.TrimSpace(t[len("@Type"):])
} else if strings.HasPrefix(t, "@Accept") { } else if strings.HasPrefix(t, "@Accept") {
accepts := strings.Split(strings.TrimSpace(strings.TrimSpace(t[len("@Accept"):])), ",") accepts := strings.Split(strings.TrimSpace(strings.TrimSpace(t[len("@Accept"):])), ",")
for _, a := range accepts { for _, a := range accepts {
@ -488,14 +519,35 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
} }
} }
} }
innerapi.Operations = append(innerapi.Operations, opts) if routerPath != "" {
if innerapi.Path != "" { var item *swagger.Item
if _, ok := controllerList[pkgpath+controllerName]; ok { if itemList, ok := controllerList[pkgpath+controllerName]; ok {
controllerList[pkgpath+controllerName] = append(controllerList[pkgpath+controllerName], innerapi) if it, ok := itemList[routerPath]; !ok {
item = &swagger.Item{}
} else { } else {
controllerList[pkgpath+controllerName] = make([]swagger.API, 1) item = it
controllerList[pkgpath+controllerName][0] = innerapi
} }
} else {
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 return nil
} }
@ -531,7 +583,7 @@ func getparams(str string) []string {
return r 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, ".") strs := strings.Split(str, ".")
objectname = strs[len(strs)-1] objectname = strs[len(strs)-1]
pkgpath = strings.Join(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) ColorLog("[ERRO] the model %s parser.ParseDir error\n", str)
os.Exit(1) os.Exit(1)
} }
m.Type = "object"
for _, pkg := range astPkgs { for _, pkg := range astPkgs {
for _, fl := range pkg.Files { for _, fl := range pkg.Files {
for k, d := range fl.Scope.Objects { for k, d := range fl.Scope.Objects {
@ -564,24 +616,36 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Model, realType
if !ok { if !ok {
continue continue
} }
m.ID = k m.Title = k
if st.Fields.List != nil { 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 { for _, field := range st.Fields.List {
isSlice, realType := typeAnalyser(field) isSlice, realType, sType := typeAnalyser(field)
realTypes = append(realTypes, realType) realTypes = append(realTypes, realType)
mp := swagger.ModelProperty{} mp := swagger.Propertie{}
// add type slice // add type slice
if isSlice { if isSlice {
if isBasicType(realType) {
mp.Type = "[]" + realType
} else {
mp.Type = "array" mp.Type = "array"
mp.Items = make(map[string]string) if isBasicType(realType) {
mp.Items["$ref"] = realType typeFormat := strings.Split(sType, ":")
mp.Items = &swagger.Propertie{
Type: typeFormat[0],
Format: typeFormat[1],
}
} else {
mp.Items = &swagger.Propertie{
Ref: "#/definitions/" + realType,
}
} }
} else { } 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 // 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 == "" { if m.Title == "" {
ColorLog("can't find the object: %v", str) ColorLog("can't find the object: %s", str)
os.Exit(1) os.Exit(1)
} }
if len(rootapi.Definitions) == 0 {
rootapi.Definitions = make(map[string]swagger.Schema)
}
rootapi.Definitions[objectname] = m
return 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 arr, ok := f.Type.(*ast.ArrayType); ok {
if isBasicType(fmt.Sprint(arr.Elt)) { 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 { 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 { if star, ok := arr.Elt.(*ast.StarExpr); ok {
return true, fmt.Sprint(star.X) return true, fmt.Sprint(star.X), "object"
} else { }
return true, fmt.Sprint(arr.Elt) return true, fmt.Sprint(arr.Elt), "object"
} }
} else {
switch t := f.Type.(type) { switch t := f.Type.(type) {
case *ast.StarExpr: case *ast.StarExpr:
return false, fmt.Sprint(t.X) return false, fmt.Sprint(t.X), "object"
case *ast.MapType:
return false, fmt.Sprint(t.Value), "object"
} }
return false, fmt.Sprint(f.Type) 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 { func isBasicType(Type string) bool {
for _, v := range basicTypes { if _, ok := basicTypes[Type]; ok {
if v == Type {
return true return true
} }
}
return false return false
} }
// refer to builtin.go // refer to builtin.go
var basicTypes = []string{ var basicTypes = map[string]string{
"bool", "bool": "boolean:",
"uint", "uint8", "uint16", "uint32", "uint64", "uint": "integer:int32", "uint8": "integer:int32", "uint16": "integer:int32", "uint32": "integer:int32", "uint64": "integer:int64",
"int", "int8", "int16", "int32", "int64", "int": "integer:int64", "int8": "integer:int32", "int16:int32": "integer:int32", "int32": "integer:int32", "int64": "integer:int64",
"float32", "float64", "uintptr": "integer:int64",
"string", "float32": "number:float", "float64": "number:double",
"complex64", "complex128", "string": "string:",
"byte", "rune", "uintptr", "complex64": "number:float", "complex128": "number:double",
"byte": "string:byte", "rune": "string:byte",
} }
// regexp get json tag // regexp get json tag
func grepJsonTag(tag string) string { func grepJSONTag(tag string) string {
r, _ := regexp.Compile(`json:"([^"]*)"`) r, _ := regexp.Compile(`json:"([^"]*)"`)
matches := r.FindAllStringSubmatch(tag, -1) matches := r.FindAllStringSubmatch(tag, -1)
if len(matches) > 0 { 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, "/")
}

View File

@ -19,6 +19,7 @@ package main
import ( import (
"database/sql" "database/sql"
"fmt"
"os" "os"
"path" "path"
"strings" "strings"
@ -31,11 +32,11 @@ func generateHproseAppcode(driver, connStr, level, tables, currpath string) {
var mode byte var mode byte
switch level { switch level {
case "1": case "1":
mode = O_MODEL mode = OModel
case "2": case "2":
mode = O_MODEL | O_CONTROLLER mode = OModel | OController
case "3": case "3":
mode = O_MODEL | O_CONTROLLER | O_ROUTER mode = OModel | OController | ORouter
default: default:
ColorLog("[ERRO] Invalid 'level' option: %s\n", level) ColorLog("[ERRO] Invalid 'level' option: %s\n", level)
ColorLog("[HINT] Level must be either 1, 2 or 3\n") 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 // It will wipe the following directories and recreate them:./models, ./controllers, ./routers
// Newly geneated files will be inside these folders. // Newly geneated files will be inside these folders.
func writeHproseSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) { 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") ColorLog("[INFO] Creating model files...\n")
writeHproseModelFiles(tables, paths.ModelPath, selectedTables) writeHproseModelFiles(tables, paths.ModelPath, selectedTables)
} }
@ -98,6 +99,8 @@ func writeHproseSourceFiles(pkgPath string, tables []*Table, mode byte, paths *M
// writeHproseModelFiles generates model files // writeHproseModelFiles generates model files
func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[string]bool) { func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[string]bool) {
w := NewColorWriter(os.Stdout)
for _, tb := range tables { for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it // if selectedTables map is not nil and this table is not selected, ignore it
if selectedTables != nil { if selectedTables != nil {
@ -110,7 +113,7 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str
var f *os.File var f *os.File
var err error var err error
if isExist(fpath) { 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() { if askForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil { if err != nil {
@ -118,7 +121,7 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str
continue continue
} }
} else { } else {
ColorLog("[WARN] skip create file\n") ColorLog("[WARN] Skipped create file '%s'\n", fpath)
continue continue
} }
} else { } else {
@ -130,10 +133,10 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str
} }
template := "" template := ""
if tb.Pk == "" { if tb.Pk == "" {
template = HPROSE_STRUCT_MODEL_TPL template = HproseStructModelTPL
} else { } else {
template = HPROSE_MODEL_TPL template = HproseModelTPL
hproseAddFunctions = append(hproseAddFunctions, strings.Replace(HPROSE_ADDFUNCTION, "{{modelName}}", camelCase(tb.Name), -1)) hproseAddFunctions = append(hproseAddFunctions, strings.Replace(HproseAddFunction, "{{modelName}}", camelCase(tb.Name), -1))
} }
fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1) fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1)
fileStr = strings.Replace(fileStr, "{{modelName}}", camelCase(tb.Name), -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, "{{timePkg}}", timePkg, -1)
fileStr = strings.Replace(fileStr, "{{importTimePkg}}", importTimePkg, -1) fileStr = strings.Replace(fileStr, "{{importTimePkg}}", importTimePkg, -1)
if _, err := f.WriteString(fileStr); err != nil { 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) os.Exit(2)
} }
f.Close() CloseFile(f)
ColorLog("[INFO] model => %s\n", fpath) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath) formatSourceCode(fpath)
} }
} }
const ( const (
HPROSE_ADDFUNCTION = ` HproseAddFunction = `
// publish about {{modelName}} function // publish about {{modelName}} function
service.AddFunction("Add{{modelName}}", models.Add{{modelName}}) service.AddFunction("Add{{modelName}}", models.Add{{modelName}})
service.AddFunction("Get{{modelName}}ById", models.Get{{modelName}}ById) service.AddFunction("Get{{modelName}}ById", models.Get{{modelName}}ById)
@ -166,12 +169,12 @@ const (
service.AddFunction("Delete{{modelName}}", models.Delete{{modelName}}) service.AddFunction("Delete{{modelName}}", models.Delete{{modelName}})
` `
HPROSE_STRUCT_MODEL_TPL = `package models HproseStructModelTPL = `package models
{{importTimePkg}} {{importTimePkg}}
{{modelStruct}} {{modelStruct}}
` `
HPROSE_MODEL_TPL = `package models HproseModelTPL = `package models
import ( import (
"errors" "errors"
@ -260,7 +263,7 @@ func GetAll{{modelName}}(query map[string]string, fields []string, sortby []stri
var l []{{modelName}} var l []{{modelName}}
qs = qs.OrderBy(sortFields...) 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 { if len(fields) == 0 {
for _, v := range l { for _, v := range l {
ml = append(ml, v) ml = append(ml, v)

View File

@ -23,15 +23,170 @@ import (
) )
const ( const (
M_PATH = "migrations" MPath = "migrations"
M_DATE_FORMAT = "20060102_150405" 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. // generateMigration generates migration file template for database schema update.
// The generated file template consists of an up() method for updating schema and // The generated file template consists of an up() method for updating schema and
// a down() method for reverting the update. // a down() method for reverting the update.
func generateMigration(mname, upsql, downsql, curpath string) { 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) { if _, err := os.Stat(migrationFilePath); os.IsNotExist(err) {
// create migrations directory // create migrations directory
if err := os.MkdirAll(migrationFilePath, 0777); err != nil { if err := os.MkdirAll(migrationFilePath, 0777); err != nil {
@ -40,26 +195,25 @@ func generateMigration(mname, upsql, downsql, curpath string) {
} }
} }
// create file // 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)) 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 { 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(MIGRATION_TPL, "{{StructName}}", camelCase(mname)+"_"+today, -1) content := strings.Replace(MigrationTPL, "{{StructName}}", camelCase(mname)+"_"+today, -1)
content = strings.Replace(content, "{{CurrTime}}", today, -1) content = strings.Replace(content, "{{CurrTime}}", today, -1)
content = strings.Replace(content, "{{UpSQL}}", upsql, -1) content = strings.Replace(content, "{{UpSQL}}", upsql, -1)
content = strings.Replace(content, "{{DownSQL}}", downsql, -1) content = strings.Replace(content, "{{DownSQL}}", downsql, -1)
f.WriteString(content) f.WriteString(content)
// gofmt generated source code // Run 'gofmt' on the generated source code
formatSourceCode(fpath) 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 { } else {
// error creating file
ColorLog("[ERRO] Could not create migration file: %s\n", err) ColorLog("[ERRO] Could not create migration file: %s\n", err)
os.Exit(2) os.Exit(2)
} }
} }
const MIGRATION_TPL = `package main const MigrationTPL = `package main
import ( import (
"github.com/astaxie/beego/migration" "github.com/astaxie/beego/migration"

View File

@ -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 package main
import ( import (
"errors" "errors"
"fmt"
"os" "os"
"path" "path"
"strings" "strings"
) )
func generateModel(mname, fields, crupath string) { func generateModel(mname, fields, currpath string) {
w := NewColorWriter(os.Stdout)
p, f := path.Split(mname) p, f := path.Split(mname)
modelName := strings.Title(f) modelName := strings.Title(f)
packageName := "models" packageName := "models"
@ -15,24 +32,28 @@ func generateModel(mname, fields, crupath string) {
i := strings.LastIndex(p[:len(p)-1], "/") i := strings.LastIndex(p[:len(p)-1], "/")
packageName = p[i+1 : 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 { 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) os.Exit(2)
} }
ColorLog("[INFO] Using '%s' as model name\n", modelName) ColorLog("[INFO] Using '%s' as model name\n", modelName)
ColorLog("[INFO] Using '%s' as package name\n", packageName) 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) { if _, err := os.Stat(fp); os.IsNotExist(err) {
// create controller directory // Create the model's directory
if err := os.MkdirAll(fp, 0777); err != nil { 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) os.Exit(2)
} }
} }
fpath := path.Join(fp, strings.ToLower(modelName)+".go") 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 { 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(modelTpl, "{{packageName}}", packageName, -1)
content = strings.Replace(content, "{{modelName}}", modelName, -1) content = strings.Replace(content, "{{modelName}}", modelName, -1)
content = strings.Replace(content, "{{modelStruct}}", modelStruct, -1) content = strings.Replace(content, "{{modelStruct}}", modelStruct, -1)
@ -42,42 +63,45 @@ func generateModel(mname, fields, crupath string) {
content = strings.Replace(content, "{{timePkg}}", "", -1) content = strings.Replace(content, "{{timePkg}}", "", -1)
} }
f.WriteString(content) f.WriteString(content)
// gofmt generated source code // Run 'gofmt' on the generated source code
formatSourceCode(fpath) 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 { } else {
// error creating file
ColorLog("[ERRO] Could not create model file: %s\n", err) ColorLog("[ERRO] Could not create model file: %s\n", err)
os.Exit(2) os.Exit(2)
} }
} }
func getStruct(structname, fields string) (string, error, bool) { func getStruct(structname, fields string) (string, bool, error) {
if fields == "" { if fields == "" {
return "", errors.New("fields can't empty"), false return "", false, errors.New("fields cannot be empty")
} }
hastime := false hastime := false
structStr := "type " + structname + " struct{\n" structStr := "type " + structname + " struct{\n"
fds := strings.Split(fields, ",") fds := strings.Split(fields, ",")
for i, v := range fds { for i, v := range fds {
kv := strings.SplitN(v, ":", 2) kv := strings.SplitN(v, ":", 2)
if len(kv) != 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]) typ, tag, hastimeinner := getType(kv[1])
if typ == "" { 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" { if i == 0 && strings.ToLower(kv[0]) != "id" {
structStr = structStr + "Id int64 `orm:\"auto\"`\n" structStr = structStr + "Id int64 `orm:\"auto\"`\n"
} }
if hastimeinner { if hastimeinner {
hastime = true hastime = true
} }
structStr = structStr + camelString(kv[0]) + " " + typ + " " + tag + "\n" structStr = structStr + camelString(kv[0]) + " " + typ + " " + tag + "\n"
} }
structStr += "}\n" structStr += "}\n"
return structStr, nil, hastime return structStr, hastime, nil
} }
// fields support type // fields support type
@ -88,9 +112,8 @@ func getType(ktype string) (kt, tag string, hasTime bool) {
case "string": case "string":
if len(kv) == 2 { if len(kv) == 2 {
return "string", "`orm:\"size(" + kv[1] + ")\"`", false return "string", "`orm:\"size(" + kv[1] + ")\"`", false
} else {
return "string", "`orm:\"size(128)\"`", false
} }
return "string", "`orm:\"size(128)\"`", false
case "text": case "text":
return "string", "`orm:\"type(longtext)\"`", false return "string", "`orm:\"type(longtext)\"`", false
case "auto": case "auto":
@ -202,7 +225,7 @@ func GetAll{{modelName}}(query map[string]string, fields []string, sortby []stri
var l []{{modelName}} var l []{{modelName}}
qs = qs.OrderBy(sortFields...) 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 { if len(fields) == 0 {
for _, v := range l { for _, v := range l {
ml = append(ml, v) ml = append(ml, v)

View File

@ -1,101 +1,48 @@
package main package main
import ( import "strings"
"fmt"
"strings"
)
func generateScaffold(sname, fields, crupath, driver, conn string) { func generateScaffold(sname, fields, currpath, driver, conn string) {
// generate model ColorLog("[INFO] Do you want to create a '%v' model? [Yes|No] ", sname)
ColorLog("[INFO] Do you want me to create a %v model? [yes|no]] ", sname)
// Generate the model
if askForConfirmation() { if askForConfirmation() {
generateModel(sname, fields, crupath) generateModel(sname, fields, currpath)
} }
// generate controller // Generate the controller
ColorLog("[INFO] Do you want me to create a %v controller? [yes|no]] ", sname) ColorLog("[INFO] Do you want to create a '%v' controller? [Yes|No] ", sname)
if askForConfirmation() { 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() { 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() { if askForConfirmation() {
upsql := "" upsql := ""
downsql := "" downsql := ""
if fields != "" { if fields != "" {
upsql = `m.SQL("CREATE TABLE ` + sname + "(" + generateSQLFromFields(fields) + `)");` dbMigrator := newDBDriver()
downsql = `m.SQL("DROP TABLE ` + "`" + sname + "`" + `")` 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() { 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)) 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 "", ""
}

View File

@ -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 package main
import ( import (
"fmt"
"os" "os"
"path" "path"
) )
// recipe // recipe
// admin/recipe // admin/recipe
func generateView(vpath, crupath string) { func generateView(viewpath, currpath string) {
absvpath := path.Join(crupath, "views", vpath) w := NewColorWriter(os.Stdout)
os.MkdirAll(absvpath, os.ModePerm)
cfile := path.Join(absvpath, "index.tpl") 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 { 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) 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 { } else {
ColorLog("[ERRO] Could not create view file: %s\n", err) ColorLog("[ERRO] Could not create view file: %s\n", err)
os.Exit(2) 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 { 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) 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 { } else {
ColorLog("[ERRO] Could not create view file: %s\n", err) ColorLog("[ERRO] Could not create view file: %s\n", err)
os.Exit(2) 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 { 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) 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 { } else {
ColorLog("[ERRO] Could not create view file: %s\n", err) ColorLog("[ERRO] Could not create view file: %s\n", err)
os.Exit(2) 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 { 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) 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 { } else {
ColorLog("[ERRO] Could not create view file: %s\n", err) ColorLog("[ERRO] Could not create view file: %s\n", err)
os.Exit(2) os.Exit(2)

View File

@ -255,6 +255,10 @@ func init() {
} }
func createhprose(cmd *Command, args []string) int { func createhprose(cmd *Command, args []string) int {
ShowShortVersionBanner()
w := NewColorWriter(os.Stdout)
curpath, _ := os.Getwd() curpath, _ := os.Getwd()
if len(args) > 1 { if len(args) > 1 {
cmd.Flag.Parse(args[1:]) cmd.Flag.Parse(args[1:])
@ -269,12 +273,15 @@ func createhprose(cmd *Command, args []string) int {
} }
if conn == "" { if conn == "" {
} }
ColorLog("[INFO] Creating Hprose application...\n")
os.MkdirAll(apppath, 0755) 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) os.Mkdir(path.Join(apppath, "conf"), 0755)
fmt.Println("create conf:", path.Join(apppath, "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.Println("create conf app.conf:", 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", "app.conf"), "\x1b[0m")
writetofile(path.Join(apppath, "conf", "app.conf"), WriteToFile(path.Join(apppath, "conf", "app.conf"),
strings.Replace(hproseconf, "{{.Appname}}", args[0], -1)) strings.Replace(hproseconf, "{{.Appname}}", args[0], -1))
if conn != "" { 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 'conn'\n", conn)
ColorLog("[INFO] Using '%s' as 'tables'\n", tables) ColorLog("[INFO] Using '%s' as 'tables'\n", tables)
generateHproseAppcode(string(driver), string(conn), "1", string(tables), path.Join(curpath, args[0])) 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(hproseMainconngo, "{{.Appname}}", packpath, -1)
maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(driver), -1) maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(driver), -1)
maingoContent = strings.Replace(maingoContent, "{{HproseFunctionList}}", strings.Join(hproseAddFunctions, ""), -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" { } else if driver == "postgres" {
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1) maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1)
} }
writetofile(path.Join(apppath, "main.go"), WriteToFile(path.Join(apppath, "main.go"),
strings.Replace( strings.Replace(
maingoContent, maingoContent,
"{{.conn}}", "{{.conn}}",
@ -301,17 +308,18 @@ func createhprose(cmd *Command, args []string) int {
) )
} else { } else {
os.Mkdir(path.Join(apppath, "models"), 0755) 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")) 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) WriteToFile(path.Join(apppath, "models", "object.go"), apiModels)
fmt.Println("create models user.go:", path.Join(apppath, "models", "user.go")) 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) WriteToFile(path.Join(apppath, "models", "user.go"), apiModels2)
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")
writetofile(path.Join(apppath, "main.go"), WriteToFile(path.Join(apppath, "main.go"),
strings.Replace(hproseMaingo, "{{.Appname}}", packpath, -1)) strings.Replace(hproseMaingo, "{{.Appname}}", packpath, -1))
} }
ColorLog("[SUCC] New Hprose application successfully created!\n")
return 0 return 0
} }

View File

@ -62,15 +62,18 @@ func init() {
// runMigration is the entry point for starting a migration // runMigration is the entry point for starting a migration
func runMigration(cmd *Command, args []string) int { func runMigration(cmd *Command, args []string) int {
crupath, _ := os.Getwd() ShowShortVersionBanner()
gopath := os.Getenv("GOPATH") currpath, _ := os.Getwd()
Debugf("gopath:%s", gopath)
if gopath == "" { gps := GetGOPATHs()
ColorLog("[ERRO] $GOPATH not found\n") if len(gps) == 0 {
ColorLog("[HINT] Set $GOPATH in your environment vairables\n") ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty")
os.Exit(2) os.Exit(2)
} }
gopath := gps[0]
Debugf("GOPATH: %s", gopath)
// load config // load config
err := loadConfig() err := loadConfig()
if err != nil { if err != nil {
@ -98,19 +101,19 @@ func runMigration(cmd *Command, args []string) int {
if len(args) == 0 { if len(args) == 0 {
// run all outstanding migrations // run all outstanding migrations
ColorLog("[INFO] Running all outstanding migrations\n") ColorLog("[INFO] Running all outstanding migrations\n")
migrateUpdate(crupath, driverStr, connStr) migrateUpdate(currpath, driverStr, connStr)
} else { } else {
mcmd := args[0] mcmd := args[0]
switch mcmd { switch mcmd {
case "rollback": case "rollback":
ColorLog("[INFO] Rolling back the last migration operation\n") ColorLog("[INFO] Rolling back the last migration operation\n")
migrateRollback(crupath, driverStr, connStr) migrateRollback(currpath, driverStr, connStr)
case "reset": case "reset":
ColorLog("[INFO] Reseting all migrations\n") ColorLog("[INFO] Reseting all migrations\n")
migrateReset(crupath, driverStr, connStr) migrateReset(currpath, driverStr, connStr)
case "refresh": case "refresh":
ColorLog("[INFO] Refreshing all migrations\n") ColorLog("[INFO] Refreshing all migrations\n")
migrateRefresh(crupath, driverStr, connStr) migrateRefresh(currpath, driverStr, connStr)
default: default:
ColorLog("[ERRO] Command is missing\n") ColorLog("[ERRO] Command is missing\n")
os.Exit(2) os.Exit(2)
@ -121,28 +124,28 @@ func runMigration(cmd *Command, args []string) int {
} }
// migrateUpdate does the schema update // migrateUpdate does the schema update
func migrateUpdate(crupath, driver, connStr string) { func migrateUpdate(currpath, driver, connStr string) {
migrate("upgrade", crupath, driver, connStr) migrate("upgrade", currpath, driver, connStr)
} }
// migrateRollback rolls back the latest migration // migrateRollback rolls back the latest migration
func migrateRollback(crupath, driver, connStr string) { func migrateRollback(currpath, driver, connStr string) {
migrate("rollback", crupath, driver, connStr) migrate("rollback", currpath, driver, connStr)
} }
// migrateReset rolls back all migrations // migrateReset rolls back all migrations
func migrateReset(crupath, driver, connStr string) { func migrateReset(currpath, driver, connStr string) {
migrate("reset", crupath, driver, connStr) migrate("reset", currpath, driver, connStr)
} }
// migrationRefresh rolls back all migrations and start over again // migrationRefresh rolls back all migrations and start over again
func migrateRefresh(crupath, driver, connStr string) { func migrateRefresh(currpath, driver, connStr string) {
migrate("refresh", crupath, driver, connStr) migrate("refresh", currpath, driver, connStr)
} }
// migrate generates source code, build it, and invoke the binary who does the actual migration // migrate generates source code, build it, and invoke the binary who does the actual migration
func migrate(goal, crupath, driver, connStr string) { func migrate(goal, currpath, driver, connStr string) {
dir := path.Join(crupath, "database", "migrations") dir := path.Join(currpath, "database", "migrations")
binary := "m" binary := "m"
source := binary + ".go" source := binary + ".go"
// connect to database // connect to database
@ -165,23 +168,23 @@ func migrate(goal, crupath, driver, connStr string) {
// checkForSchemaUpdateTable checks the existence of migrations table. // 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. // 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) { func checkForSchemaUpdateTable(db *sql.DB, driver string) {
showTableSql := showMigrationsTableSql(driver) showTableSQL := showMigrationsTableSQL(driver)
if rows, err := db.Query(showTableSql); err != nil { if rows, err := db.Query(showTableSQL); err != nil {
ColorLog("[ERRO] Could not show migrations table: %s\n", err) ColorLog("[ERRO] Could not show migrations table: %s\n", err)
os.Exit(2) os.Exit(2)
} else if !rows.Next() { } else if !rows.Next() {
// no migrations table, create anew // no migrations table, create anew
createTableSql := createMigrationsTableSql(driver) createTableSQL := createMigrationsTableSQL(driver)
ColorLog("[INFO] Creating 'migrations' table...\n") 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) ColorLog("[ERRO] Could not create migrations table: %s\n", err)
os.Exit(2) os.Exit(2)
} }
} }
// checking that migrations table schema are expected // checking that migrations table schema are expected
selectTableSql := selectMigrationsTableSql(driver) selectTableSQL := selectMigrationsTableSQL(driver)
if rows, err := db.Query(selectTableSql); err != nil { if rows, err := db.Query(selectTableSQL); err != nil {
ColorLog("[ERRO] Could not show columns of migrations table: %s\n", err) ColorLog("[ERRO] Could not show columns of migrations table: %s\n", err)
os.Exit(2) os.Exit(2)
} else { } else {
@ -217,7 +220,7 @@ func checkForSchemaUpdateTable(db *sql.DB, driver string) {
} }
} }
func showMigrationsTableSql(driver string) string { func showMigrationsTableSQL(driver string) string {
switch driver { switch driver {
case "mysql": case "mysql":
return "SHOW TABLES LIKE 'migrations'" 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 { switch driver {
case "mysql": case "mysql":
return MYSQL_MIGRATION_DDL return MYSQLMigrationDDL
case "postgres": case "postgres":
return POSTGRES_MIGRATION_DDL return POSTGRESMigrationDDL
default: default:
return MYSQL_MIGRATION_DDL return MYSQLMigrationDDL
} }
} }
func selectMigrationsTableSql(driver string) string { func selectMigrationsTableSQL(driver string) string {
switch driver { switch driver {
case "mysql": case "mysql":
return "DESC migrations" 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) ColorLog("[ERRO] Could not create file: %s\n", err)
os.Exit(2) os.Exit(2)
} else { } 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, "{{ConnStr}}", connStr, -1)
content = strings.Replace(content, "{{LatestTime}}", strconv.FormatInt(latestTime, 10), -1) content = strings.Replace(content, "{{LatestTime}}", strconv.FormatInt(latestTime, 10), -1)
content = strings.Replace(content, "{{LatestName}}", latestName, -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) ColorLog("[ERRO] Could not write to file: %s\n", err)
os.Exit(2) os.Exit(2)
} }
f.Close() CloseFile(f)
} }
} }
@ -367,7 +370,7 @@ func formatShellOutput(o string) {
} }
const ( const (
MIGRATION_MAIN_TPL = `package main MigrationMainTPL = `package main
import( import(
"os" "os"
@ -406,7 +409,7 @@ func main(){
} }
` `
MYSQL_MIGRATION_DDL = ` MYSQLMigrationDDL = `
CREATE TABLE migrations ( CREATE TABLE migrations (
id_migration int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key', id_migration int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key',
name varchar(255) DEFAULT NULL COMMENT 'migration name, unique', name varchar(255) DEFAULT NULL COMMENT 'migration name, unique',
@ -418,7 +421,7 @@ CREATE TABLE migrations (
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ) ENGINE=InnoDB DEFAULT CHARSET=utf8
` `
POSTGRES_MIGRATION_DDL = ` POSTGRESMigrationDDL = `
CREATE TYPE migrations_status AS ENUM('update', 'rollback'); CREATE TYPE migrations_status AS ENUM('update', 'rollback');
CREATE TABLE migrations ( CREATE TABLE migrations (

98
new.go
View File

@ -55,100 +55,67 @@ func init() {
} }
func createApp(cmd *Command, args []string) int { func createApp(cmd *Command, args []string) int {
curpath, _ := os.Getwd() ShowShortVersionBanner()
w := NewColorWriter(os.Stdout)
if len(args) != 1 { if len(args) != 1 {
ColorLog("[ERRO] Argument [appname] is missing\n") ColorLog("[ERRO] Argument [appname] is missing\n")
os.Exit(2) os.Exit(2)
} }
apppath, packpath, err := checkEnv(args[0])
gopath := os.Getenv("GOPATH") if err != nil {
Debugf("gopath:%s", gopath) fmt.Println(err)
if gopath == "" {
ColorLog("[ERRO] $GOPATH not found\n")
ColorLog("[HINT] Set $GOPATH in your environment vairables\n")
os.Exit(2) 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) { if isExist(apppath) {
ColorLog("[ERRO] Path (%s) already exists\n", 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() { if !askForConfirmation() {
os.Exit(2) os.Exit(2)
} }
} }
fmt.Println("[INFO] Creating application...") ColorLog("[INFO] Creating application...\n")
os.MkdirAll(apppath, 0755) 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) 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) 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) 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) 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) 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) 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) 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) 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) os.Mkdir(path.Join(apppath, "static", "img"), 0755)
fmt.Println(path.Join(apppath, "static", "img") + 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.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, "views")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "views"), 0755) os.Mkdir(path.Join(apppath, "views"), 0755)
fmt.Println(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", "app.conf"), "\x1b[0m")
writetofile(path.Join(apppath, "conf", "app.conf"), strings.Replace(appconf, "{{.Appname}}", args[0], -1)) WriteToFile(path.Join(apppath, "conf", "app.conf"), strings.Replace(appconf, "{{.Appname}}", path.Base(args[0]), -1))
fmt.Println(path.Join(apppath, "controllers", "default.go")) 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) WriteToFile(path.Join(apppath, "controllers", "default.go"), controllers)
fmt.Println(path.Join(apppath, "views", "index.tpl")) 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) WriteToFile(path.Join(apppath, "views", "index.tpl"), indextpl)
fmt.Println(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(router, "{{.Appname}}", strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/"), -1)) WriteToFile(path.Join(apppath, "routers", "router.go"), strings.Replace(router, "{{.Appname}}", packpath, -1))
fmt.Println(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(test, "{{.Appname}}", strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/"), -1)) WriteToFile(path.Join(apppath, "tests", "default_test.go"), strings.Replace(test, "{{.Appname}}", packpath, -1))
fmt.Println(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(maingo, "{{.Appname}}", strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/"), -1)) WriteToFile(path.Join(apppath, "main.go"), strings.Replace(maingo, "{{.Appname}}", packpath, -1))
ColorLog("[SUCC] New application successfully created!\n") ColorLog("[SUCC] New application successfully created!\n")
return 0 return 0
@ -336,11 +303,12 @@ var indextpl = `<!DOCTYPE html>
</html> </html>
` `
func writetofile(filename, content string) { // WriteToFile creates a file and writes content to it
func WriteToFile(filename, content string) {
f, err := os.Create(filename) f, err := os.Create(filename)
defer CloseFile(f)
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer f.Close()
f.WriteString(content) f.WriteString(content)
} }

38
pack.go
View File

@ -72,6 +72,7 @@ var (
buildEnvs ListOpts buildEnvs ListOpts
verbose bool verbose bool
format string format string
w io.Writer
) )
type ListOpts []string type ListOpts []string
@ -101,6 +102,7 @@ func init() {
fs.BoolVar(&verbose, "v", false, "verbose") fs.BoolVar(&verbose, "v", false, "verbose")
cmdPack.Flag = *fs cmdPack.Flag = *fs
cmdPack.Run = packApp cmdPack.Run = packApp
w = NewColorWriter(os.Stdout)
} }
func exitPrint(con string) { 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 added, err := wft.wak.compress(name, fpath, fi); added {
if verbose { 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 wft.allfiles[name] = true
return err return err
} else {
return err
} }
return err
} }
func (wft *walkFileTree) iterDirectory(fpath string, fi os.FileInfo) error { 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 { if err != nil {
return false, err return false, err
} }
defer fr.Close() defer CloseFile(fr)
_, err = io.Copy(tw, fr) _, err = io.Copy(tw, fr)
if err != nil { if err != nil {
return false, err return false, err
@ -375,7 +376,7 @@ func (wft *zipWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
defer fr.Close() defer CloseFile(fr)
_, err = io.Copy(w, fr) _, err = io.Copy(w, fr)
if err != nil { if err != nil {
return false, err 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, func packDirectory(excludePrefix []string, excludeSuffix []string,
excludeRegexp []*regexp.Regexp, includePath ...string) (err error) { excludeRegexp []*regexp.Regexp, includePath ...string) (err error) {
fmt.Printf("exclude relpath prefix: %s\n", strings.Join(excludePrefix, ":")) ColorLog("Excluding relpath prefix: %s\n", strings.Join(excludePrefix, ":"))
fmt.Printf("exclude relpath suffix: %s\n", strings.Join(excludeSuffix, ":")) ColorLog("Excluding relpath suffix: %s\n", strings.Join(excludeSuffix, ":"))
if len(excludeRegexp) > 0 { 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) 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 { func packApp(cmd *Command, args []string) int {
ShowShortVersionBanner()
curPath, _ := os.Getwd() curPath, _ := os.Getwd()
thePath := "" thePath := ""
@ -493,17 +496,17 @@ func packApp(cmd *Command, args []string) int {
thePath, err := path.Abs(appPath) thePath, err := path.Abs(appPath)
if err != nil { 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 { 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 { 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) appName := path.Base(thePath)
@ -523,8 +526,7 @@ func packApp(cmd *Command, args []string) int {
os.Mkdir(tmpdir, 0700) os.Mkdir(tmpdir, 0700)
if build { if build {
fmt.Println("build", appName) ColorLog("Building application...\n")
var envs []string var envs []string
for _, env := range buildEnvs { for _, env := range buildEnvs {
parts := strings.SplitN(env, "=", 2) parts := strings.SplitN(env, "=", 2)
@ -546,7 +548,7 @@ func packApp(cmd *Command, args []string) int {
os.Setenv("GOOS", goos) os.Setenv("GOOS", goos)
os.Setenv("GOARCH", goarch) os.Setenv("GOARCH", goarch)
fmt.Println("GOOS", goos, "GOARCH", goarch) ColorLog("Env: GOOS=%s GOARCH=%s\n", goos, goarch)
binPath := path.Join(tmpdir, appName) binPath := path.Join(tmpdir, appName)
if goos == "windows" { if goos == "windows" {
@ -559,7 +561,7 @@ func packApp(cmd *Command, args []string) int {
} }
if verbose { 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...) execmd := exec.Command("go", args...)
@ -572,7 +574,7 @@ func packApp(cmd *Command, args []string) int {
exitPrint(err.Error()) exitPrint(err.Error())
} }
fmt.Println("build success") ColorLog("Build successful\n")
} }
switch format { switch format {
@ -624,6 +626,6 @@ func packApp(cmd *Command, args []string) int {
exitPrint(err.Error()) exitPrint(err.Error())
} }
fmt.Printf("file write to `%s`\n", outputP) ColorLog("Writing to output: `%s`\n", outputP)
return 0 return 0
} }

118
run.go
View File

@ -17,16 +17,14 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"os/exec"
path "path/filepath" path "path/filepath"
"runtime" "runtime"
"strings" "strings"
) )
var cmdRun = &Command{ 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", Short: "run the app and start a Web server for development",
Long: ` Long: `
Run command will supervise the file system of the beego project using inotify, 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 (
mainFiles ListOpts
var downdoc docValue downdoc docValue
var gendoc docValue gendoc docValue
// The flags list of the paths excluded from watching
// The flags list of the paths excluded from watching excludedPaths strFlags
var excludedPaths strFlags // Pass through to -tags arg of "go build"
buildTags string
// Pass through to -tags arg of "go build" // Application path
var buildTags string 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() { func init() {
cmdRun.Run = runApp cmdRun.Run = runApp
@ -52,58 +59,59 @@ func init() {
cmdRun.Flag.Var(&gendoc, "gendoc", "auto generate the docs") cmdRun.Flag.Var(&gendoc, "gendoc", "auto generate the docs")
cmdRun.Flag.Var(&downdoc, "downdoc", "auto download swagger file when not exist") cmdRun.Flag.Var(&downdoc, "downdoc", "auto download swagger file when not exist")
cmdRun.Flag.Var(&excludedPaths, "e", "Excluded paths[].") 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/)") 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 { func runApp(cmd *Command, args []string) int {
fmt.Println("bee :" + version) ShowShortVersionBanner()
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()
if len(args) == 0 || args[0] == "watchall" { if len(args) == 0 || args[0] == "watchall" {
appname = path.Base(crupath) currpath, _ = os.Getwd()
ColorLog("[INFO] Uses '%s' as 'appname'\n", appname)
if found, _gopath, _ := SearchGOPATHs(currpath); found {
appname = path.Base(currpath)
currentGoPath = _gopath
} else { } else {
appname = args[0] exitPrint(fmt.Sprintf("Bee does not support non Beego project: %s", currpath))
ColorLog("[INFO] Uses '%s' as 'appname'\n", appname) }
if strings.HasSuffix(appname, ".go") && isExist(path.Join(crupath, appname)) { ColorLog("[INFO] Using '%s' as 'appname'\n", appname)
ColorLog("[WARN] The appname has conflic with crupath's file, do you want to build appname as %s\n", appname) } else {
// 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]] ") ColorLog("[INFO] Do you want to overwrite it? [yes|no]] ")
if !askForConfirmation() { if !askForConfirmation() {
return 0 return 0
} }
} }
} }
Debugf("current path:%s\n", crupath)
err = loadConfig() Debugf("current path:%s\n", currpath)
err := loadConfig()
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err) ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err)
} }
var paths []string var paths []string
readAppDirectories(currpath, &paths)
readAppDirectories(crupath, &paths)
// Because monitor files has some issues, we watch current directory // Because monitor files has some issues, we watch current directory
// and ignore non-go files. // 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 { 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{} files := []string{}
@ -112,7 +120,14 @@ func runApp(cmd *Command, args []string) int {
files = append(files, arg) 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" { if gendoc == "true" {
NewWatcher(paths, files, true) NewWatcher(paths, files, true)
Autobuild(files, true) Autobuild(files, true)
@ -120,14 +135,7 @@ func runApp(cmd *Command, args []string) int {
NewWatcher(paths, files, false) NewWatcher(paths, files, false)
Autobuild(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 { for {
select { select {
case <-exit: case <-exit:
@ -147,6 +155,13 @@ func readAppDirectories(directory string, paths *[]string) {
if strings.HasSuffix(fileInfo.Name(), "docs") { if strings.HasSuffix(fileInfo.Name(), "docs") {
continue continue
} }
if strings.HasSuffix(fileInfo.Name(), "swagger") {
continue
}
if !vendorWatch && strings.HasSuffix(fileInfo.Name(), "vendor") {
continue
}
if isExcluded(path.Join(directory, fileInfo.Name())) { if isExcluded(path.Join(directory, fileInfo.Name())) {
continue continue
@ -166,7 +181,6 @@ func readAppDirectories(directory string, paths *[]string) {
useDirectory = true useDirectory = true
} }
} }
return return
} }

View File

@ -20,7 +20,7 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"path/filepath" "strings"
) )
var cmdRundocs = &Command{ var cmdRundocs = &Command{
@ -33,8 +33,9 @@ var cmdRundocs = &Command{
`, `,
} }
const ( var (
swaggerlink = "https://github.com/beego/swagger/archive/v1.zip" swaggerVersion = "2"
swaggerlink = "https://github.com/beego/swagger/archive/v" + swaggerVersion + ".zip"
) )
type docValue string type docValue string
@ -59,8 +60,8 @@ func init() {
func runDocs(cmd *Command, args []string) int { func runDocs(cmd *Command, args []string) int {
if isDownload == "true" { if isDownload == "true" {
downloadFromUrl(swaggerlink, "swagger.zip") downloadFromURL(swaggerlink, "swagger.zip")
err := unzipAndDelete("swagger.zip", "swagger") err := unzipAndDelete("swagger.zip")
if err != nil { if err != nil {
fmt.Println("has err exet unzipAndDelete", err) fmt.Println("has err exet unzipAndDelete", err)
} }
@ -77,40 +78,50 @@ func runDocs(cmd *Command, args []string) int {
return 0 return 0
} }
func downloadFromUrl(url, fileName string) { func downloadFromURL(url, fileName string) {
fmt.Println("Downloading", url, "to", fileName) 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
}
if down {
ColorLog("[%s]Downloading %s to %s\n", SUCC, url, fileName)
output, err := os.Create(fileName) output, err := os.Create(fileName)
if err != nil { if err != nil {
fmt.Println("Error while creating", fileName, "-", err) ColorLog("[%s]Error while creating %s: %s\n", ERRO, fileName, err)
return return
} }
defer output.Close() defer output.Close()
response, err := http.Get(url) response, err := http.Get(url)
if err != nil { if err != nil {
fmt.Println("Error while downloading", url, "-", err) ColorLog("[%s]Error while downloading %s:%s\n", ERRO, url, err)
return return
} }
defer response.Body.Close() defer response.Body.Close()
n, err := io.Copy(output, response.Body) n, err := io.Copy(output, response.Body)
if err != nil { if err != nil {
fmt.Println("Error while downloading", url, "-", err) ColorLog("[%s]Error while downloading %s:%s\n", ERRO, url, err)
return return
} }
ColorLog("[%s] %d bytes downloaded.\n", SUCC, n)
fmt.Println(n, "bytes downloaded.") }
} }
func unzipAndDelete(src, dest string) error { func unzipAndDelete(src string) error {
fmt.Println("start to unzip file from " + src + " to " + dest) ColorLog("[%s]start to unzip file from %s\n", INFO, src)
r, err := zip.OpenReader(src) r, err := zip.OpenReader(src)
if err != nil { if err != nil {
return err return err
} }
defer r.Close() defer r.Close()
rp := strings.NewReplacer("swagger-"+swaggerVersion, "swagger")
for _, f := range r.File { for _, f := range r.File {
rc, err := f.Open() rc, err := f.Open()
if err != nil { if err != nil {
@ -118,12 +129,12 @@ func unzipAndDelete(src, dest string) error {
} }
defer rc.Close() defer rc.Close()
path := filepath.Join(dest, f.Name) fname := rp.Replace(f.Name)
if f.FileInfo().IsDir() { if f.FileInfo().IsDir() {
os.MkdirAll(path, f.Mode()) os.MkdirAll(fname, f.Mode())
} else { } else {
f, err := os.OpenFile( 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 { if err != nil {
return err return err
} }
@ -135,11 +146,6 @@ func unzipAndDelete(src, dest string) error {
} }
} }
} }
ColorLog("[%s]Start delete src file %s\n", INFO, src)
fmt.Println("Start delete src file " + src) return os.RemoveAll(src)
err = os.RemoveAll(src)
if err != nil {
return err
}
return nil
} }

39
util.go
View File

@ -15,13 +15,14 @@
package main package main
import ( import (
"fmt"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"time" "time"
"path"
"fmt"
) )
// Go is a basic promise implementation: it wraps calls a function in a goroutine // Go is a basic promise implementation: it wraps calls a function in a goroutine
@ -173,6 +174,34 @@ func GetGOPATHs() []string {
return paths 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 // 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 // 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 // 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) *s = append(*s, value)
return nil 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)
}
}

View File

@ -2,53 +2,81 @@ package main
import ( import (
"bufio" "bufio"
"bytes"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"os/exec"
path "path/filepath" path "path/filepath"
"regexp" "regexp"
) )
var cmdVersion = &Command{ var cmdVersion = &Command{
UsageLine: "version", UsageLine: "version",
Short: "show the Bee, Beego and Go version", Short: "prints the current Bee version",
Long: ` Long: `
show the Bee, Beego and Go version Prints the current Bee, Beego and Go version alongside the platform information
bee version
bee :1.2.3
beego :1.4.2
Go :go version go1.3.3 linux/amd64
`, `,
} }
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() { func init() {
cmdVersion.Run = versionCmd cmdVersion.Run = versionCmd
} }
func versionCmd(cmd *Command, args []string) int { func versionCmd(cmd *Command, args []string) int {
fmt.Println("bee :" + version) ShowVerboseVersionBanner()
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))
return 0 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") gopath := os.Getenv("GOPATH")
re, err := regexp.Compile(`VERSION = "([0-9.]+)"`) re, err := regexp.Compile(`VERSION = "([0-9.]+)"`)
if err != nil { if err != nil {
return "" return ""
} }
if gopath == "" { if gopath == "" {
err = fmt.Errorf("you should set GOPATH in the env") err = fmt.Errorf("You should set GOPATH env variable")
return "" return ""
} }
wgopath := path.SplitList(gopath) wgopath := path.SplitList(gopath)
@ -60,11 +88,11 @@ func getbeegoVersion() string {
if os.IsNotExist(err) { if os.IsNotExist(err) {
continue continue
} }
ColorLog("[ERRO] get beego.go has error\n") ColorLog("[ERRO] Get `beego.go` has error\n")
} }
fd, err := os.Open(filename) fd, err := os.Open(filename)
if err != nil { if err != nil {
ColorLog("[ERRO] open beego.go has error\n") ColorLog("[ERRO] Open `beego.go` has error\n")
continue continue
} }
reader := bufio.NewReader(fd) 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"
} }

View File

@ -17,14 +17,14 @@ package main
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/howeyc/fsnotify"
"os" "os"
"os/exec" "os/exec"
"regexp"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/howeyc/fsnotify"
) )
var ( var (
@ -47,8 +47,8 @@ func NewWatcher(paths []string, files []string, isgenerate bool) {
case e := <-watcher.Event: case e := <-watcher.Event:
isbuild := true isbuild := true
// Skip TMP files for Sublime Text. // Skip ignored files
if checkTMPFile(e.Name) { if shouldIgnoreFile(e.Name) {
continue continue
} }
if !checkIfWatchExt(e.Name) { if !checkIfWatchExt(e.Name) {
@ -121,8 +121,8 @@ func Autobuild(files []string, isgenerate bool) {
defer state.Unlock() defer state.Unlock()
ColorLog("[INFO] Start building...\n") ColorLog("[INFO] Start building...\n")
path, _ := os.Getwd()
os.Chdir(path) os.Chdir(currpath)
cmdName := "go" cmdName := "go"
if conf.Gopm.Enable { if conf.Gopm.Enable {
@ -230,15 +230,30 @@ func Start(appname string) {
started <- true started <- true
} }
// checkTMPFile returns true if the event was for TMP files. // Should ignore filenames generated by
func checkTMPFile(name string) bool { // Emacs, Vim or SublimeText
if strings.HasSuffix(strings.ToLower(name), ".tmp") { 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 return true
} else {
continue
}
} }
return false return false
} }
var watchExts = []string{".go"} var watchExts = []string{".go"}
var ignoredFilesRegExps = []string{
`.#(\w+).go`,
`.(\w+).go.swp`,
`(\w+).go~`,
`(\w+).tmp`,
}
// checkIfWatchExt returns true if the name HasSuffix <watch_ext>. // checkIfWatchExt returns true if the name HasSuffix <watch_ext>.
func checkIfWatchExt(name string) bool { func checkIfWatchExt(name string) bool {