bee generate scaffold model

This commit is contained in:
astaxie 2014-08-11 17:01:00 +08:00
parent c58ee3ca2c
commit cd31fe26ed
6 changed files with 359 additions and 23 deletions

View File

@ -585,7 +585,7 @@ func createapi(cmd *Command, args []string) {
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'", tables) ColorLog("[INFO] Using '%s' as 'tables'", tables)
generateModel(string(driver), string(conn), "3", string(tables), path.Join(curpath, packpath)) generateAppcode(string(driver), string(conn), "3", string(tables), path.Join(curpath, packpath))
} 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.Println("create models:", path.Join(apppath, "models"))

65
g.go
View File

@ -20,20 +20,13 @@ var cmdGenerate = &Command{
UsageLine: "generate [Command]", UsageLine: "generate [Command]",
Short: "generate code based on application", Short: "generate code based on application",
Long: ` Long: `
bee generate scaffold [modelname] [-fields=""] bee generate scaffold [scaffoldname] [-fields=""]
The generate scaffold command will do a number of things for you The generate scaffold command will do a number of things for you
-fields: a list of database fields. -fields: a list of database fields.
example: bee generate scaffold post -fields="title:string,body:text" example: bee generate scaffold post -fields="title:string,body:text"
bee generate model [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test] [-level=1]
generate model based on an existing database
-tables: a list of table names separated by ',', default is empty, indicating all tables
-driver: [mysql | postgresql | sqlite], the default is mysql
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test
-level: [1 | 2 | 3], 1 = model; 2 = models,controller; 3 = models,controllers,router
bee generate migration [migrationfile] bee generate model [modelname] [-fields=""]
generate migration file for making database schema update generate RESTFul model based on fields
bee generate controller [controllerfile] bee generate controller [controllerfile]
generate RESTFul controllers generate RESTFul controllers
@ -41,11 +34,21 @@ bee generate controller [controllerfile]
bee generate view [viewpath] bee generate view [viewpath]
generate CRUD view in viewpath generate CRUD view in viewpath
bee generate migration [migrationfile]
generate migration file for making database schema update
bee generate docs bee generate docs
generate swagger doc file generate swagger doc file
bee generate test [routerfile] bee generate test [routerfile]
generate testcase generate testcase
bee generate appcode [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test] [-level=1]
generate appcode based on an existing database
-tables: a list of table names separated by ',', default is empty, indicating all tables
-driver: [mysql | postgresql | sqlite], the default is mysql
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test
-level: [1 | 2 | 3], 1 = model; 2 = models,controller; 3 = models,controllers,router
`, `,
} }
@ -53,6 +56,7 @@ var driver docValue
var conn docValue var conn docValue
var level docValue var level docValue
var tables docValue var tables docValue
var fields docValue
func init() { func init() {
cmdGenerate.Run = generateCode cmdGenerate.Run = generateCode
@ -60,6 +64,7 @@ func init() {
cmdGenerate.Flag.Var(&driver, "driver", "database driver: mysql, postgresql, etc.") cmdGenerate.Flag.Var(&driver, "driver", "database driver: mysql, postgresql, etc.")
cmdGenerate.Flag.Var(&conn, "conn", "connection string used by the driver to connect to a database instance") cmdGenerate.Flag.Var(&conn, "conn", "connection string used by the driver to connect to a database instance")
cmdGenerate.Flag.Var(&level, "level", "1 = models only; 2 = models and controllers; 3 = models, controllers and routers") cmdGenerate.Flag.Var(&level, "level", "1 = models only; 2 = models and controllers; 3 = models, controllers and routers")
cmdGenerate.Flag.Var(&fields, "fields", "specify the fields want to generate.")
} }
func generateCode(cmd *Command, args []string) { func generateCode(cmd *Command, args []string) {
@ -79,9 +84,24 @@ func generateCode(cmd *Command, args []string) {
gcmd := args[0] gcmd := args[0]
switch gcmd { switch gcmd {
case "scaffold":
if len(args) < 2 {
ColorLog("[ERRO] Wrong number of arguments\n")
ColorLog("[HINT] Usage: bee generate scaffold [scaffoldname] [-fields=\"\"]\n")
os.Exit(2)
}
cmd.Flag.Parse(args[2:])
if fields == "" {
ColorLog("[ERRO] Wrong number of arguments\n")
ColorLog("[HINT] Usage: bee generate scaffold [scaffoldname] [-fields=\"title:string,body:text\"]\n")
os.Exit(2)
}
sname := args[1]
ColorLog("[INFO] Using '%s' as scaffold name\n", sname)
generateScaffold(sname, fields.String(), curpath)
case "docs": case "docs":
generateDocs(curpath) generateDocs(curpath)
case "model": case "appcode":
cmd.Flag.Parse(args[1:]) cmd.Flag.Parse(args[1:])
if driver == "" { if driver == "" {
driver = "mysql" driver = "mysql"
@ -96,7 +116,7 @@ func generateCode(cmd *Command, args []string) {
ColorLog("[INFO] Using '%s' as 'conn'\n", conn) ColorLog("[INFO] Using '%s' as 'conn'\n", conn)
ColorLog("[INFO] Using '%s' as 'tables'", tables) ColorLog("[INFO] Using '%s' as 'tables'", tables)
ColorLog("[INFO] Using '%s' as 'level'\n", level) ColorLog("[INFO] Using '%s' as 'level'\n", level)
generateModel(string(driver), string(conn), string(level), string(tables), curpath) generateAppcode(string(driver), string(conn), string(level), string(tables), curpath)
case "migration": case "migration":
if len(args) == 2 { if len(args) == 2 {
mname := args[1] mname := args[1]
@ -104,7 +124,7 @@ func generateCode(cmd *Command, args []string) {
generateMigration(mname, curpath) generateMigration(mname, curpath)
} else { } else {
ColorLog("[ERRO] Wrong number of arguments\n") ColorLog("[ERRO] Wrong number of arguments\n")
ColorLog("[HINT] Usage: bee generate migration [filename]\n") ColorLog("[HINT] Usage: bee generate migration [migrationname]\n")
os.Exit(2) os.Exit(2)
} }
case "controller": case "controller":
@ -113,16 +133,31 @@ func generateCode(cmd *Command, args []string) {
generateController(cname, curpath) generateController(cname, curpath)
} else { } else {
ColorLog("[ERRO] Wrong number of arguments\n") ColorLog("[ERRO] Wrong number of arguments\n")
ColorLog("[HINT] Usage: bee generate controller [filename]\n") ColorLog("[HINT] Usage: bee generate controller [controllername]\n")
os.Exit(2) os.Exit(2)
} }
case "model":
if len(args) < 2 {
ColorLog("[ERRO] Wrong number of arguments\n")
ColorLog("[HINT] Usage: bee generate model [modelname] [-fields=\"\"]\n")
os.Exit(2)
}
cmd.Flag.Parse(args[2:])
if fields == "" {
ColorLog("[ERRO] Wrong number of arguments\n")
ColorLog("[HINT] Usage: bee generate model [modelname] [-fields=\"title:string,body:text\"]\n")
os.Exit(2)
}
sname := args[1]
ColorLog("[INFO] Using '%s' as model name\n", sname)
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, curpath)
} else { } else {
ColorLog("[ERRO] Wrong number of arguments\n") ColorLog("[ERRO] Wrong number of arguments\n")
ColorLog("[HINT] Usage: bee generate view [filename]\n") ColorLog("[HINT] Usage: bee generate view [viewpath]\n")
os.Exit(2) os.Exit(2)
} }
default: default:

View File

@ -191,7 +191,7 @@ func (tag *OrmTag) String() string {
return fmt.Sprintf("`orm:\"%s\"`", strings.Join(ormOptions, ";")) return fmt.Sprintf("`orm:\"%s\"`", strings.Join(ormOptions, ";"))
} }
func generateModel(driver string, connStr string, level string, tables string, currpath string) { func generateAppcode(driver string, connStr string, level string, tables string, currpath string) {
var mode byte var mode byte
if level == "1" { if level == "1" {
mode = O_MODEL mode = O_MODEL
@ -663,14 +663,12 @@ func getFileName(tbName string) (filename string) {
} }
const ( const (
STRUCT_MODEL_TPL = ` STRUCT_MODEL_TPL = `package models
package models
{{modelStruct}} {{modelStruct}}
` `
MODEL_TPL = ` MODEL_TPL = `package models
package models
{{modelStruct}} {{modelStruct}}

271
g_model.go Normal file
View File

@ -0,0 +1,271 @@
package main
import (
"errors"
"os"
"path"
"strings"
)
func generateModel(mname, fields, crupath string) {
p, f := path.Split(mname)
modelName := strings.Title(f)
packageName := "models"
if p != "" {
i := strings.LastIndex(p[:len(p)-1], "/")
packageName = p[i+1 : len(p)-1]
}
modelStruct, err := getStruct(modelName, fields)
if err != nil {
ColorLog("[ERRO] Could not genrate models struct: %s\n", err)
os.Exit(2)
}
ColorLog("[INFO] Using '%s' as model name\n", modelName)
ColorLog("[INFO] Using '%s' as package name\n", packageName)
fp := path.Join(crupath, "models", p)
if _, err := os.Stat(fp); os.IsNotExist(err) {
// create controller directory
if err := os.MkdirAll(fp, 0777); err != nil {
ColorLog("[ERRO] Could not create models directory: %s\n", err)
os.Exit(2)
}
}
fpath := path.Join(fp, strings.ToLower(modelName)+".go")
if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer f.Close()
content := strings.Replace(modelTpl, "{{packageName}}", packageName, -1)
content = strings.Replace(content, "{{modelName}}", modelName, -1)
content = strings.Replace(content, "{{modelStruct}}", modelStruct, -1)
f.WriteString(content)
// gofmt generated source code
formatAndFixImports(fpath)
ColorLog("[INFO] model file generated: %s\n", fpath)
} else {
// error creating file
ColorLog("[ERRO] Could not create model file: %s\n", err)
os.Exit(2)
}
}
func getStruct(structname, fields string) (string, error) {
if fields == "" {
return "", errors.New("fields can't empty")
}
structStr := "type " + structname + " struct{\n"
fds := strings.Split(fields, ",")
for i, v := range fds {
kv := strings.SplitN(v, ":", 2)
if len(kv) != 2 {
return "", errors.New("the filds format is wrong. should key:type,key:type " + v)
}
typ, tag := getType(kv[1])
if typ == "" {
return "", errors.New("the filds format is wrong. should key:type,key:type " + v)
}
if i == 0 && strings.ToLower(kv[0]) != "id" {
structStr = structStr + "Id int64 `orm:\"auto\"`\n"
}
structStr = structStr + camelString(kv[0]) + " " + typ + " " + tag + "\n"
}
structStr += "}\n"
return structStr, nil
}
func camelString(s string) string {
data := make([]byte, 0, len(s))
j := false
k := false
num := len(s) - 1
for i := 0; i <= num; i++ {
d := s[i]
if k == false && d >= 'A' && d <= 'Z' {
k = true
}
if d >= 'a' && d <= 'z' && (j || k == false) {
d = d - 32
j = false
k = true
}
if k && d == '_' && num > i && s[i+1] >= 'a' && s[i+1] <= 'z' {
j = true
continue
}
data = append(data, d)
}
return string(data[:len(data)])
}
// fields support type
// http://beego.me/docs/mvc/model/models.md#mysql
func getType(ktype string) (kt, tag string) {
kv := strings.SplitN(ktype, ":", 2)
switch kv[0] {
case "string":
if len(kv) == 2 {
return "string", "`orm:\"size(" + kv[1] + ")\"`"
} else {
return "string", "`orm:\"size(128)\"`"
}
case "text":
return "string", "`orm:\"type(longtext)\"`"
case "auto":
return "int64", "`orm:\"auto\"`"
case "pk":
return "int64", "`orm:\"pk\"`"
case "datetime":
return "time.Time", "`orm:\"type(datetime)\"`"
case "int", "int8", "int16", "int32", "int64":
fallthrough
case "uint", "uint8", "uint16", "uint32", "uint64":
fallthrough
case "bool":
fallthrough
case "float32", "float64":
return kv[0], ""
case "float":
return "float64", ""
}
return "", ""
}
var modelTpl = `package {{packageName}}
import (
"errors"
"fmt"
"reflect"
"strings"
"time"
"github.com/astaxie/beego/orm"
)
{{modelStruct}}
func init() {
orm.RegisterModel(new({{modelName}}))
}
// Add{{modelName}} insert a new {{modelName}} into database and returns
// last inserted Id on success.
func Add{{modelName}}(m *{{modelName}}) (id int64, err error) {
o := orm.NewOrm()
id, err = o.Insert(m)
return
}
// Get{{modelName}}ById retrieves {{modelName}} by Id. Returns error if
// Id doesn't exist
func Get{{modelName}}ById(id int) (v *{{modelName}}, err error) {
o := orm.NewOrm()
v = &{{modelName}}{Id: id}
if err = o.Read(v); err == nil {
return v, nil
}
return nil, err
}
// GetAll{{modelName}} retrieves all {{modelName}} matches certain condition. Returns empty list if
// no records exist
func GetAll{{modelName}}(query map[string]string, fields []string, sortby []string, order []string,
offset int64, limit int64) (ml []interface{}, err error) {
o := orm.NewOrm()
qs := o.QueryTable(new({{modelName}}))
// query k=v
for k, v := range query {
// rewrite dot-notation to Object__Attribute
k = strings.Replace(k, ".", "__", -1)
qs = qs.Filter(k, v)
}
// order by:
var sortFields []string
if len(sortby) != 0 {
if len(sortby) == len(order) {
// 1) for each sort field, there is an associated order
for i, v := range sortby {
orderby := ""
if order[i] == "desc" {
orderby = "-" + v
} else if order[i] == "asc" {
orderby = v
} else {
return nil, errors.New("Error: Invalid order. Must be either [asc|desc]")
}
sortFields = append(sortFields, orderby)
}
qs = qs.OrderBy(sortFields...)
} else if len(sortby) != len(order) && len(order) == 1 {
// 2) there is exactly one order, all the sorted fields will be sorted by this order
for _, v := range sortby {
orderby := ""
if order[0] == "desc" {
orderby = "-" + v
} else if order[0] == "asc" {
orderby = v
} else {
return nil, errors.New("Error: Invalid order. Must be either [asc|desc]")
}
sortFields = append(sortFields, orderby)
}
} else if len(sortby) != len(order) && len(order) != 1 {
return nil, errors.New("Error: 'sortby', 'order' sizes mismatch or 'order' size is not 1")
}
} else {
if len(order) != 0 {
return nil, errors.New("Error: unused 'order' fields")
}
}
var l []{{modelName}}
qs = qs.OrderBy(sortFields...)
if _, err := qs.Limit(limit, offset).All(&l, fields...); err == nil {
if len(fields) == 0 {
for _, v := range l {
ml = append(ml, v)
}
} else {
// trim unused fields
for _, v := range l {
m := make(map[string]interface{})
val := reflect.ValueOf(v)
for _, fname := range fields {
m[fname] = val.FieldByName(fname).Interface()
}
ml = append(ml, m)
}
}
return ml, nil
}
return nil, err
}
// Update{{modelName}} updates {{modelName}} by Id and returns error if
// the record to be updated doesn't exist
func Update{{modelName}}ById(m *{{modelName}}) (err error) {
o := orm.NewOrm()
v := {{modelName}}{Id: m.Id}
// ascertain id exists in the database
if err = o.Read(&v); err == nil {
var num int64
if num, err = o.Update(m); err == nil {
fmt.Println("Number of records updated in database:", num)
}
}
return
}
// Delete{{modelName}} deletes {{modelName}} by Id and returns error if
// the record to be deleted doesn't exist
func Delete{{modelName}}(id int) (err error) {
o := orm.NewOrm()
v := {{modelName}}{Id: id}
// ascertain id exists in the database
if err = o.Read(&v); err == nil {
var num int64
if num, err = o.Delete(&{{modelName}}{Id: id}); err == nil {
fmt.Println("Number of records deleted in database:", num)
}
}
return
}
`

View File

@ -1 +1,33 @@
package main package main
import "strings"
func generateScaffold(sname, fields, crupath string) {
// generate model
ColorLog("[INFO] Do you want me to create a %v model? [yes|no]] ", sname)
if askForConfirmation() {
generateModel(sname, fields, crupath)
}
// generate controller
ColorLog("[INFO] Do you want me to create a %v controller? [yes|no]] ", sname)
if askForConfirmation() {
generateController(sname, crupath)
}
// generate view
ColorLog("[INFO] Do you want me to create views for this %v resource? [yes|no]] ", sname)
if askForConfirmation() {
generateView(sname, crupath)
}
// generate migration
ColorLog("[INFO] Do you want me to create a %v migration and schema for this resource? [yes|no]] ", sname)
if askForConfirmation() {
generateMigration(sname, crupath)
}
// run migration
ColorLog("[INFO] Do you want to go ahead and migrate the database? [yes|no]] ")
if askForConfirmation() {
// @todo
}
ColorLog("[INFO] All done! Don't forget to add beego.Router(\"/%v\" ,&controllers.%vController{}) to routers/route.go\n", sname, strings.Title(sname))
}

View File

@ -8,7 +8,7 @@ import (
// recipe // recipe
// admin/recipe // admin/recipe
func generateView(vpath, crupath string) { func generateView(vpath, crupath string) {
absvpath := path.Join(crupath, vpath) absvpath := path.Join(crupath, "views", vpath)
os.MkdirAll(absvpath, os.ModePerm) os.MkdirAll(absvpath, os.ModePerm)
cfile := path.Join(absvpath, "index.tpl") cfile := path.Join(absvpath, "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 {