mirror of
https://github.com/beego/bee.git
synced 2025-06-12 04:50:40 +00:00
Refactor!
create sub packages delete unused code delete code from not use command cmdRouter,cmdTest, cmdRundocs make command plugins check with gosimple,staticcheck,go vet,unused,unconvert
This commit is contained in:
102
cmd/bee.go
Normal file
102
cmd/bee.go
Normal file
@ -0,0 +1,102 @@
|
||||
// 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.
|
||||
|
||||
// Bee is a tool for developing applications based on beego framework.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/beego/bee/cmd/commands"
|
||||
_ "github.com/beego/bee/cmd/commands/api"
|
||||
_ "github.com/beego/bee/cmd/commands/bale"
|
||||
_ "github.com/beego/bee/cmd/commands/beefix"
|
||||
_ "github.com/beego/bee/cmd/commands/dockerize"
|
||||
_ "github.com/beego/bee/cmd/commands/generate"
|
||||
_ "github.com/beego/bee/cmd/commands/hprose"
|
||||
_ "github.com/beego/bee/cmd/commands/migrate"
|
||||
_ "github.com/beego/bee/cmd/commands/new"
|
||||
_ "github.com/beego/bee/cmd/commands/pack"
|
||||
_ "github.com/beego/bee/cmd/commands/run"
|
||||
_ "github.com/beego/bee/cmd/commands/version"
|
||||
"github.com/beego/bee/utils"
|
||||
)
|
||||
|
||||
func IfGenerateDocs(name string, args []string) bool {
|
||||
if name != "generate" {
|
||||
return false
|
||||
}
|
||||
for _, a := range args {
|
||||
if a == "docs" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var usageTemplate = `Bee is a Fast and Flexible tool for managing your Beego Web Application.
|
||||
|
||||
{{"USAGE" | headline}}
|
||||
{{"bee command [arguments]" | bold}}
|
||||
|
||||
{{"AVAILABLE COMMANDS" | headline}}
|
||||
{{range .}}{{if .Runnable}}
|
||||
{{.Name | printf "%-11s" | bold}} {{.Short}}{{end}}{{end}}
|
||||
|
||||
Use {{"bee help [command]" | bold}} for more information about a command.
|
||||
|
||||
{{"ADDITIONAL HELP TOPICS" | headline}}
|
||||
{{range .}}{{if not .Runnable}}
|
||||
{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
|
||||
|
||||
Use {{"bee help [topic]" | bold}} for more information about that topic.
|
||||
`
|
||||
|
||||
var helpTemplate = `{{"USAGE" | headline}}
|
||||
{{.UsageLine | printf "bee %s" | bold}}
|
||||
{{if .Options}}{{endline}}{{"OPTIONS" | headline}}{{range $k,$v := .Options}}
|
||||
{{$k | printf "-%s" | bold}}
|
||||
{{$v}}
|
||||
{{end}}{{end}}
|
||||
{{"DESCRIPTION" | headline}}
|
||||
{{tmpltostr .Long . | trim}}
|
||||
`
|
||||
|
||||
var ErrorTemplate = `bee: %s.
|
||||
Use {{"bee help" | bold}} for more information.
|
||||
`
|
||||
|
||||
func Usage() {
|
||||
utils.Tmpl(usageTemplate, commands.AvailableCommands)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func Help(args []string) {
|
||||
if len(args) == 0 {
|
||||
Usage()
|
||||
}
|
||||
if len(args) != 1 {
|
||||
utils.PrintErrorAndExit("Too many arguments", ErrorTemplate)
|
||||
}
|
||||
|
||||
arg := args[0]
|
||||
|
||||
for _, cmd := range commands.AvailableCommands {
|
||||
if cmd.Name() == arg {
|
||||
utils.Tmpl(helpTemplate, cmd)
|
||||
return
|
||||
}
|
||||
}
|
||||
utils.PrintErrorAndExit("Unknown help topic", ErrorTemplate)
|
||||
}
|
634
cmd/commands/api/apiapp.go
Normal file
634
cmd/commands/api/apiapp.go
Normal file
@ -0,0 +1,634 @@
|
||||
// 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 apiapp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
path "path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/bee/cmd/commands"
|
||||
"github.com/beego/bee/cmd/commands/version"
|
||||
"github.com/beego/bee/generate"
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
"github.com/beego/bee/utils"
|
||||
)
|
||||
|
||||
var CmdApiapp = &commands.Command{
|
||||
// CustomFlags: true,
|
||||
UsageLine: "api [appname]",
|
||||
Short: "Creates a Beego API application",
|
||||
Long: `
|
||||
The command 'api' creates a Beego API application.
|
||||
|
||||
{{"Example:"|bold}}
|
||||
$ bee api [appname] [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test]
|
||||
|
||||
If 'conn' argument is empty, the command will generate an example API application. Otherwise the command
|
||||
will connect to your database and generate models based on the existing tables.
|
||||
|
||||
The command 'api' creates a folder named [appname] with the following structure:
|
||||
|
||||
├── main.go
|
||||
├── {{"conf"|foldername}}
|
||||
│ └── app.conf
|
||||
├── {{"controllers"|foldername}}
|
||||
│ └── object.go
|
||||
│ └── user.go
|
||||
├── {{"routers"|foldername}}
|
||||
│ └── router.go
|
||||
├── {{"tests"|foldername}}
|
||||
│ └── default_test.go
|
||||
└── {{"models"|foldername}}
|
||||
└── object.go
|
||||
└── user.go
|
||||
`,
|
||||
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
|
||||
Run: createapi,
|
||||
}
|
||||
var apiconf = `appname = {{.Appname}}
|
||||
httpport = 8080
|
||||
runmode = dev
|
||||
autorender = false
|
||||
copyrequestbody = true
|
||||
EnableDocs = true
|
||||
`
|
||||
var apiMaingo = `package main
|
||||
|
||||
import (
|
||||
_ "{{.Appname}}/routers"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if beego.BConfig.RunMode == "dev" {
|
||||
beego.BConfig.WebConfig.DirectoryIndex = true
|
||||
beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
|
||||
}
|
||||
beego.Run()
|
||||
}
|
||||
`
|
||||
|
||||
var apiMainconngo = `package main
|
||||
|
||||
import (
|
||||
_ "{{.Appname}}/routers"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/orm"
|
||||
{{.DriverPkg}}
|
||||
)
|
||||
|
||||
func init() {
|
||||
orm.RegisterDataBase("default", "{{.DriverName}}", "{{.conn}}")
|
||||
}
|
||||
|
||||
func main() {
|
||||
if beego.BConfig.RunMode == "dev" {
|
||||
beego.BConfig.WebConfig.DirectoryIndex = true
|
||||
beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
|
||||
}
|
||||
beego.Run()
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
var apirouter = `// @APIVersion 1.0.0
|
||||
// @Title beego Test API
|
||||
// @Description beego has a very cool tools to autogenerate documents for your API
|
||||
// @Contact astaxie@gmail.com
|
||||
// @TermsOfServiceUrl http://beego.me/
|
||||
// @License Apache 2.0
|
||||
// @LicenseUrl http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
package routers
|
||||
|
||||
import (
|
||||
"{{.Appname}}/controllers"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
func init() {
|
||||
ns := beego.NewNamespace("/v1",
|
||||
beego.NSNamespace("/object",
|
||||
beego.NSInclude(
|
||||
&controllers.ObjectController{},
|
||||
),
|
||||
),
|
||||
beego.NSNamespace("/user",
|
||||
beego.NSInclude(
|
||||
&controllers.UserController{},
|
||||
),
|
||||
),
|
||||
)
|
||||
beego.AddNamespace(ns)
|
||||
}
|
||||
`
|
||||
|
||||
var ApiModels = `package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
Objects map[string]*Object
|
||||
)
|
||||
|
||||
type Object struct {
|
||||
ObjectId string
|
||||
Score int64
|
||||
PlayerName string
|
||||
}
|
||||
|
||||
func init() {
|
||||
Objects = make(map[string]*Object)
|
||||
Objects["hjkhsbnmn123"] = &Object{"hjkhsbnmn123", 100, "astaxie"}
|
||||
Objects["mjjkxsxsaa23"] = &Object{"mjjkxsxsaa23", 101, "someone"}
|
||||
}
|
||||
|
||||
func AddOne(object Object) (ObjectId string) {
|
||||
object.ObjectId = "astaxie" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
Objects[object.ObjectId] = &object
|
||||
return object.ObjectId
|
||||
}
|
||||
|
||||
func GetOne(ObjectId string) (object *Object, err error) {
|
||||
if v, ok := Objects[ObjectId]; ok {
|
||||
return v, nil
|
||||
}
|
||||
return nil, errors.New("ObjectId Not Exist")
|
||||
}
|
||||
|
||||
func GetAll() map[string]*Object {
|
||||
return Objects
|
||||
}
|
||||
|
||||
func Update(ObjectId string, Score int64) (err error) {
|
||||
if v, ok := Objects[ObjectId]; ok {
|
||||
v.Score = Score
|
||||
return nil
|
||||
}
|
||||
return errors.New("ObjectId Not Exist")
|
||||
}
|
||||
|
||||
func Delete(ObjectId string) {
|
||||
delete(Objects, ObjectId)
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
var ApiModels2 = `package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
UserList map[string]*User
|
||||
)
|
||||
|
||||
func init() {
|
||||
UserList = make(map[string]*User)
|
||||
u := User{"user_11111", "astaxie", "11111", Profile{"male", 20, "Singapore", "astaxie@gmail.com"}}
|
||||
UserList["user_11111"] = &u
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Id string
|
||||
Username string
|
||||
Password string
|
||||
Profile Profile
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
Gender string
|
||||
Age int
|
||||
Address string
|
||||
Email string
|
||||
}
|
||||
|
||||
func AddUser(u User) string {
|
||||
u.Id = "user_" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
UserList[u.Id] = &u
|
||||
return u.Id
|
||||
}
|
||||
|
||||
func GetUser(uid string) (u *User, err error) {
|
||||
if u, ok := UserList[uid]; ok {
|
||||
return u, nil
|
||||
}
|
||||
return nil, errors.New("User not exists")
|
||||
}
|
||||
|
||||
func GetAllUsers() map[string]*User {
|
||||
return UserList
|
||||
}
|
||||
|
||||
func UpdateUser(uid string, uu *User) (a *User, err error) {
|
||||
if u, ok := UserList[uid]; ok {
|
||||
if uu.Username != "" {
|
||||
u.Username = uu.Username
|
||||
}
|
||||
if uu.Password != "" {
|
||||
u.Password = uu.Password
|
||||
}
|
||||
if uu.Profile.Age != 0 {
|
||||
u.Profile.Age = uu.Profile.Age
|
||||
}
|
||||
if uu.Profile.Address != "" {
|
||||
u.Profile.Address = uu.Profile.Address
|
||||
}
|
||||
if uu.Profile.Gender != "" {
|
||||
u.Profile.Gender = uu.Profile.Gender
|
||||
}
|
||||
if uu.Profile.Email != "" {
|
||||
u.Profile.Email = uu.Profile.Email
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
return nil, errors.New("User Not Exist")
|
||||
}
|
||||
|
||||
func Login(username, password string) bool {
|
||||
for _, u := range UserList {
|
||||
if u.Username == username && u.Password == password {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func DeleteUser(uid string) {
|
||||
delete(UserList, uid)
|
||||
}
|
||||
`
|
||||
|
||||
var apiControllers = `package controllers
|
||||
|
||||
import (
|
||||
"{{.Appname}}/models"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
// Operations about object
|
||||
type ObjectController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// @Title Create
|
||||
// @Description create object
|
||||
// @Param body body models.Object true "The object content"
|
||||
// @Success 200 {string} models.Object.Id
|
||||
// @Failure 403 body is empty
|
||||
// @router / [post]
|
||||
func (o *ObjectController) Post() {
|
||||
var ob models.Object
|
||||
json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
|
||||
objectid := models.AddOne(ob)
|
||||
o.Data["json"] = map[string]string{"ObjectId": objectid}
|
||||
o.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title Get
|
||||
// @Description find object by objectid
|
||||
// @Param objectId path string true "the objectid you want to get"
|
||||
// @Success 200 {object} models.Object
|
||||
// @Failure 403 :objectId is empty
|
||||
// @router /:objectId [get]
|
||||
func (o *ObjectController) Get() {
|
||||
objectId := o.Ctx.Input.Param(":objectId")
|
||||
if objectId != "" {
|
||||
ob, err := models.GetOne(objectId)
|
||||
if err != nil {
|
||||
o.Data["json"] = err.Error()
|
||||
} else {
|
||||
o.Data["json"] = ob
|
||||
}
|
||||
}
|
||||
o.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetAll
|
||||
// @Description get all objects
|
||||
// @Success 200 {object} models.Object
|
||||
// @Failure 403 :objectId is empty
|
||||
// @router / [get]
|
||||
func (o *ObjectController) GetAll() {
|
||||
obs := models.GetAll()
|
||||
o.Data["json"] = obs
|
||||
o.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title Update
|
||||
// @Description update the object
|
||||
// @Param objectId path string true "The objectid you want to update"
|
||||
// @Param body body models.Object true "The body"
|
||||
// @Success 200 {object} models.Object
|
||||
// @Failure 403 :objectId is empty
|
||||
// @router /:objectId [put]
|
||||
func (o *ObjectController) Put() {
|
||||
objectId := o.Ctx.Input.Param(":objectId")
|
||||
var ob models.Object
|
||||
json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
|
||||
|
||||
err := models.Update(objectId, ob.Score)
|
||||
if err != nil {
|
||||
o.Data["json"] = err.Error()
|
||||
} else {
|
||||
o.Data["json"] = "update success!"
|
||||
}
|
||||
o.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title Delete
|
||||
// @Description delete the object
|
||||
// @Param objectId path string true "The objectId you want to delete"
|
||||
// @Success 200 {string} delete success!
|
||||
// @Failure 403 objectId is empty
|
||||
// @router /:objectId [delete]
|
||||
func (o *ObjectController) Delete() {
|
||||
objectId := o.Ctx.Input.Param(":objectId")
|
||||
models.Delete(objectId)
|
||||
o.Data["json"] = "delete success!"
|
||||
o.ServeJSON()
|
||||
}
|
||||
|
||||
`
|
||||
var apiControllers2 = `package controllers
|
||||
|
||||
import (
|
||||
"{{.Appname}}/models"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
// Operations about Users
|
||||
type UserController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// @Title CreateUser
|
||||
// @Description create users
|
||||
// @Param body body models.User true "body for user content"
|
||||
// @Success 200 {int} models.User.Id
|
||||
// @Failure 403 body is empty
|
||||
// @router / [post]
|
||||
func (u *UserController) Post() {
|
||||
var user models.User
|
||||
json.Unmarshal(u.Ctx.Input.RequestBody, &user)
|
||||
uid := models.AddUser(user)
|
||||
u.Data["json"] = map[string]string{"uid": uid}
|
||||
u.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetAll
|
||||
// @Description get all Users
|
||||
// @Success 200 {object} models.User
|
||||
// @router / [get]
|
||||
func (u *UserController) GetAll() {
|
||||
users := models.GetAllUsers()
|
||||
u.Data["json"] = users
|
||||
u.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title Get
|
||||
// @Description get user by uid
|
||||
// @Param uid path string true "The key for staticblock"
|
||||
// @Success 200 {object} models.User
|
||||
// @Failure 403 :uid is empty
|
||||
// @router /:uid [get]
|
||||
func (u *UserController) Get() {
|
||||
uid := u.GetString(":uid")
|
||||
if uid != "" {
|
||||
user, err := models.GetUser(uid)
|
||||
if err != nil {
|
||||
u.Data["json"] = err.Error()
|
||||
} else {
|
||||
u.Data["json"] = user
|
||||
}
|
||||
}
|
||||
u.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title Update
|
||||
// @Description update the user
|
||||
// @Param uid path string true "The uid you want to update"
|
||||
// @Param body body models.User true "body for user content"
|
||||
// @Success 200 {object} models.User
|
||||
// @Failure 403 :uid is not int
|
||||
// @router /:uid [put]
|
||||
func (u *UserController) Put() {
|
||||
uid := u.GetString(":uid")
|
||||
if uid != "" {
|
||||
var user models.User
|
||||
json.Unmarshal(u.Ctx.Input.RequestBody, &user)
|
||||
uu, err := models.UpdateUser(uid, &user)
|
||||
if err != nil {
|
||||
u.Data["json"] = err.Error()
|
||||
} else {
|
||||
u.Data["json"] = uu
|
||||
}
|
||||
}
|
||||
u.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title Delete
|
||||
// @Description delete the user
|
||||
// @Param uid path string true "The uid you want to delete"
|
||||
// @Success 200 {string} delete success!
|
||||
// @Failure 403 uid is empty
|
||||
// @router /:uid [delete]
|
||||
func (u *UserController) Delete() {
|
||||
uid := u.GetString(":uid")
|
||||
models.DeleteUser(uid)
|
||||
u.Data["json"] = "delete success!"
|
||||
u.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title Login
|
||||
// @Description Logs user into the system
|
||||
// @Param username query string true "The username for login"
|
||||
// @Param password query string true "The password for login"
|
||||
// @Success 200 {string} login success
|
||||
// @Failure 403 user not exist
|
||||
// @router /login [get]
|
||||
func (u *UserController) Login() {
|
||||
username := u.GetString("username")
|
||||
password := u.GetString("password")
|
||||
if models.Login(username, password) {
|
||||
u.Data["json"] = "login success"
|
||||
} else {
|
||||
u.Data["json"] = "user not exist"
|
||||
}
|
||||
u.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title logout
|
||||
// @Description Logs out current logged in user session
|
||||
// @Success 200 {string} logout success
|
||||
// @router /logout [get]
|
||||
func (u *UserController) Logout() {
|
||||
u.Data["json"] = "logout success"
|
||||
u.ServeJSON()
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
var apiTests = `package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"runtime"
|
||||
"path/filepath"
|
||||
_ "{{.Appname}}/routers"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func init() {
|
||||
_, file, _, _ := runtime.Caller(1)
|
||||
apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".." + string(filepath.Separator))))
|
||||
beego.TestBeegoInit(apppath)
|
||||
}
|
||||
|
||||
// TestGet is a sample to run an endpoint test
|
||||
func TestGet(t *testing.T) {
|
||||
r, _ := http.NewRequest("GET", "/v1/object", nil)
|
||||
w := httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
|
||||
beego.Trace("testing", "TestGet", "Code[%d]\n%s", w.Code, w.Body.String())
|
||||
|
||||
Convey("Subject: Test Station Endpoint\n", t, func() {
|
||||
Convey("Status Code Should Be 200", func() {
|
||||
So(w.Code, ShouldEqual, 200)
|
||||
})
|
||||
Convey("The Result Should Not Be Empty", func() {
|
||||
So(w.Body.Len(), ShouldBeGreaterThan, 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
func init() {
|
||||
CmdApiapp.Flag.Var(&generate.Tables, "tables", "List of table names separated by a comma.")
|
||||
CmdApiapp.Flag.Var(&generate.SQLDriver, "driver", "Database driver. Either mysql, postgres or sqlite.")
|
||||
CmdApiapp.Flag.Var(&generate.SQLConn, "conn", "Connection string used by the driver to connect to a database instance.")
|
||||
commands.AvailableCommands = append(commands.AvailableCommands, CmdApiapp)
|
||||
}
|
||||
|
||||
func createapi(cmd *commands.Command, args []string) int {
|
||||
output := cmd.Out()
|
||||
|
||||
if len(args) < 1 {
|
||||
beeLogger.Log.Fatal("Argument [appname] is missing")
|
||||
}
|
||||
|
||||
if len(args) > 1 {
|
||||
cmd.Flag.Parse(args[1:])
|
||||
}
|
||||
|
||||
apppath, packpath, err := utils.CheckEnv(args[0])
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("%s", err)
|
||||
}
|
||||
if generate.SQLDriver == "" {
|
||||
generate.SQLDriver = "mysql"
|
||||
}
|
||||
|
||||
beeLogger.Log.Info("Creating API...")
|
||||
|
||||
os.MkdirAll(apppath, 0755)
|
||||
fmt.Fprintf(output, "\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(output, "\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(output, "\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(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
|
||||
utils.WriteToFile(path.Join(apppath, "conf", "app.conf"),
|
||||
strings.Replace(apiconf, "{{.Appname}}", path.Base(args[0]), -1))
|
||||
|
||||
if generate.SQLConn != "" {
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
|
||||
maingoContent := strings.Replace(apiMainconngo, "{{.Appname}}", packpath, -1)
|
||||
maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(generate.SQLDriver), -1)
|
||||
if generate.SQLDriver == "mysql" {
|
||||
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/go-sql-driver/mysql"`, -1)
|
||||
} else if generate.SQLDriver == "postgres" {
|
||||
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1)
|
||||
}
|
||||
utils.WriteToFile(path.Join(apppath, "main.go"),
|
||||
strings.Replace(
|
||||
maingoContent,
|
||||
"{{.conn}}",
|
||||
generate.SQLConn.String(),
|
||||
-1,
|
||||
),
|
||||
)
|
||||
beeLogger.Log.Infof("Using '%s' as 'driver'", generate.SQLDriver)
|
||||
beeLogger.Log.Infof("Using '%s' as 'conn'", generate.SQLConn)
|
||||
beeLogger.Log.Infof("Using '%s' as 'tables'", generate.Tables)
|
||||
generate.GenerateAppcode(string(generate.SQLDriver), string(generate.SQLConn), "3", string(generate.Tables), apppath)
|
||||
} else {
|
||||
os.Mkdir(path.Join(apppath, "models"), 0755)
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models"), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "routers"), 0755)
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers")+string(path.Separator), "\x1b[0m")
|
||||
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "object.go"), "\x1b[0m")
|
||||
utils.WriteToFile(path.Join(apppath, "controllers", "object.go"),
|
||||
strings.Replace(apiControllers, "{{.Appname}}", packpath, -1))
|
||||
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "user.go"), "\x1b[0m")
|
||||
utils.WriteToFile(path.Join(apppath, "controllers", "user.go"),
|
||||
strings.Replace(apiControllers2, "{{.Appname}}", packpath, -1))
|
||||
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests", "default_test.go"), "\x1b[0m")
|
||||
utils.WriteToFile(path.Join(apppath, "tests", "default_test.go"),
|
||||
strings.Replace(apiTests, "{{.Appname}}", packpath, -1))
|
||||
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers", "router.go"), "\x1b[0m")
|
||||
utils.WriteToFile(path.Join(apppath, "routers", "router.go"),
|
||||
strings.Replace(apirouter, "{{.Appname}}", packpath, -1))
|
||||
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "object.go"), "\x1b[0m")
|
||||
utils.WriteToFile(path.Join(apppath, "models", "object.go"), ApiModels)
|
||||
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "user.go"), "\x1b[0m")
|
||||
utils.WriteToFile(path.Join(apppath, "models", "user.go"), ApiModels2)
|
||||
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
|
||||
utils.WriteToFile(path.Join(apppath, "main.go"),
|
||||
strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1))
|
||||
}
|
||||
beeLogger.Log.Success("New API successfully created!")
|
||||
return 0
|
||||
}
|
251
cmd/commands/bale/bale.go
Normal file
251
cmd/commands/bale/bale.go
Normal file
@ -0,0 +1,251 @@
|
||||
// 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 bale
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/bee/cmd/commands"
|
||||
"github.com/beego/bee/cmd/commands/version"
|
||||
"github.com/beego/bee/config"
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
"github.com/beego/bee/utils"
|
||||
)
|
||||
|
||||
var CmdBale = &commands.Command{
|
||||
UsageLine: "bale",
|
||||
Short: "Transforms non-Go files to Go source files",
|
||||
Long: `Bale command compress all the static files in to a single binary file.
|
||||
|
||||
This is useful to not have to carry static files including js, css, images and
|
||||
views when deploying a Web application.
|
||||
|
||||
It will auto-generate an unpack function to the main package then run it during the runtime.
|
||||
This is mainly used for zealots who are requiring 100% Go code.
|
||||
`,
|
||||
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
|
||||
Run: runBale,
|
||||
}
|
||||
|
||||
func init() {
|
||||
commands.AvailableCommands = append(commands.AvailableCommands, CmdBale)
|
||||
}
|
||||
|
||||
func runBale(cmd *commands.Command, args []string) int {
|
||||
os.RemoveAll("bale")
|
||||
os.Mkdir("bale", os.ModePerm)
|
||||
|
||||
// Pack and compress data
|
||||
for _, p := range config.Conf.Bale.Dirs {
|
||||
if !utils.IsExist(p) {
|
||||
beeLogger.Log.Warnf("Skipped directory: %s", p)
|
||||
continue
|
||||
}
|
||||
beeLogger.Log.Infof("Packaging directory: %s", p)
|
||||
filepath.Walk(p, walkFn)
|
||||
}
|
||||
|
||||
// Generate auto-uncompress function.
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString(fmt.Sprintf(BaleHeader, config.Conf.Bale.Import,
|
||||
strings.Join(resFiles, "\",\n\t\t\""),
|
||||
strings.Join(resFiles, ",\n\t\tbale.R")))
|
||||
|
||||
fw, err := os.Create("bale.go")
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("Failed to create file: %s", err)
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
_, err = fw.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("Failed to write data: %s", err)
|
||||
}
|
||||
|
||||
beeLogger.Log.Success("Baled resources successfully!")
|
||||
return 0
|
||||
}
|
||||
|
||||
const (
|
||||
// BaleHeader ...
|
||||
BaleHeader = `package main
|
||||
|
||||
import(
|
||||
"os"
|
||||
"strings"
|
||||
"path"
|
||||
|
||||
"%s"
|
||||
)
|
||||
|
||||
func isExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil || os.IsExist(err)
|
||||
}
|
||||
|
||||
func init() {
|
||||
files := []string{
|
||||
"%s",
|
||||
}
|
||||
|
||||
funcs := []func() []byte{
|
||||
bale.R%s,
|
||||
}
|
||||
|
||||
for i, f := range funcs {
|
||||
fp := getFilePath(files[i])
|
||||
if !isExist(fp) {
|
||||
saveFile(fp, f())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getFilePath(name string) string {
|
||||
name = strings.Replace(name, "_4_", "/", -1)
|
||||
name = strings.Replace(name, "_3_", " ", -1)
|
||||
name = strings.Replace(name, "_2_", "-", -1)
|
||||
name = strings.Replace(name, "_1_", ".", -1)
|
||||
name = strings.Replace(name, "_0_", "_", -1)
|
||||
return name
|
||||
}
|
||||
|
||||
func saveFile(filePath string, b []byte) (int, error) {
|
||||
os.MkdirAll(path.Dir(filePath), os.ModePerm)
|
||||
fw, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer fw.Close()
|
||||
return fw.Write(b)
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
var resFiles = make([]string, 0, 10)
|
||||
|
||||
func walkFn(resPath string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() || filterSuffix(resPath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Open resource files
|
||||
fr, err := os.Open(resPath)
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("Failed to read file: %s", err)
|
||||
}
|
||||
|
||||
// Convert path
|
||||
resPath = strings.Replace(resPath, "_", "_0_", -1)
|
||||
resPath = strings.Replace(resPath, ".", "_1_", -1)
|
||||
resPath = strings.Replace(resPath, "-", "_2_", -1)
|
||||
resPath = strings.Replace(resPath, " ", "_3_", -1)
|
||||
sep := "/"
|
||||
if runtime.GOOS == "windows" {
|
||||
sep = "\\"
|
||||
}
|
||||
resPath = strings.Replace(resPath, sep, "_4_", -1)
|
||||
|
||||
// Create corresponding Go source files
|
||||
os.MkdirAll(path.Dir(resPath), os.ModePerm)
|
||||
fw, err := os.Create("bale/" + resPath + ".go")
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("Failed to create file: %s", err)
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
// Write header
|
||||
fmt.Fprintf(fw, Header, resPath)
|
||||
|
||||
// Copy and compress data
|
||||
gz := gzip.NewWriter(&ByteWriter{Writer: fw})
|
||||
io.Copy(gz, fr)
|
||||
gz.Close()
|
||||
|
||||
// Write footer.
|
||||
fmt.Fprint(fw, Footer)
|
||||
|
||||
resFiles = append(resFiles, resPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func filterSuffix(name string) bool {
|
||||
for _, s := range config.Conf.Bale.IngExt {
|
||||
if strings.HasSuffix(name, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
// Header ...
|
||||
Header = `package bale
|
||||
|
||||
import(
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
)
|
||||
|
||||
func R%s() []byte {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer([]byte{`
|
||||
// Footer ...
|
||||
Footer = `
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
panic("Unpack resources failed: " + err.Error())
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
io.Copy(&b, gz)
|
||||
gz.Close()
|
||||
|
||||
return b.Bytes()
|
||||
}`
|
||||
)
|
||||
|
||||
var newline = []byte{'\n'}
|
||||
|
||||
// ByteWriter ...
|
||||
type ByteWriter struct {
|
||||
io.Writer
|
||||
c int
|
||||
}
|
||||
|
||||
func (w *ByteWriter) Write(p []byte) (n int, err error) {
|
||||
if len(p) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for n = range p {
|
||||
if w.c%12 == 0 {
|
||||
w.Writer.Write(newline)
|
||||
w.c = 0
|
||||
}
|
||||
fmt.Fprintf(w.Writer, "0x%02x,", p[n])
|
||||
w.c++
|
||||
}
|
||||
n++
|
||||
return
|
||||
}
|
232
cmd/commands/beefix/fix.go
Normal file
232
cmd/commands/beefix/fix.go
Normal file
@ -0,0 +1,232 @@
|
||||
package beefix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/bee/cmd/commands"
|
||||
"github.com/beego/bee/cmd/commands/version"
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
"github.com/beego/bee/logger/colors"
|
||||
)
|
||||
|
||||
var CmdFix = &commands.Command{
|
||||
UsageLine: "fix",
|
||||
Short: "Fixes your application by making it compatible with newer versions of Beego",
|
||||
Long: `As of {{"Beego 1.6"|bold}}, there are some backward compatibility issues.
|
||||
|
||||
The command 'fix' will try to solve those issues by upgrading your code base
|
||||
to be compatible with Beego version 1.6+.
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
CmdFix.Run = runFix
|
||||
CmdFix.PreRun = func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() }
|
||||
commands.AvailableCommands = append(commands.AvailableCommands, CmdFix)
|
||||
}
|
||||
|
||||
func runFix(cmd *commands.Command, args []string) int {
|
||||
output := cmd.Out()
|
||||
|
||||
beeLogger.Log.Info("Upgrading the application...")
|
||||
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("Error while getting the current working directory: %s", err)
|
||||
}
|
||||
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
if strings.HasPrefix(info.Name(), ".") {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasSuffix(info.Name(), ".exe") {
|
||||
return nil
|
||||
}
|
||||
err = fixFile(path)
|
||||
fmt.Fprintf(output, colors.GreenBold("\tfix\t")+"%s\n", path)
|
||||
if err != nil {
|
||||
beeLogger.Log.Errorf("Could not fix file: %s", err)
|
||||
}
|
||||
return err
|
||||
})
|
||||
beeLogger.Log.Success("Upgrade Done!")
|
||||
return 0
|
||||
}
|
||||
|
||||
var rules = []string{
|
||||
"beego.AppName", "beego.BConfig.AppName",
|
||||
"beego.RunMode", "beego.BConfig.RunMode",
|
||||
"beego.RecoverPanic", "beego.BConfig.RecoverPanic",
|
||||
"beego.RouterCaseSensitive", "beego.BConfig.RouterCaseSensitive",
|
||||
"beego.BeegoServerName", "beego.BConfig.ServerName",
|
||||
"beego.EnableGzip", "beego.BConfig.EnableGzip",
|
||||
"beego.ErrorsShow", "beego.BConfig.EnableErrorsShow",
|
||||
"beego.CopyRequestBody", "beego.BConfig.CopyRequestBody",
|
||||
"beego.MaxMemory", "beego.BConfig.MaxMemory",
|
||||
"beego.Graceful", "beego.BConfig.Listen.Graceful",
|
||||
"beego.HttpAddr", "beego.BConfig.Listen.HTTPAddr",
|
||||
"beego.HttpPort", "beego.BConfig.Listen.HTTPPort",
|
||||
"beego.ListenTCP4", "beego.BConfig.Listen.ListenTCP4",
|
||||
"beego.EnableHttpListen", "beego.BConfig.Listen.EnableHTTP",
|
||||
"beego.EnableHttpTLS", "beego.BConfig.Listen.EnableHTTPS",
|
||||
"beego.HttpsAddr", "beego.BConfig.Listen.HTTPSAddr",
|
||||
"beego.HttpsPort", "beego.BConfig.Listen.HTTPSPort",
|
||||
"beego.HttpCertFile", "beego.BConfig.Listen.HTTPSCertFile",
|
||||
"beego.HttpKeyFile", "beego.BConfig.Listen.HTTPSKeyFile",
|
||||
"beego.EnableAdmin", "beego.BConfig.Listen.EnableAdmin",
|
||||
"beego.AdminHttpAddr", "beego.BConfig.Listen.AdminAddr",
|
||||
"beego.AdminHttpPort", "beego.BConfig.Listen.AdminPort",
|
||||
"beego.UseFcgi", "beego.BConfig.Listen.EnableFcgi",
|
||||
"beego.HttpServerTimeOut", "beego.BConfig.Listen.ServerTimeOut",
|
||||
"beego.AutoRender", "beego.BConfig.WebConfig.AutoRender",
|
||||
"beego.ViewsPath", "beego.BConfig.WebConfig.ViewsPath",
|
||||
"beego.StaticDir", "beego.BConfig.WebConfig.StaticDir",
|
||||
"beego.StaticExtensionsToGzip", "beego.BConfig.WebConfig.StaticExtensionsToGzip",
|
||||
"beego.DirectoryIndex", "beego.BConfig.WebConfig.DirectoryIndex",
|
||||
"beego.FlashName", "beego.BConfig.WebConfig.FlashName",
|
||||
"beego.FlashSeperator", "beego.BConfig.WebConfig.FlashSeparator",
|
||||
"beego.EnableDocs", "beego.BConfig.WebConfig.EnableDocs",
|
||||
"beego.XSRFKEY", "beego.BConfig.WebConfig.XSRFKey",
|
||||
"beego.EnableXSRF", "beego.BConfig.WebConfig.EnableXSRF",
|
||||
"beego.XSRFExpire", "beego.BConfig.WebConfig.XSRFExpire",
|
||||
"beego.TemplateLeft", "beego.BConfig.WebConfig.TemplateLeft",
|
||||
"beego.TemplateRight", "beego.BConfig.WebConfig.TemplateRight",
|
||||
"beego.SessionOn", "beego.BConfig.WebConfig.Session.SessionOn",
|
||||
"beego.SessionProvider", "beego.BConfig.WebConfig.Session.SessionProvider",
|
||||
"beego.SessionName", "beego.BConfig.WebConfig.Session.SessionName",
|
||||
"beego.SessionGCMaxLifetime", "beego.BConfig.WebConfig.Session.SessionGCMaxLifetime",
|
||||
"beego.SessionSavePath", "beego.BConfig.WebConfig.Session.SessionProviderConfig",
|
||||
"beego.SessionCookieLifeTime", "beego.BConfig.WebConfig.Session.SessionCookieLifeTime",
|
||||
"beego.SessionAutoSetCookie", "beego.BConfig.WebConfig.Session.SessionAutoSetCookie",
|
||||
"beego.SessionDomain", "beego.BConfig.WebConfig.Session.SessionDomain",
|
||||
"Ctx.Input.CopyBody(", "Ctx.Input.CopyBody(beego.BConfig.MaxMemory",
|
||||
".UrlFor(", ".URLFor(",
|
||||
".ServeJson(", ".ServeJSON(",
|
||||
".ServeXml(", ".ServeXML(",
|
||||
".ServeJsonp(", ".ServeJSONP(",
|
||||
".XsrfToken(", ".XSRFToken(",
|
||||
".CheckXsrfCookie(", ".CheckXSRFCookie(",
|
||||
".XsrfFormHtml(", ".XSRFFormHTML(",
|
||||
"beego.UrlFor(", "beego.URLFor(",
|
||||
"beego.GlobalDocApi", "beego.GlobalDocAPI",
|
||||
"beego.Errorhandler", "beego.ErrorHandler",
|
||||
"Output.Jsonp(", "Output.JSONP(",
|
||||
"Output.Json(", "Output.JSON(",
|
||||
"Output.Xml(", "Output.XML(",
|
||||
"Input.Uri()", "Input.URI()",
|
||||
"Input.Url()", "Input.URL()",
|
||||
"Input.AcceptsHtml()", "Input.AcceptsHTML()",
|
||||
"Input.AcceptsXml()", "Input.AcceptsXML()",
|
||||
"Input.AcceptsJson()", "Input.AcceptsJSON()",
|
||||
"Ctx.XsrfToken()", "Ctx.XSRFToken()",
|
||||
"Ctx.CheckXsrfCookie()", "Ctx.CheckXSRFCookie()",
|
||||
"session.SessionStore", "session.Store",
|
||||
".TplNames", ".TplName",
|
||||
"swagger.ApiRef", "swagger.APIRef",
|
||||
"swagger.ApiDeclaration", "swagger.APIDeclaration",
|
||||
"swagger.Api", "swagger.API",
|
||||
"swagger.ApiRef", "swagger.APIRef",
|
||||
"swagger.Infomation", "swagger.Information",
|
||||
"toolbox.UrlMap", "toolbox.URLMap",
|
||||
"logs.LoggerInterface", "logs.Logger",
|
||||
"Input.Request", "Input.Context.Request",
|
||||
"Input.Params)", "Input.Params())",
|
||||
"httplib.BeegoHttpSettings", "httplib.BeegoHTTPSettings",
|
||||
"httplib.BeegoHttpRequest", "httplib.BeegoHTTPRequest",
|
||||
".TlsClientConfig", ".TLSClientConfig",
|
||||
".JsonBody", ".JSONBody",
|
||||
".ToJson", ".ToJSON",
|
||||
".ToXml", ".ToXML",
|
||||
"beego.Html2str", "beego.HTML2str",
|
||||
"beego.AssetsCss", "beego.AssetsCSS",
|
||||
"orm.DR_Sqlite", "orm.DRSqlite",
|
||||
"orm.DR_Postgres", "orm.DRPostgres",
|
||||
"orm.DR_MySQL", "orm.DRMySQL",
|
||||
"orm.DR_Oracle", "orm.DROracle",
|
||||
"orm.Col_Add", "orm.ColAdd",
|
||||
"orm.Col_Minus", "orm.ColMinus",
|
||||
"orm.Col_Multiply", "orm.ColMultiply",
|
||||
"orm.Col_Except", "orm.ColExcept",
|
||||
"GenerateOperatorSql", "GenerateOperatorSQL",
|
||||
"OperatorSql", "OperatorSQL",
|
||||
"orm.Debug_Queries", "orm.DebugQueries",
|
||||
"orm.COMMA_SPACE", "orm.CommaSpace",
|
||||
".SendOut()", ".DoRequest()",
|
||||
"validation.ValidationError", "validation.Error",
|
||||
}
|
||||
|
||||
func fixFile(file string) error {
|
||||
rp := strings.NewReplacer(rules...)
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fixed := rp.Replace(string(content))
|
||||
|
||||
// Forword the RequestBody from the replace
|
||||
// "Input.Request", "Input.Context.Request",
|
||||
fixed = strings.Replace(fixed, "Input.Context.RequestBody", "Input.RequestBody", -1)
|
||||
|
||||
// Regexp replace
|
||||
pareg := regexp.MustCompile(`(Input.Params\[")(.*)("])`)
|
||||
fixed = pareg.ReplaceAllString(fixed, "Input.Param(\"$2\")")
|
||||
pareg = regexp.MustCompile(`(Input.Data\[\")(.*)(\"\])(\s)(=)(\s)(.*)`)
|
||||
fixed = pareg.ReplaceAllString(fixed, "Input.SetData(\"$2\", $7)")
|
||||
pareg = regexp.MustCompile(`(Input.Data\[\")(.*)(\"\])`)
|
||||
fixed = pareg.ReplaceAllString(fixed, "Input.Data(\"$2\")")
|
||||
// Fix the cache object Put method
|
||||
pareg = regexp.MustCompile(`(\.Put\(\")(.*)(\",)(\s)(.*)(,\s*)([^\*.]*)(\))`)
|
||||
if pareg.MatchString(fixed) && strings.HasSuffix(file, ".go") {
|
||||
fixed = pareg.ReplaceAllString(fixed, ".Put(\"$2\", $5, $7*time.Second)")
|
||||
fset := token.NewFileSet() // positions are relative to fset
|
||||
f, err := parser.ParseFile(fset, file, nil, parser.ImportsOnly)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Print the imports from the file's AST.
|
||||
hasTimepkg := false
|
||||
for _, s := range f.Imports {
|
||||
if s.Path.Value == `"time"` {
|
||||
hasTimepkg = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasTimepkg {
|
||||
fixed = strings.Replace(fixed, "import (", "import (\n\t\"time\"", 1)
|
||||
}
|
||||
}
|
||||
// Replace the v.Apis in docs.go
|
||||
if strings.Contains(file, "docs.go") {
|
||||
fixed = strings.Replace(fixed, "v.Apis", "v.APIs", -1)
|
||||
}
|
||||
// Replace the config file
|
||||
if strings.HasSuffix(file, ".conf") {
|
||||
fixed = strings.Replace(fixed, "HttpCertFile", "HTTPSCertFile", -1)
|
||||
fixed = strings.Replace(fixed, "HttpKeyFile", "HTTPSKeyFile", -1)
|
||||
fixed = strings.Replace(fixed, "EnableHttpListen", "HTTPEnable", -1)
|
||||
fixed = strings.Replace(fixed, "EnableHttpTLS", "EnableHTTPS", -1)
|
||||
fixed = strings.Replace(fixed, "EnableHttpTLS", "EnableHTTPS", -1)
|
||||
fixed = strings.Replace(fixed, "BeegoServerName", "ServerName", -1)
|
||||
fixed = strings.Replace(fixed, "AdminHttpAddr", "AdminAddr", -1)
|
||||
fixed = strings.Replace(fixed, "AdminHttpPort", "AdminPort", -1)
|
||||
fixed = strings.Replace(fixed, "HttpServerTimeOut", "ServerTimeOut", -1)
|
||||
}
|
||||
err = os.Truncate(file, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(file, []byte(fixed), 0666)
|
||||
}
|
94
cmd/commands/command.go
Normal file
94
cmd/commands/command.go
Normal file
@ -0,0 +1,94 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/bee/logger/colors"
|
||||
"github.com/beego/bee/utils"
|
||||
)
|
||||
|
||||
// Command is the unit of execution
|
||||
type Command struct {
|
||||
// Run runs the command.
|
||||
// The args are the arguments after the command name.
|
||||
Run func(cmd *Command, args []string) int
|
||||
|
||||
// PreRun performs an operation before running the command
|
||||
PreRun func(cmd *Command, args []string)
|
||||
|
||||
// UsageLine is the one-line Usage message.
|
||||
// The first word in the line is taken to be the command name.
|
||||
UsageLine string
|
||||
|
||||
// Short is the short description shown in the 'go help' output.
|
||||
Short string
|
||||
|
||||
// Long is the long message shown in the 'go help <this-command>' output.
|
||||
Long string
|
||||
|
||||
// Flag is a set of flags specific to this command.
|
||||
Flag flag.FlagSet
|
||||
|
||||
// CustomFlags indicates that the command will do its own
|
||||
// flag parsing.
|
||||
CustomFlags bool
|
||||
|
||||
// output out writer if set in SetOutput(w)
|
||||
output *io.Writer
|
||||
}
|
||||
|
||||
var AvailableCommands = []*Command{}
|
||||
var cmdUsage = `Use {{printf "bee help %s" .Name | bold}} for more information.{{endline}}`
|
||||
|
||||
// Name returns the command's name: the first word in the Usage line.
|
||||
func (c *Command) Name() string {
|
||||
name := c.UsageLine
|
||||
i := strings.Index(name, " ")
|
||||
if i >= 0 {
|
||||
name = name[:i]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// SetOutput sets the destination for Usage and error messages.
|
||||
// If output is nil, os.Stderr is used.
|
||||
func (c *Command) SetOutput(output io.Writer) {
|
||||
c.output = &output
|
||||
}
|
||||
|
||||
// Out returns the out writer of the current command.
|
||||
// If cmd.output is nil, os.Stderr is used.
|
||||
func (c *Command) Out() io.Writer {
|
||||
if c.output != nil {
|
||||
return *c.output
|
||||
}
|
||||
return colors.NewColorWriter(os.Stderr)
|
||||
}
|
||||
|
||||
// Usage puts out the Usage for the command.
|
||||
func (c *Command) Usage() {
|
||||
utils.Tmpl(cmdUsage, c)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Runnable reports whether the command can be run; otherwise
|
||||
// it is a documentation pseudo-command such as import path.
|
||||
func (c *Command) Runnable() bool {
|
||||
return c.Run != nil
|
||||
}
|
||||
|
||||
func (c *Command) Options() map[string]string {
|
||||
options := make(map[string]string)
|
||||
c.Flag.VisitAll(func(f *flag.Flag) {
|
||||
defaultVal := f.DefValue
|
||||
if len(defaultVal) > 0 {
|
||||
options[f.Name+"="+defaultVal] = f.Usage
|
||||
} else {
|
||||
options[f.Name] = f.Usage
|
||||
}
|
||||
})
|
||||
return options
|
||||
}
|
134
cmd/commands/dockerize/dockerize.go
Normal file
134
cmd/commands/dockerize/dockerize.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright 2016 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 dockerize
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/beego/bee/cmd/commands"
|
||||
"github.com/beego/bee/cmd/commands/version"
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
"github.com/beego/bee/utils"
|
||||
)
|
||||
|
||||
const dockerBuildTemplate = `FROM {{.BaseImage}}
|
||||
|
||||
# Godep for vendoring
|
||||
RUN go get github.com/tools/godep
|
||||
|
||||
# Recompile the standard library without CGO
|
||||
RUN CGO_ENABLED=0 go install -a std
|
||||
|
||||
ENV APP_DIR $GOPATH{{.Appdir}}
|
||||
RUN mkdir -p $APP_DIR
|
||||
|
||||
# Set the entrypoint
|
||||
ENTRYPOINT $APP_DIR/{{.Entrypoint}}
|
||||
ADD . $APP_DIR
|
||||
|
||||
# Compile the binary and statically link
|
||||
RUN cd $APP_DIR
|
||||
RUN CGO_ENABLED=0 godep go build -ldflags '-d -w -s'
|
||||
|
||||
EXPOSE {{.Expose}}
|
||||
`
|
||||
|
||||
// Dockerfile holds the information about the Docker container.
|
||||
type Dockerfile struct {
|
||||
BaseImage string
|
||||
Appdir string
|
||||
Entrypoint string
|
||||
Expose string
|
||||
}
|
||||
|
||||
var CmdDockerize = &commands.Command{
|
||||
CustomFlags: true,
|
||||
UsageLine: "dockerize",
|
||||
Short: "Generates a Dockerfile for your Beego application",
|
||||
Long: `Dockerize generates a Dockerfile for your Beego Web Application.
|
||||
The Dockerfile will compile, get the dependencies with {{"godep"|bold}}, and set the entrypoint.
|
||||
|
||||
{{"Example:"|bold}}
|
||||
$ bee dockerize -expose="3000,80,25"
|
||||
`,
|
||||
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
|
||||
Run: dockerizeApp,
|
||||
}
|
||||
|
||||
var (
|
||||
expose string
|
||||
baseImage string
|
||||
)
|
||||
|
||||
func init() {
|
||||
fs := flag.NewFlagSet("dockerize", flag.ContinueOnError)
|
||||
fs.StringVar(&baseImage, "image", "library/golang", "Set the base image of the Docker container.")
|
||||
fs.StringVar(&expose, "expose", "8080", "Port(s) to expose in the Docker container.")
|
||||
CmdDockerize.Flag = *fs
|
||||
commands.AvailableCommands = append(commands.AvailableCommands, CmdDockerize)
|
||||
}
|
||||
|
||||
func dockerizeApp(cmd *commands.Command, args []string) int {
|
||||
if err := cmd.Flag.Parse(args); err != nil {
|
||||
beeLogger.Log.Fatalf("Error parsing flags: %v", err.Error())
|
||||
}
|
||||
|
||||
beeLogger.Log.Info("Generating Dockerfile...")
|
||||
|
||||
gopath := os.Getenv("GOPATH")
|
||||
dir, err := filepath.Abs(".")
|
||||
if err != nil {
|
||||
beeLogger.Log.Error(err.Error())
|
||||
}
|
||||
|
||||
appdir := strings.Replace(dir, gopath, "", 1)
|
||||
|
||||
// In case of multiple ports to expose inside the container,
|
||||
// replace all the commas with whitespaces.
|
||||
// See the verb EXPOSE in the Docker documentation.
|
||||
if strings.Contains(expose, ",") {
|
||||
expose = strings.Replace(expose, ",", " ", -1)
|
||||
}
|
||||
|
||||
_, entrypoint := path.Split(appdir)
|
||||
dockerfile := Dockerfile{
|
||||
BaseImage: baseImage,
|
||||
Appdir: appdir,
|
||||
Entrypoint: entrypoint,
|
||||
Expose: expose,
|
||||
}
|
||||
|
||||
generateDockerfile(dockerfile)
|
||||
return 0
|
||||
}
|
||||
|
||||
func generateDockerfile(df Dockerfile) {
|
||||
t := template.Must(template.New("dockerBuildTemplate").Parse(dockerBuildTemplate)).Funcs(utils.BeeFuncMap())
|
||||
|
||||
f, err := os.Create("Dockerfile")
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("Error writing Dockerfile: %v", err.Error())
|
||||
}
|
||||
defer utils.CloseFile(f)
|
||||
|
||||
t.Execute(f, df)
|
||||
|
||||
beeLogger.Log.Success("Dockerfile generated.")
|
||||
}
|
203
cmd/commands/generate/generate.go
Normal file
203
cmd/commands/generate/generate.go
Normal file
@ -0,0 +1,203 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/bee/cmd/commands"
|
||||
"github.com/beego/bee/cmd/commands/version"
|
||||
"github.com/beego/bee/config"
|
||||
"github.com/beego/bee/generate"
|
||||
"github.com/beego/bee/generate/swaggergen"
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
"github.com/beego/bee/utils"
|
||||
)
|
||||
|
||||
var CmdGenerate = &commands.Command{
|
||||
UsageLine: "generate [command]",
|
||||
Short: "Source code generator",
|
||||
Long: `▶ {{"To scaffold out your entire application:"|bold}}
|
||||
|
||||
$ bee generate scaffold [scaffoldname] [-fields="title:string,body:text"] [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
|
||||
▶ {{"To generate a Model based on fields:"|bold}}
|
||||
|
||||
$ bee generate model [modelname] [-fields="name:type"]
|
||||
|
||||
▶ {{"To generate a controller:"|bold}}
|
||||
|
||||
$ bee generate controller [controllerfile]
|
||||
|
||||
▶ {{"To generate a CRUD view:"|bold}}
|
||||
|
||||
$ bee generate view [viewpath]
|
||||
|
||||
▶ {{"To generate a migration file for making database schema updates:"|bold}}
|
||||
|
||||
$ bee generate migration [migrationfile] [-fields="name:type"]
|
||||
|
||||
▶ {{"To generate swagger doc file:"|bold}}
|
||||
|
||||
$ bee generate docs
|
||||
|
||||
▶ {{"To generate a test case:"|bold}}
|
||||
|
||||
$ bee generate test [routerfile]
|
||||
|
||||
▶ {{"To generate appcode based on an existing database:"|bold}}
|
||||
|
||||
$ bee generate appcode [-tables=""] [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"] [-level=3]
|
||||
`,
|
||||
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
|
||||
Run: GenerateCode,
|
||||
}
|
||||
|
||||
func init() {
|
||||
CmdGenerate.Flag.Var(&generate.Tables, "Tables", "List of table names separated by a comma.")
|
||||
CmdGenerate.Flag.Var(&generate.SQLDriver, "SQLDriver", "Database SQLDriver. Either mysql, postgres or sqlite.")
|
||||
CmdGenerate.Flag.Var(&generate.SQLConn, "SQLConn", "Connection string used by the SQLDriver to connect to a database instance.")
|
||||
CmdGenerate.Flag.Var(&generate.Level, "Level", "Either 1, 2 or 3. i.e. 1=models; 2=models and controllers; 3=models, controllers and routers.")
|
||||
CmdGenerate.Flag.Var(&generate.Fields, "Fields", "List of table Fields.")
|
||||
commands.AvailableCommands = append(commands.AvailableCommands, CmdGenerate)
|
||||
}
|
||||
|
||||
func GenerateCode(cmd *commands.Command, args []string) int {
|
||||
currpath, _ := os.Getwd()
|
||||
if len(args) < 1 {
|
||||
beeLogger.Log.Fatal("Command is missing")
|
||||
}
|
||||
|
||||
gps := utils.GetGOPATHs()
|
||||
if len(gps) == 0 {
|
||||
beeLogger.Log.Fatal("GOPATH environment variable is not set or empty")
|
||||
}
|
||||
|
||||
gopath := gps[0]
|
||||
|
||||
beeLogger.Log.Debugf("GOPATH: %s", utils.FILE(), utils.LINE(), gopath)
|
||||
|
||||
gcmd := args[0]
|
||||
switch gcmd {
|
||||
case "scaffold":
|
||||
scaffold(cmd, args, currpath)
|
||||
case "docs":
|
||||
swaggergen.GenerateDocs(currpath)
|
||||
case "appcode":
|
||||
appCode(cmd, args, currpath)
|
||||
case "migration":
|
||||
migration(cmd, args, currpath)
|
||||
case "controller":
|
||||
controller(args, currpath)
|
||||
case "model":
|
||||
model(cmd, args, currpath)
|
||||
case "view":
|
||||
view(args, currpath)
|
||||
default:
|
||||
beeLogger.Log.Fatal("Command is missing")
|
||||
}
|
||||
beeLogger.Log.Successf("%s successfully generated!", strings.Title(gcmd))
|
||||
return 0
|
||||
}
|
||||
|
||||
func scaffold(cmd *commands.Command, args []string, currpath string) {
|
||||
if len(args) < 2 {
|
||||
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
|
||||
}
|
||||
|
||||
cmd.Flag.Parse(args[2:])
|
||||
if generate.SQLDriver == "" {
|
||||
generate.SQLDriver = utils.DocValue(config.Conf.Database.Driver)
|
||||
if generate.SQLDriver == "" {
|
||||
generate.SQLDriver = "mysql"
|
||||
}
|
||||
}
|
||||
if generate.SQLConn == "" {
|
||||
generate.SQLConn = utils.DocValue(config.Conf.Database.Conn)
|
||||
if generate.SQLConn == "" {
|
||||
generate.SQLConn = "root:@tcp(127.0.0.1:3306)/test"
|
||||
}
|
||||
}
|
||||
if generate.Fields == "" {
|
||||
beeLogger.Log.Hint("Fields option should not be empty, i.e. -Fields=\"title:string,body:text\"")
|
||||
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
|
||||
}
|
||||
sname := args[1]
|
||||
generate.GenerateScaffold(sname, generate.Fields.String(), currpath, generate.SQLDriver.String(), generate.SQLConn.String())
|
||||
}
|
||||
|
||||
func appCode(cmd *commands.Command, args []string, currpath string) {
|
||||
cmd.Flag.Parse(args[1:])
|
||||
if generate.SQLDriver == "" {
|
||||
generate.SQLDriver = utils.DocValue(config.Conf.Database.Driver)
|
||||
if generate.SQLDriver == "" {
|
||||
generate.SQLDriver = "mysql"
|
||||
}
|
||||
}
|
||||
if generate.SQLConn == "" {
|
||||
generate.SQLConn = utils.DocValue(config.Conf.Database.Conn)
|
||||
if generate.SQLConn == "" {
|
||||
if generate.SQLDriver == "mysql" {
|
||||
generate.SQLConn = "root:@tcp(127.0.0.1:3306)/test"
|
||||
} else if generate.SQLDriver == "postgres" {
|
||||
generate.SQLConn = "postgres://postgres:postgres@127.0.0.1:5432/postgres"
|
||||
}
|
||||
}
|
||||
}
|
||||
if generate.Level == "" {
|
||||
generate.Level = "3"
|
||||
}
|
||||
beeLogger.Log.Infof("Using '%s' as 'SQLDriver'", generate.SQLDriver)
|
||||
beeLogger.Log.Infof("Using '%s' as 'SQLConn'", generate.SQLConn)
|
||||
beeLogger.Log.Infof("Using '%s' as 'Tables'", generate.Tables)
|
||||
beeLogger.Log.Infof("Using '%s' as 'Level'", generate.Level)
|
||||
generate.GenerateAppcode(generate.SQLDriver.String(), generate.SQLConn.String(), generate.Level.String(), generate.Tables.String(), currpath)
|
||||
}
|
||||
func migration(cmd *commands.Command, args []string, currpath string) {
|
||||
if len(args) < 2 {
|
||||
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
|
||||
}
|
||||
cmd.Flag.Parse(args[2:])
|
||||
mname := args[1]
|
||||
|
||||
beeLogger.Log.Infof("Using '%s' as migration name", mname)
|
||||
|
||||
upsql := ""
|
||||
downsql := ""
|
||||
if generate.Fields != "" {
|
||||
dbMigrator := generate.NewDBDriver()
|
||||
upsql = dbMigrator.GenerateCreateUp(mname)
|
||||
downsql = dbMigrator.GenerateCreateDown(mname)
|
||||
}
|
||||
generate.GenerateMigration(mname, upsql, downsql, currpath)
|
||||
}
|
||||
|
||||
func controller(args []string, currpath string) {
|
||||
if len(args) == 2 {
|
||||
cname := args[1]
|
||||
generate.GenerateController(cname, currpath)
|
||||
} else {
|
||||
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
|
||||
}
|
||||
}
|
||||
|
||||
func model(cmd *commands.Command, args []string, currpath string) {
|
||||
if len(args) < 2 {
|
||||
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
|
||||
}
|
||||
cmd.Flag.Parse(args[2:])
|
||||
if generate.Fields == "" {
|
||||
beeLogger.Log.Hint("Fields option should not be empty, i.e. -Fields=\"title:string,body:text\"")
|
||||
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
|
||||
}
|
||||
sname := args[1]
|
||||
generate.GenerateModel(sname, generate.Fields.String(), currpath)
|
||||
}
|
||||
|
||||
func view(args []string, currpath string) {
|
||||
if len(args) == 2 {
|
||||
cname := args[1]
|
||||
generate.GenerateView(cname, currpath)
|
||||
} else {
|
||||
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
|
||||
}
|
||||
}
|
119
cmd/commands/hprose/hprose.go
Normal file
119
cmd/commands/hprose/hprose.go
Normal file
@ -0,0 +1,119 @@
|
||||
package hprose
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/bee/cmd/commands"
|
||||
"github.com/beego/bee/cmd/commands/api"
|
||||
"github.com/beego/bee/cmd/commands/version"
|
||||
"github.com/beego/bee/generate"
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
"github.com/beego/bee/utils"
|
||||
)
|
||||
|
||||
var CmdHproseapp = &commands.Command{
|
||||
// CustomFlags: true,
|
||||
UsageLine: "hprose [appname]",
|
||||
Short: "Creates an RPC application based on Hprose and Beego frameworks",
|
||||
Long: `
|
||||
The command 'hprose' creates an RPC application based on both Beego and Hprose (http://hprose.com/).
|
||||
|
||||
{{"To scaffold out your application, use:"|bold}}
|
||||
|
||||
$ bee hprose [appname] [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test]
|
||||
|
||||
If 'conn' is empty, the command will generate a sample application. Otherwise the command
|
||||
will connect to your database and generate models based on the existing tables.
|
||||
|
||||
The command 'hprose' creates a folder named [appname] with the following structure:
|
||||
|
||||
├── main.go
|
||||
├── {{"conf"|foldername}}
|
||||
│ └── app.conf
|
||||
└── {{"models"|foldername}}
|
||||
└── object.go
|
||||
└── user.go
|
||||
`,
|
||||
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
|
||||
Run: createhprose,
|
||||
}
|
||||
|
||||
func init() {
|
||||
CmdHproseapp.Flag.Var(&generate.Tables, "tables", "List of table names separated by a comma.")
|
||||
CmdHproseapp.Flag.Var(&generate.SQLDriver, "driver", "Database driver. Either mysql, postgres or sqlite.")
|
||||
CmdHproseapp.Flag.Var(&generate.SQLConn, "conn", "Connection string used by the driver to connect to a database instance.")
|
||||
commands.AvailableCommands = append(commands.AvailableCommands, CmdHproseapp)
|
||||
}
|
||||
|
||||
func createhprose(cmd *commands.Command, args []string) int {
|
||||
output := cmd.Out()
|
||||
|
||||
if len(args) != 1 {
|
||||
beeLogger.Log.Fatal("Argument [appname] is missing")
|
||||
}
|
||||
|
||||
curpath, _ := os.Getwd()
|
||||
if len(args) > 1 {
|
||||
cmd.Flag.Parse(args[1:])
|
||||
}
|
||||
apppath, packpath, err := utils.CheckEnv(args[0])
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("%s", err)
|
||||
}
|
||||
if generate.SQLDriver == "" {
|
||||
generate.SQLDriver = "mysql"
|
||||
}
|
||||
beeLogger.Log.Info("Creating Hprose application...")
|
||||
|
||||
os.MkdirAll(apppath, 0755)
|
||||
fmt.Fprintf(output, "\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(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
|
||||
utils.WriteToFile(path.Join(apppath, "conf", "app.conf"),
|
||||
strings.Replace(generate.Hproseconf, "{{.Appname}}", args[0], -1))
|
||||
|
||||
if generate.SQLConn != "" {
|
||||
beeLogger.Log.Infof("Using '%s' as 'driver'", generate.SQLDriver)
|
||||
beeLogger.Log.Infof("Using '%s' as 'conn'", generate.SQLConn)
|
||||
beeLogger.Log.Infof("Using '%s' as 'tables'", generate.Tables)
|
||||
generate.GenerateHproseAppcode(string(generate.SQLDriver), string(generate.SQLConn), "1", string(generate.Tables), path.Join(curpath, args[0]))
|
||||
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
|
||||
maingoContent := strings.Replace(generate.HproseMainconngo, "{{.Appname}}", packpath, -1)
|
||||
maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(generate.SQLDriver), -1)
|
||||
maingoContent = strings.Replace(maingoContent, "{{HproseFunctionList}}", strings.Join(generate.HproseAddFunctions, ""), -1)
|
||||
if generate.SQLDriver == "mysql" {
|
||||
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/go-sql-driver/mysql"`, -1)
|
||||
} else if generate.SQLDriver == "postgres" {
|
||||
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1)
|
||||
}
|
||||
utils.WriteToFile(path.Join(apppath, "main.go"),
|
||||
strings.Replace(
|
||||
maingoContent,
|
||||
"{{.conn}}",
|
||||
generate.SQLConn.String(),
|
||||
-1,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
os.Mkdir(path.Join(apppath, "models"), 0755)
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models"), "\x1b[0m")
|
||||
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "object.go"), "\x1b[0m")
|
||||
utils.WriteToFile(path.Join(apppath, "models", "object.go"), apiapp.ApiModels)
|
||||
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "user.go"), "\x1b[0m")
|
||||
utils.WriteToFile(path.Join(apppath, "models", "user.go"), apiapp.ApiModels2)
|
||||
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
|
||||
utils.WriteToFile(path.Join(apppath, "main.go"),
|
||||
strings.Replace(generate.HproseMaingo, "{{.Appname}}", packpath, -1))
|
||||
}
|
||||
beeLogger.Log.Success("New Hprose application successfully created!")
|
||||
return 0
|
||||
}
|
411
cmd/commands/migrate/migrate.go
Normal file
411
cmd/commands/migrate/migrate.go
Normal file
@ -0,0 +1,411 @@
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/beego/bee/cmd/commands"
|
||||
"github.com/beego/bee/cmd/commands/version"
|
||||
"github.com/beego/bee/config"
|
||||
"github.com/beego/bee/utils"
|
||||
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
)
|
||||
|
||||
var CmdMigrate = &commands.Command{
|
||||
UsageLine: "migrate [Command]",
|
||||
Short: "Runs database migrations",
|
||||
Long: `The command 'migrate' allows you to run database migrations to keep it up-to-date.
|
||||
|
||||
▶ {{"To run all the migrations:"|bold}}
|
||||
|
||||
$ bee migrate [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
|
||||
▶ {{"To rollback the last migration:"|bold}}
|
||||
|
||||
$ bee migrate rollback [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
|
||||
▶ {{"To do a reset, which will rollback all the migrations:"|bold}}
|
||||
|
||||
$ bee migrate reset [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
|
||||
▶ {{"To update your schema:"|bold}}
|
||||
|
||||
$ bee migrate refresh [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
`,
|
||||
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
|
||||
Run: RunMigration,
|
||||
}
|
||||
|
||||
var mDriver utils.DocValue
|
||||
var mConn utils.DocValue
|
||||
|
||||
func init() {
|
||||
CmdMigrate.Flag.Var(&mDriver, "driver", "Database driver. Either mysql, postgres or sqlite.")
|
||||
CmdMigrate.Flag.Var(&mConn, "conn", "Connection string used by the driver to connect to a database instance.")
|
||||
commands.AvailableCommands = append(commands.AvailableCommands, CmdMigrate)
|
||||
}
|
||||
|
||||
// runMigration is the entry point for starting a migration
|
||||
func RunMigration(cmd *commands.Command, args []string) int {
|
||||
currpath, _ := os.Getwd()
|
||||
|
||||
gps := utils.GetGOPATHs()
|
||||
if len(gps) == 0 {
|
||||
beeLogger.Log.Fatal("GOPATH environment variable is not set or empty")
|
||||
}
|
||||
|
||||
gopath := gps[0]
|
||||
|
||||
beeLogger.Log.Debugf("GOPATH: %s", utils.FILE(), utils.LINE(), gopath)
|
||||
|
||||
// Getting command line arguments
|
||||
if len(args) != 0 {
|
||||
cmd.Flag.Parse(args[1:])
|
||||
}
|
||||
if mDriver == "" {
|
||||
mDriver = utils.DocValue(config.Conf.Database.Driver)
|
||||
if mDriver == "" {
|
||||
mDriver = "mysql"
|
||||
}
|
||||
}
|
||||
if mConn == "" {
|
||||
mConn = utils.DocValue(config.Conf.Database.Conn)
|
||||
if mConn == "" {
|
||||
mConn = "root:@tcp(127.0.0.1:3306)/test"
|
||||
}
|
||||
}
|
||||
beeLogger.Log.Infof("Using '%s' as 'driver'", mDriver)
|
||||
beeLogger.Log.Infof("Using '%s' as 'conn'", mConn)
|
||||
driverStr, connStr := string(mDriver), string(mConn)
|
||||
if len(args) == 0 {
|
||||
// run all outstanding migrations
|
||||
beeLogger.Log.Info("Running all outstanding migrations")
|
||||
MigrateUpdate(currpath, driverStr, connStr)
|
||||
} else {
|
||||
mcmd := args[0]
|
||||
switch mcmd {
|
||||
case "rollback":
|
||||
beeLogger.Log.Info("Rolling back the last migration operation")
|
||||
MigrateRollback(currpath, driverStr, connStr)
|
||||
case "reset":
|
||||
beeLogger.Log.Info("Reseting all migrations")
|
||||
MigrateReset(currpath, driverStr, connStr)
|
||||
case "refresh":
|
||||
beeLogger.Log.Info("Refreshing all migrations")
|
||||
MigrateRefresh(currpath, driverStr, connStr)
|
||||
default:
|
||||
beeLogger.Log.Fatal("Command is missing")
|
||||
}
|
||||
}
|
||||
beeLogger.Log.Success("Migration successful!")
|
||||
return 0
|
||||
}
|
||||
|
||||
// migrate generates source code, build it, and invoke the binary who does the actual migration
|
||||
func migrate(goal, currpath, driver, connStr string) {
|
||||
dir := path.Join(currpath, "database", "migrations")
|
||||
postfix := ""
|
||||
if runtime.GOOS == "windows" {
|
||||
postfix = ".exe"
|
||||
}
|
||||
binary := "m" + postfix
|
||||
source := binary + ".go"
|
||||
|
||||
// Connect to database
|
||||
db, err := sql.Open(driver, connStr)
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("Could not connect to database using '%s': %s", connStr, err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
checkForSchemaUpdateTable(db, driver)
|
||||
latestName, latestTime := getLatestMigration(db, goal)
|
||||
writeMigrationSourceFile(dir, source, driver, connStr, latestTime, latestName, goal)
|
||||
buildMigrationBinary(dir, binary)
|
||||
runMigrationBinary(dir, binary)
|
||||
removeTempFile(dir, source)
|
||||
removeTempFile(dir, binary)
|
||||
}
|
||||
|
||||
// checkForSchemaUpdateTable checks the existence of migrations table.
|
||||
// It checks for the proper table structures and creates the table using MYSQL_MIGRATION_DDL if it does not exist.
|
||||
func checkForSchemaUpdateTable(db *sql.DB, driver string) {
|
||||
showTableSQL := showMigrationsTableSQL(driver)
|
||||
if rows, err := db.Query(showTableSQL); err != nil {
|
||||
beeLogger.Log.Fatalf("Could not show migrations table: %s", err)
|
||||
} else if !rows.Next() {
|
||||
// No migrations table, create new ones
|
||||
createTableSQL := createMigrationsTableSQL(driver)
|
||||
|
||||
beeLogger.Log.Infof("Creating 'migrations' table...")
|
||||
|
||||
if _, err := db.Query(createTableSQL); err != nil {
|
||||
beeLogger.Log.Fatalf("Could not create migrations table: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Checking that migrations table schema are expected
|
||||
selectTableSQL := selectMigrationsTableSQL(driver)
|
||||
if rows, err := db.Query(selectTableSQL); err != nil {
|
||||
beeLogger.Log.Fatalf("Could not show columns of migrations table: %s", err)
|
||||
} else {
|
||||
for rows.Next() {
|
||||
var fieldBytes, typeBytes, nullBytes, keyBytes, defaultBytes, extraBytes []byte
|
||||
if err := rows.Scan(&fieldBytes, &typeBytes, &nullBytes, &keyBytes, &defaultBytes, &extraBytes); err != nil {
|
||||
beeLogger.Log.Fatalf("Could not read column information: %s", err)
|
||||
}
|
||||
fieldStr, typeStr, nullStr, keyStr, defaultStr, extraStr :=
|
||||
string(fieldBytes), string(typeBytes), string(nullBytes), string(keyBytes), string(defaultBytes), string(extraBytes)
|
||||
if fieldStr == "id_migration" {
|
||||
if keyStr != "PRI" || extraStr != "auto_increment" {
|
||||
beeLogger.Log.Hint("Expecting KEY: PRI, EXTRA: auto_increment")
|
||||
beeLogger.Log.Fatalf("Column migration.id_migration type mismatch: KEY: %s, EXTRA: %s", keyStr, extraStr)
|
||||
}
|
||||
} else if fieldStr == "name" {
|
||||
if !strings.HasPrefix(typeStr, "varchar") || nullStr != "YES" {
|
||||
beeLogger.Log.Hint("Expecting TYPE: varchar, NULL: YES")
|
||||
beeLogger.Log.Fatalf("Column migration.name type mismatch: TYPE: %s, NULL: %s", typeStr, nullStr)
|
||||
}
|
||||
} else if fieldStr == "created_at" {
|
||||
if typeStr != "timestamp" || defaultStr != "CURRENT_TIMESTAMP" {
|
||||
beeLogger.Log.Hint("Expecting TYPE: timestamp, DEFAULT: CURRENT_TIMESTAMP")
|
||||
beeLogger.Log.Fatalf("Column migration.timestamp type mismatch: TYPE: %s, DEFAULT: %s", typeStr, defaultStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showMigrationsTableSQL(driver string) string {
|
||||
switch driver {
|
||||
case "mysql":
|
||||
return "SHOW TABLES LIKE 'migrations'"
|
||||
case "postgres":
|
||||
return "SELECT * FROM pg_catalog.pg_tables WHERE tablename = 'migrations';"
|
||||
default:
|
||||
return "SHOW TABLES LIKE 'migrations'"
|
||||
}
|
||||
}
|
||||
|
||||
func createMigrationsTableSQL(driver string) string {
|
||||
switch driver {
|
||||
case "mysql":
|
||||
return MYSQLMigrationDDL
|
||||
case "postgres":
|
||||
return POSTGRESMigrationDDL
|
||||
default:
|
||||
return MYSQLMigrationDDL
|
||||
}
|
||||
}
|
||||
|
||||
func selectMigrationsTableSQL(driver string) string {
|
||||
switch driver {
|
||||
case "mysql":
|
||||
return "DESC migrations"
|
||||
case "postgres":
|
||||
return "SELECT * FROM migrations WHERE false ORDER BY id_migration;"
|
||||
default:
|
||||
return "DESC migrations"
|
||||
}
|
||||
}
|
||||
|
||||
// getLatestMigration retrives latest migration with status 'update'
|
||||
func getLatestMigration(db *sql.DB, goal string) (file string, createdAt int64) {
|
||||
sql := "SELECT name FROM migrations where status = 'update' ORDER BY id_migration DESC LIMIT 1"
|
||||
if rows, err := db.Query(sql); err != nil {
|
||||
beeLogger.Log.Fatalf("Could not retrieve migrations: %s", err)
|
||||
} else {
|
||||
if rows.Next() {
|
||||
if err := rows.Scan(&file); err != nil {
|
||||
beeLogger.Log.Fatalf("Could not read migrations in database: %s", err)
|
||||
}
|
||||
createdAtStr := file[len(file)-15:]
|
||||
if t, err := time.Parse("20060102_150405", createdAtStr); err != nil {
|
||||
beeLogger.Log.Fatalf("Could not parse time: %s", err)
|
||||
} else {
|
||||
createdAt = t.Unix()
|
||||
}
|
||||
} else {
|
||||
// migration table has no 'update' record, no point rolling back
|
||||
if goal == "rollback" {
|
||||
beeLogger.Log.Fatal("There is nothing to rollback")
|
||||
}
|
||||
file, createdAt = "", 0
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// writeMigrationSourceFile create the source file based on MIGRATION_MAIN_TPL
|
||||
func writeMigrationSourceFile(dir, source, driver, connStr string, latestTime int64, latestName string, task string) {
|
||||
changeDir(dir)
|
||||
if f, err := os.OpenFile(source, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err != nil {
|
||||
beeLogger.Log.Fatalf("Could not create file: %s", err)
|
||||
} else {
|
||||
content := strings.Replace(MigrationMainTPL, "{{DBDriver}}", driver, -1)
|
||||
content = strings.Replace(content, "{{ConnStr}}", connStr, -1)
|
||||
content = strings.Replace(content, "{{LatestTime}}", strconv.FormatInt(latestTime, 10), -1)
|
||||
content = strings.Replace(content, "{{LatestName}}", latestName, -1)
|
||||
content = strings.Replace(content, "{{Task}}", task, -1)
|
||||
if _, err := f.WriteString(content); err != nil {
|
||||
beeLogger.Log.Fatalf("Could not write to file: %s", err)
|
||||
}
|
||||
utils.CloseFile(f)
|
||||
}
|
||||
}
|
||||
|
||||
// buildMigrationBinary changes directory to database/migrations folder and go-build the source
|
||||
func buildMigrationBinary(dir, binary string) {
|
||||
changeDir(dir)
|
||||
cmd := exec.Command("go", "build", "-o", binary)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
beeLogger.Log.Errorf("Could not build migration binary: %s", err)
|
||||
formatShellErrOutput(string(out))
|
||||
removeTempFile(dir, binary)
|
||||
removeTempFile(dir, binary+".go")
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
// runMigrationBinary runs the migration program who does the actual work
|
||||
func runMigrationBinary(dir, binary string) {
|
||||
changeDir(dir)
|
||||
cmd := exec.Command("./" + binary)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
formatShellOutput(string(out))
|
||||
beeLogger.Log.Errorf("Could not run migration binary: %s", err)
|
||||
removeTempFile(dir, binary)
|
||||
removeTempFile(dir, binary+".go")
|
||||
os.Exit(2)
|
||||
} else {
|
||||
formatShellOutput(string(out))
|
||||
}
|
||||
}
|
||||
|
||||
// changeDir changes working directory to dir.
|
||||
// It exits the system when encouter an error
|
||||
func changeDir(dir string) {
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
beeLogger.Log.Fatalf("Could not find migration directory: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// removeTempFile removes a file in dir
|
||||
func removeTempFile(dir, file string) {
|
||||
changeDir(dir)
|
||||
if err := os.Remove(file); err != nil {
|
||||
beeLogger.Log.Warnf("Could not remove temporary file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// formatShellErrOutput formats the error shell output
|
||||
func formatShellErrOutput(o string) {
|
||||
for _, line := range strings.Split(o, "\n") {
|
||||
if line != "" {
|
||||
beeLogger.Log.Errorf("|> %s", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// formatShellOutput formats the normal shell output
|
||||
func formatShellOutput(o string) {
|
||||
for _, line := range strings.Split(o, "\n") {
|
||||
if line != "" {
|
||||
beeLogger.Log.Infof("|> %s", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// MigrationMainTPL migration main template
|
||||
MigrationMainTPL = `package main
|
||||
|
||||
import(
|
||||
"os"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/astaxie/beego/migration"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
func init(){
|
||||
orm.RegisterDataBase("default", "{{DBDriver}}","{{ConnStr}}")
|
||||
}
|
||||
|
||||
func main(){
|
||||
task := "{{Task}}"
|
||||
switch task {
|
||||
case "upgrade":
|
||||
if err := migration.Upgrade({{LatestTime}}); err != nil {
|
||||
os.Exit(2)
|
||||
}
|
||||
case "rollback":
|
||||
if err := migration.Rollback("{{LatestName}}"); err != nil {
|
||||
os.Exit(2)
|
||||
}
|
||||
case "reset":
|
||||
if err := migration.Reset(); err != nil {
|
||||
os.Exit(2)
|
||||
}
|
||||
case "refresh":
|
||||
if err := migration.Refresh(); err != nil {
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
// MYSQLMigrationDDL MySQL migration SQL
|
||||
MYSQLMigrationDDL = `
|
||||
CREATE TABLE migrations (
|
||||
id_migration int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key',
|
||||
name varchar(255) DEFAULT NULL COMMENT 'migration name, unique',
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'date migrated or rolled back',
|
||||
statements longtext COMMENT 'SQL statements for this migration',
|
||||
rollback_statements longtext COMMENT 'SQL statment for rolling back migration',
|
||||
status ENUM('update', 'rollback') COMMENT 'update indicates it is a normal migration while rollback means this migration is rolled back',
|
||||
PRIMARY KEY (id_migration)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8
|
||||
`
|
||||
// POSTGRESMigrationDDL Postgres migration SQL
|
||||
POSTGRESMigrationDDL = `
|
||||
CREATE TYPE migrations_status AS ENUM('update', 'rollback');
|
||||
|
||||
CREATE TABLE migrations (
|
||||
id_migration SERIAL PRIMARY KEY,
|
||||
name varchar(255) DEFAULT NULL,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
statements text,
|
||||
rollback_statements text,
|
||||
status migrations_status
|
||||
)`
|
||||
)
|
||||
|
||||
// MigrateUpdate does the schema update
|
||||
func MigrateUpdate(currpath, driver, connStr string) {
|
||||
migrate("upgrade", currpath, driver, connStr)
|
||||
}
|
||||
|
||||
// MigrateRollback rolls back the latest migration
|
||||
func MigrateRollback(currpath, driver, connStr string) {
|
||||
migrate("rollback", currpath, driver, connStr)
|
||||
}
|
||||
|
||||
// MigrateReset rolls back all migrations
|
||||
func MigrateReset(currpath, driver, connStr string) {
|
||||
migrate("reset", currpath, driver, connStr)
|
||||
}
|
||||
|
||||
// migrationRefresh rolls back all migrations and start over again
|
||||
func MigrateRefresh(currpath, driver, connStr string) {
|
||||
migrate("refresh", currpath, driver, connStr)
|
||||
}
|
315
cmd/commands/new/new.go
Normal file
315
cmd/commands/new/new.go
Normal file
File diff suppressed because one or more lines are too long
577
cmd/commands/pack/pack.go
Normal file
577
cmd/commands/pack/pack.go
Normal file
@ -0,0 +1,577 @@
|
||||
package pack
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"compress/gzip"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
path "path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/beego/bee/cmd/commands"
|
||||
"github.com/beego/bee/cmd/commands/version"
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
"github.com/beego/bee/utils"
|
||||
)
|
||||
|
||||
var CmdPack = &commands.Command{
|
||||
CustomFlags: true,
|
||||
UsageLine: "pack",
|
||||
Short: "Compresses a Beego application into a single file",
|
||||
Long: `Pack is used to compress Beego applications into a tarball/zip file.
|
||||
This eases the deployment by directly extracting the file to a server.
|
||||
|
||||
{{"Example:"|bold}}
|
||||
$ bee pack -v -ba="-ldflags '-s -w'"
|
||||
`,
|
||||
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
|
||||
Run: packApp,
|
||||
}
|
||||
|
||||
var (
|
||||
appPath string
|
||||
excludeP string
|
||||
excludeS string
|
||||
outputP string
|
||||
excludeR utils.ListOpts
|
||||
fsym bool
|
||||
ssym bool
|
||||
build bool
|
||||
buildArgs string
|
||||
buildEnvs utils.ListOpts
|
||||
verbose bool
|
||||
format string
|
||||
)
|
||||
|
||||
func init() {
|
||||
fs := flag.NewFlagSet("pack", flag.ContinueOnError)
|
||||
fs.StringVar(&appPath, "p", "", "Set the application path. Defaults to the current path.")
|
||||
fs.BoolVar(&build, "b", true, "Tell the command to do a build for the current platform. Defaults to true.")
|
||||
fs.StringVar(&buildArgs, "ba", "", "Specify additional args for Go build.")
|
||||
fs.Var(&buildEnvs, "be", "Specify additional env variables for Go build. e.g. GOARCH=arm.")
|
||||
fs.StringVar(&outputP, "o", "", "Set the compressed file output path. Defaults to the current path.")
|
||||
fs.StringVar(&format, "f", "tar.gz", "Set file format. Either tar.gz or zip. Defaults to tar.gz.")
|
||||
fs.StringVar(&excludeP, "exp", ".", "Set prefixes of paths to be excluded. Uses a column (:) as separator.")
|
||||
fs.StringVar(&excludeS, "exs", ".go:.DS_Store:.tmp", "Set suffixes of paths to be excluded. Uses a column (:) as separator.")
|
||||
fs.Var(&excludeR, "exr", "Set a regular expression of files to be excluded.")
|
||||
fs.BoolVar(&fsym, "fs", false, "Tell the command to follow symlinks. Defaults to false.")
|
||||
fs.BoolVar(&ssym, "ss", false, "Tell the command to skip symlinks. Defaults to false.")
|
||||
fs.BoolVar(&verbose, "v", false, "Be more verbose during the operation. Defaults to false.")
|
||||
CmdPack.Flag = *fs
|
||||
commands.AvailableCommands = append(commands.AvailableCommands, CmdPack)
|
||||
}
|
||||
|
||||
type walker interface {
|
||||
isExclude(string) bool
|
||||
isEmpty(string) bool
|
||||
relName(string) string
|
||||
virPath(string) string
|
||||
compress(string, string, os.FileInfo) (bool, error)
|
||||
walkRoot(string) error
|
||||
}
|
||||
|
||||
type byName []os.FileInfo
|
||||
|
||||
func (f byName) Len() int { return len(f) }
|
||||
func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
|
||||
func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||
|
||||
type walkFileTree struct {
|
||||
wak walker
|
||||
prefix string
|
||||
excludePrefix []string
|
||||
excludeRegexp []*regexp.Regexp
|
||||
excludeSuffix []string
|
||||
allfiles map[string]bool
|
||||
output *io.Writer
|
||||
}
|
||||
|
||||
func (wft *walkFileTree) isExclude(fPath string) bool {
|
||||
if fPath == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, prefix := range wft.excludePrefix {
|
||||
if strings.HasPrefix(fPath, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, suffix := range wft.excludeSuffix {
|
||||
if strings.HasSuffix(fPath, suffix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (wft *walkFileTree) isExcludeName(name string) bool {
|
||||
for _, r := range wft.excludeRegexp {
|
||||
if r.MatchString(name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (wft *walkFileTree) isEmpty(fpath string) bool {
|
||||
fh, _ := os.Open(fpath)
|
||||
defer fh.Close()
|
||||
infos, _ := fh.Readdir(-1)
|
||||
for _, fi := range infos {
|
||||
fn := fi.Name()
|
||||
fp := path.Join(fpath, fn)
|
||||
if wft.isExclude(wft.virPath(fp)) {
|
||||
continue
|
||||
}
|
||||
if wft.isExcludeName(fn) {
|
||||
continue
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink > 0 {
|
||||
continue
|
||||
}
|
||||
if fi.IsDir() && wft.isEmpty(fp) {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (wft *walkFileTree) relName(fpath string) string {
|
||||
name, _ := path.Rel(wft.prefix, fpath)
|
||||
return name
|
||||
}
|
||||
|
||||
func (wft *walkFileTree) virPath(fpath string) string {
|
||||
name := fpath[len(wft.prefix):]
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
name = name[1:]
|
||||
name = path.ToSlash(name)
|
||||
return name
|
||||
}
|
||||
|
||||
func (wft *walkFileTree) readDir(dirname string) ([]os.FileInfo, error) {
|
||||
f, err := os.Open(dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err := f.Readdir(-1)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Sort(byName(list))
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (wft *walkFileTree) walkLeaf(fpath string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fpath == outputP {
|
||||
return nil
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ssym && fi.Mode()&os.ModeSymlink > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := wft.virPath(fpath)
|
||||
|
||||
if wft.allfiles[name] {
|
||||
return nil
|
||||
}
|
||||
|
||||
if added, err := wft.wak.compress(name, fpath, fi); added {
|
||||
if verbose {
|
||||
fmt.Fprintf(*wft.output, "\t%s%scompressed%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", name, "\x1b[0m")
|
||||
}
|
||||
wft.allfiles[name] = true
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (wft *walkFileTree) iterDirectory(fpath string, fi os.FileInfo) error {
|
||||
doFSym := fsym && fi.Mode()&os.ModeSymlink > 0
|
||||
if doFSym {
|
||||
nfi, err := os.Stat(fpath)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
fi = nfi
|
||||
}
|
||||
|
||||
relPath := wft.virPath(fpath)
|
||||
|
||||
if len(relPath) > 0 {
|
||||
if wft.isExcludeName(fi.Name()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if wft.isExclude(relPath) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
err := wft.walkLeaf(fpath, fi, nil)
|
||||
if err != nil {
|
||||
if fi.IsDir() && err == path.SkipDir {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
list, err := wft.readDir(fpath)
|
||||
if err != nil {
|
||||
return wft.walkLeaf(fpath, fi, err)
|
||||
}
|
||||
|
||||
for _, fileInfo := range list {
|
||||
err = wft.iterDirectory(path.Join(fpath, fileInfo.Name()), fileInfo)
|
||||
if err != nil {
|
||||
if !fileInfo.IsDir() || err != path.SkipDir {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wft *walkFileTree) walkRoot(root string) error {
|
||||
wft.prefix = root
|
||||
fi, err := os.Stat(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return wft.iterDirectory(root, fi)
|
||||
}
|
||||
|
||||
type tarWalk struct {
|
||||
walkFileTree
|
||||
tw *tar.Writer
|
||||
}
|
||||
|
||||
func (wft *tarWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
|
||||
isSym := fi.Mode()&os.ModeSymlink > 0
|
||||
link := ""
|
||||
if isSym {
|
||||
link, _ = os.Readlink(fpath)
|
||||
}
|
||||
|
||||
hdr, err := tar.FileInfoHeader(fi, link)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
hdr.Name = name
|
||||
|
||||
tw := wft.tw
|
||||
err = tw.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !isSym {
|
||||
fr, err := os.Open(fpath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer utils.CloseFile(fr)
|
||||
_, err = io.Copy(tw, fr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
tw.Flush()
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type zipWalk struct {
|
||||
walkFileTree
|
||||
zw *zip.Writer
|
||||
}
|
||||
|
||||
func (wft *zipWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
|
||||
isSym := fi.Mode()&os.ModeSymlink > 0
|
||||
|
||||
hdr, err := zip.FileInfoHeader(fi)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
hdr.Name = name
|
||||
|
||||
zw := wft.zw
|
||||
w, err := zw.CreateHeader(hdr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !isSym {
|
||||
fr, err := os.Open(fpath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer utils.CloseFile(fr)
|
||||
_, err = io.Copy(w, fr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
var link string
|
||||
if link, err = os.Readlink(fpath); err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, err = w.Write([]byte(link))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func packDirectory(output io.Writer, excludePrefix []string, excludeSuffix []string,
|
||||
excludeRegexp []*regexp.Regexp, includePath ...string) (err error) {
|
||||
|
||||
beeLogger.Log.Infof("Excluding relpath prefix: %s", strings.Join(excludePrefix, ":"))
|
||||
beeLogger.Log.Infof("Excluding relpath suffix: %s", strings.Join(excludeSuffix, ":"))
|
||||
if len(excludeRegexp) > 0 {
|
||||
beeLogger.Log.Infof("Excluding filename regex: `%s`", strings.Join(excludeR, "`, `"))
|
||||
}
|
||||
|
||||
w, err := os.OpenFile(outputP, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var wft walker
|
||||
|
||||
if format == "zip" {
|
||||
walk := new(zipWalk)
|
||||
walk.output = &output
|
||||
zw := zip.NewWriter(w)
|
||||
defer func() {
|
||||
zw.Close()
|
||||
}()
|
||||
walk.allfiles = make(map[string]bool)
|
||||
walk.zw = zw
|
||||
walk.wak = walk
|
||||
walk.excludePrefix = excludePrefix
|
||||
walk.excludeSuffix = excludeSuffix
|
||||
walk.excludeRegexp = excludeRegexp
|
||||
wft = walk
|
||||
} else {
|
||||
walk := new(tarWalk)
|
||||
walk.output = &output
|
||||
cw := gzip.NewWriter(w)
|
||||
tw := tar.NewWriter(cw)
|
||||
|
||||
defer func() {
|
||||
tw.Flush()
|
||||
cw.Flush()
|
||||
tw.Close()
|
||||
cw.Close()
|
||||
}()
|
||||
walk.allfiles = make(map[string]bool)
|
||||
walk.tw = tw
|
||||
walk.wak = walk
|
||||
walk.excludePrefix = excludePrefix
|
||||
walk.excludeSuffix = excludeSuffix
|
||||
walk.excludeRegexp = excludeRegexp
|
||||
wft = walk
|
||||
}
|
||||
|
||||
for _, p := range includePath {
|
||||
err = wft.walkRoot(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func packApp(cmd *commands.Command, args []string) int {
|
||||
output := cmd.Out()
|
||||
curPath, _ := os.Getwd()
|
||||
thePath := ""
|
||||
|
||||
nArgs := []string{}
|
||||
has := false
|
||||
for _, a := range args {
|
||||
if a != "" && a[0] == '-' {
|
||||
has = true
|
||||
}
|
||||
if has {
|
||||
nArgs = append(nArgs, a)
|
||||
}
|
||||
}
|
||||
cmd.Flag.Parse(nArgs)
|
||||
|
||||
if !path.IsAbs(appPath) {
|
||||
appPath = path.Join(curPath, appPath)
|
||||
}
|
||||
|
||||
thePath, err := path.Abs(appPath)
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("Wrong application path: %s", thePath)
|
||||
}
|
||||
if stat, err := os.Stat(thePath); os.IsNotExist(err) || !stat.IsDir() {
|
||||
beeLogger.Log.Fatalf("Application path does not exist: %s", thePath)
|
||||
}
|
||||
|
||||
beeLogger.Log.Infof("Packaging application on '%s'...", thePath)
|
||||
|
||||
appName := path.Base(thePath)
|
||||
|
||||
goos := runtime.GOOS
|
||||
if v, found := syscall.Getenv("GOOS"); found {
|
||||
goos = v
|
||||
}
|
||||
goarch := runtime.GOARCH
|
||||
if v, found := syscall.Getenv("GOARCH"); found {
|
||||
goarch = v
|
||||
}
|
||||
|
||||
str := strconv.FormatInt(time.Now().UnixNano(), 10)[9:]
|
||||
|
||||
tmpdir := path.Join(os.TempDir(), "beePack-"+str)
|
||||
|
||||
os.Mkdir(tmpdir, 0700)
|
||||
defer func() {
|
||||
// Remove the tmpdir once bee pack is done
|
||||
err := os.RemoveAll(tmpdir)
|
||||
if err != nil {
|
||||
beeLogger.Log.Error("Failed to remove the generated temp dir")
|
||||
}
|
||||
}()
|
||||
|
||||
if build {
|
||||
beeLogger.Log.Info("Building application...")
|
||||
var envs []string
|
||||
for _, env := range buildEnvs {
|
||||
parts := strings.SplitN(env, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
k, v := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
|
||||
if len(k) > 0 && len(v) > 0 {
|
||||
switch k {
|
||||
case "GOOS":
|
||||
goos = v
|
||||
case "GOARCH":
|
||||
goarch = v
|
||||
default:
|
||||
envs = append(envs, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
os.Setenv("GOOS", goos)
|
||||
os.Setenv("GOARCH", goarch)
|
||||
|
||||
beeLogger.Log.Infof("Using: GOOS=%s GOARCH=%s", goos, goarch)
|
||||
|
||||
binPath := path.Join(tmpdir, appName)
|
||||
if goos == "windows" {
|
||||
binPath += ".exe"
|
||||
}
|
||||
|
||||
args := []string{"build", "-o", binPath}
|
||||
if len(buildArgs) > 0 {
|
||||
args = append(args, strings.Fields(buildArgs)...)
|
||||
}
|
||||
|
||||
if verbose {
|
||||
fmt.Fprintf(output, "\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.Env = append(os.Environ(), envs...)
|
||||
execmd.Stdout = os.Stdout
|
||||
execmd.Stderr = os.Stderr
|
||||
execmd.Dir = thePath
|
||||
err = execmd.Run()
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
beeLogger.Log.Success("Build Successful!")
|
||||
}
|
||||
|
||||
switch format {
|
||||
case "zip":
|
||||
default:
|
||||
format = "tar.gz"
|
||||
}
|
||||
|
||||
outputN := appName + "." + format
|
||||
|
||||
if outputP == "" || !path.IsAbs(outputP) {
|
||||
outputP = path.Join(curPath, outputP)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(outputP); err != nil {
|
||||
err = os.MkdirAll(outputP, 0755)
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
outputP = path.Join(outputP, outputN)
|
||||
|
||||
var exp, exs []string
|
||||
for _, p := range strings.Split(excludeP, ":") {
|
||||
if len(p) > 0 {
|
||||
exp = append(exp, p)
|
||||
}
|
||||
}
|
||||
for _, p := range strings.Split(excludeS, ":") {
|
||||
if len(p) > 0 {
|
||||
exs = append(exs, p)
|
||||
}
|
||||
}
|
||||
|
||||
var exr []*regexp.Regexp
|
||||
for _, r := range excludeR {
|
||||
if len(r) > 0 {
|
||||
if re, err := regexp.Compile(r); err != nil {
|
||||
beeLogger.Log.Fatal(err.Error())
|
||||
} else {
|
||||
exr = append(exr, re)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beeLogger.Log.Infof("Writing to output: %s", outputP)
|
||||
|
||||
err = packDirectory(output, exp, exs, exr, tmpdir, thePath)
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
beeLogger.Log.Success("Application packed!")
|
||||
return 0
|
||||
}
|
88
cmd/commands/run/docs.go
Normal file
88
cmd/commands/run/docs.go
Normal file
@ -0,0 +1,88 @@
|
||||
package run
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
swaggerVersion = "2"
|
||||
swaggerlink = "https://github.com/beego/swagger/archive/v" + swaggerVersion + ".zip"
|
||||
)
|
||||
|
||||
func downloadFromURL(url, fileName string) {
|
||||
var down bool
|
||||
if fd, err := os.Stat(fileName); err != nil && os.IsNotExist(err) {
|
||||
down = true
|
||||
} else if fd.Size() == int64(0) {
|
||||
down = true
|
||||
} else {
|
||||
beeLogger.Log.Infof("'%s' already exists", fileName)
|
||||
return
|
||||
}
|
||||
if down {
|
||||
beeLogger.Log.Infof("Downloading '%s' to '%s'...", url, fileName)
|
||||
output, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
beeLogger.Log.Errorf("Error while creating '%s': %s", fileName, err)
|
||||
return
|
||||
}
|
||||
defer output.Close()
|
||||
|
||||
response, err := http.Get(url)
|
||||
if err != nil {
|
||||
beeLogger.Log.Errorf("Error while downloading '%s': %s", url, err)
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
n, err := io.Copy(output, response.Body)
|
||||
if err != nil {
|
||||
beeLogger.Log.Errorf("Error while downloading '%s': %s", url, err)
|
||||
return
|
||||
}
|
||||
beeLogger.Log.Successf("%d bytes downloaded!", n)
|
||||
}
|
||||
}
|
||||
|
||||
func unzipAndDelete(src string) error {
|
||||
beeLogger.Log.Infof("Unzipping '%s'...", src)
|
||||
r, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
rp := strings.NewReplacer("swagger-"+swaggerVersion, "swagger")
|
||||
for _, f := range r.File {
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
fname := rp.Replace(f.Name)
|
||||
if f.FileInfo().IsDir() {
|
||||
os.MkdirAll(fname, f.Mode())
|
||||
} else {
|
||||
f, err := os.OpenFile(
|
||||
fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
beeLogger.Log.Successf("Done! Deleting '%s'...", src)
|
||||
return os.RemoveAll(src)
|
||||
}
|
192
cmd/commands/run/reload.go
Normal file
192
cmd/commands/run/reload.go
Normal file
@ -0,0 +1,192 @@
|
||||
// Copyright 2017 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 run
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// wsBroker maintains the set of active clients and broadcasts messages to the clients.
|
||||
type wsBroker struct {
|
||||
clients map[*wsClient]bool // Registered clients.
|
||||
broadcast chan []byte // Inbound messages from the clients.
|
||||
register chan *wsClient // Register requests from the clients.
|
||||
unregister chan *wsClient // Unregister requests from clients.
|
||||
}
|
||||
|
||||
func (br *wsBroker) run() {
|
||||
for {
|
||||
select {
|
||||
case client := <-br.register:
|
||||
br.clients[client] = true
|
||||
case client := <-br.unregister:
|
||||
if _, ok := br.clients[client]; ok {
|
||||
delete(br.clients, client)
|
||||
close(client.send)
|
||||
}
|
||||
case message := <-br.broadcast:
|
||||
for client := range br.clients {
|
||||
select {
|
||||
case client.send <- message:
|
||||
default:
|
||||
close(client.send)
|
||||
delete(br.clients, client)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wsClient represents the end-client.
|
||||
type wsClient struct {
|
||||
broker *wsBroker // The broker.
|
||||
conn *websocket.Conn // The websocket connection.
|
||||
send chan []byte // Buffered channel of outbound messages.
|
||||
}
|
||||
|
||||
// readPump pumps messages from the websocket connection to the broker.
|
||||
func (c *wsClient) readPump() {
|
||||
defer func() {
|
||||
c.broker.unregister <- c
|
||||
c.conn.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
_, _, err := c.conn.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
||||
beeLogger.Log.Errorf("An error happened when reading from the Websocket client: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write writes a message with the given message type and payload.
|
||||
func (c *wsClient) write(mt int, payload []byte) error {
|
||||
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
return c.conn.WriteMessage(mt, payload)
|
||||
}
|
||||
|
||||
// writePump pumps messages from the broker to the websocket connection.
|
||||
func (c *wsClient) writePump() {
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
c.conn.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-c.send:
|
||||
if !ok {
|
||||
// The broker closed the channel.
|
||||
c.write(websocket.CloseMessage, []byte{})
|
||||
return
|
||||
}
|
||||
|
||||
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
w, err := c.conn.NextWriter(websocket.TextMessage)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
w.Write(message)
|
||||
|
||||
n := len(c.send)
|
||||
for i := 0; i < n; i++ {
|
||||
w.Write([]byte("/n"))
|
||||
w.Write(<-c.send)
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
case <-ticker.C:
|
||||
if err := c.write(websocket.PingMessage, []byte{}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
broker *wsBroker // The broker.
|
||||
reloadAddress = ":12450" // The port on which the reload server will listen to.
|
||||
|
||||
upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
writeWait = 10 * time.Second // Time allowed to write a message to the peer.
|
||||
pongWait = 60 * time.Second // Time allowed to read the next pong message from the peer.
|
||||
pingPeriod = (pongWait * 9) / 10 // Send pings to peer with this period. Must be less than pongWait.
|
||||
)
|
||||
|
||||
func startReloadServer() {
|
||||
broker = &wsBroker{
|
||||
broadcast: make(chan []byte),
|
||||
register: make(chan *wsClient),
|
||||
unregister: make(chan *wsClient),
|
||||
clients: make(map[*wsClient]bool),
|
||||
}
|
||||
|
||||
go broker.run()
|
||||
http.HandleFunc("/reload", func(w http.ResponseWriter, r *http.Request) {
|
||||
handleWsRequest(broker, w, r)
|
||||
})
|
||||
|
||||
go startServer()
|
||||
beeLogger.Log.Infof("Reload server listening at %s", reloadAddress)
|
||||
}
|
||||
|
||||
func startServer() {
|
||||
err := http.ListenAndServe(reloadAddress, nil)
|
||||
if err != nil {
|
||||
beeLogger.Log.Errorf("Failed to start up the Reload server: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func sendReload(payload string) {
|
||||
message := bytes.TrimSpace([]byte(payload))
|
||||
broker.broadcast <- message
|
||||
}
|
||||
|
||||
// handleWsRequest handles websocket requests from the peer.
|
||||
func handleWsRequest(broker *wsBroker, w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
beeLogger.Log.Errorf("error while upgrading server connection: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
client := &wsClient{
|
||||
broker: broker,
|
||||
conn: conn,
|
||||
send: make(chan []byte, 256),
|
||||
}
|
||||
client.broker.register <- client
|
||||
|
||||
go client.writePump()
|
||||
client.readPump()
|
||||
}
|
247
cmd/commands/run/run.go
Normal file
247
cmd/commands/run/run.go
Normal file
@ -0,0 +1,247 @@
|
||||
// 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 run
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
path "path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/bee/cmd/commands"
|
||||
"github.com/beego/bee/cmd/commands/version"
|
||||
"github.com/beego/bee/config"
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
"github.com/beego/bee/utils"
|
||||
)
|
||||
|
||||
var CmdRun = &commands.Command{
|
||||
UsageLine: "run [appname] [watchall] [-main=*.go] [-downdoc=true] [-gendoc=true] [-vendor=true] [-e=folderToExclude] [-ex=extraPackageToWatch] [-tags=goBuildTags] [-runmode=BEEGO_RUNMODE]",
|
||||
Short: "Run the application by starting a local development server",
|
||||
Long: `
|
||||
Run command will supervise the filesystem of the application for any changes, and recompile/restart it.
|
||||
|
||||
`,
|
||||
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
|
||||
Run: RunApp,
|
||||
}
|
||||
|
||||
var (
|
||||
mainFiles utils.ListOpts
|
||||
downdoc utils.DocValue
|
||||
gendoc utils.DocValue
|
||||
// The flags list of the paths excluded from watching
|
||||
excludedPaths utils.StrFlags
|
||||
// Pass through to -tags arg of "go build"
|
||||
buildTags string
|
||||
// Application path
|
||||
currpath string
|
||||
// Application name
|
||||
appname string
|
||||
// Channel to signal an Exit
|
||||
exit chan bool
|
||||
// Flag to watch the vendor folder
|
||||
vendorWatch bool
|
||||
// Current user workspace
|
||||
currentGoPath string
|
||||
// Current runmode
|
||||
runmode string
|
||||
// Extra directories
|
||||
extraPackages utils.StrFlags
|
||||
)
|
||||
var started = make(chan bool)
|
||||
|
||||
func init() {
|
||||
CmdRun.Flag.Var(&mainFiles, "main", "Specify main go files.")
|
||||
CmdRun.Flag.Var(&gendoc, "gendoc", "Enable auto-generate the docs.")
|
||||
CmdRun.Flag.Var(&downdoc, "downdoc", "Enable auto-download of the swagger file if it does not exist.")
|
||||
CmdRun.Flag.Var(&excludedPaths, "e", "List of paths to exclude.")
|
||||
CmdRun.Flag.BoolVar(&vendorWatch, "vendor", false, "Enable watch vendor folder.")
|
||||
CmdRun.Flag.StringVar(&buildTags, "tags", "", "Set the build tags. See: https://golang.org/pkg/go/build/")
|
||||
CmdRun.Flag.StringVar(&runmode, "runmode", "", "Set the Beego run mode.")
|
||||
CmdRun.Flag.Var(&extraPackages, "ex", "List of extra package to watch.")
|
||||
exit = make(chan bool)
|
||||
commands.AvailableCommands = append(commands.AvailableCommands, CmdRun)
|
||||
}
|
||||
|
||||
func RunApp(cmd *commands.Command, args []string) int {
|
||||
if len(args) == 0 || args[0] == "watchall" {
|
||||
currpath, _ = os.Getwd()
|
||||
if found, _gopath, _ := utils.SearchGOPATHs(currpath); found {
|
||||
appname = path.Base(currpath)
|
||||
currentGoPath = _gopath
|
||||
} else {
|
||||
beeLogger.Log.Fatalf("No application '%s' found in your GOPATH", currpath)
|
||||
}
|
||||
} else {
|
||||
// Check if passed Bee application path/name exists in the GOPATH(s)
|
||||
if found, _gopath, _path := utils.SearchGOPATHs(args[0]); found {
|
||||
currpath = _path
|
||||
currentGoPath = _gopath
|
||||
appname = path.Base(currpath)
|
||||
} else {
|
||||
beeLogger.Log.Fatalf("No application '%s' found in your GOPATH", args[0])
|
||||
}
|
||||
|
||||
if strings.HasSuffix(appname, ".go") && utils.IsExist(currpath) {
|
||||
beeLogger.Log.Warnf("The appname is in conflict with file's current path. Do you want to build appname as '%s'", appname)
|
||||
beeLogger.Log.Info("Do you want to overwrite it? [yes|no] ")
|
||||
if !utils.AskForConfirmation() {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beeLogger.Log.Infof("Using '%s' as 'appname'", appname)
|
||||
|
||||
beeLogger.Log.Debugf("Current path: %s", utils.FILE(), utils.LINE(), currpath)
|
||||
|
||||
if runmode == "prod" || runmode == "dev" {
|
||||
os.Setenv("BEEGO_RUNMODE", runmode)
|
||||
beeLogger.Log.Infof("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE"))
|
||||
} else if runmode != "" {
|
||||
os.Setenv("BEEGO_RUNMODE", runmode)
|
||||
beeLogger.Log.Warnf("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE"))
|
||||
} else if os.Getenv("BEEGO_RUNMODE") != "" {
|
||||
beeLogger.Log.Warnf("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE"))
|
||||
}
|
||||
|
||||
var paths []string
|
||||
readAppDirectories(currpath, &paths)
|
||||
|
||||
// Because monitor files has some issues, we watch current directory
|
||||
// and ignore non-go files.
|
||||
for _, p := range config.Conf.DirStruct.Others {
|
||||
paths = append(paths, strings.Replace(p, "$GOPATH", currentGoPath, -1))
|
||||
}
|
||||
|
||||
if len(extraPackages) > 0 {
|
||||
// get the full path
|
||||
for _, packagePath := range extraPackages {
|
||||
if found, _, _fullPath := utils.SearchGOPATHs(packagePath); found {
|
||||
readAppDirectories(_fullPath, &paths)
|
||||
} else {
|
||||
beeLogger.Log.Warnf("No extra package '%s' found in your GOPATH", packagePath)
|
||||
}
|
||||
}
|
||||
// let paths unique
|
||||
strSet := make(map[string]struct{})
|
||||
for _, p := range paths {
|
||||
strSet[p] = struct{}{}
|
||||
}
|
||||
paths = make([]string, len(strSet))
|
||||
index := 0
|
||||
for i := range strSet {
|
||||
paths[index] = i
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
files := []string{}
|
||||
for _, arg := range mainFiles {
|
||||
if len(arg) > 0 {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start the Reload server (if enabled)
|
||||
if config.Conf.EnableReload {
|
||||
startReloadServer()
|
||||
}
|
||||
if gendoc == "true" {
|
||||
NewWatcher(paths, files, true)
|
||||
AutoBuild(files, true)
|
||||
} else {
|
||||
NewWatcher(paths, files, false)
|
||||
AutoBuild(files, false)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-exit:
|
||||
runtime.Goexit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readAppDirectories(directory string, paths *[]string) {
|
||||
fileInfos, err := ioutil.ReadDir(directory)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
useDirectory := false
|
||||
for _, fileInfo := range fileInfos {
|
||||
if strings.HasSuffix(fileInfo.Name(), "docs") {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(fileInfo.Name(), "swagger") {
|
||||
continue
|
||||
}
|
||||
|
||||
if !vendorWatch && strings.HasSuffix(fileInfo.Name(), "vendor") {
|
||||
continue
|
||||
}
|
||||
|
||||
if isExcluded(path.Join(directory, fileInfo.Name())) {
|
||||
continue
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() && fileInfo.Name()[0] != '.' {
|
||||
readAppDirectories(directory+"/"+fileInfo.Name(), paths)
|
||||
continue
|
||||
}
|
||||
|
||||
if useDirectory {
|
||||
continue
|
||||
}
|
||||
|
||||
if path.Ext(fileInfo.Name()) == ".go" || (ifStaticFile(fileInfo.Name()) && config.Conf.EnableReload) {
|
||||
*paths = append(*paths, directory)
|
||||
useDirectory = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// If a file is excluded
|
||||
func isExcluded(filePath string) bool {
|
||||
for _, p := range excludedPaths {
|
||||
absP, err := path.Abs(p)
|
||||
if err != nil {
|
||||
beeLogger.Log.Errorf("Cannot get absolute path of '%s'", p)
|
||||
continue
|
||||
}
|
||||
absFilePath, err := path.Abs(filePath)
|
||||
if err != nil {
|
||||
beeLogger.Log.Errorf("Cannot get absolute path of '%s'", filePath)
|
||||
break
|
||||
}
|
||||
if strings.HasPrefix(absFilePath, absP) {
|
||||
beeLogger.Log.Infof("'%s' is not being watched", filePath)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
288
cmd/commands/run/watch.go
Normal file
288
cmd/commands/run/watch.go
Normal file
@ -0,0 +1,288 @@
|
||||
// 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 run
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/beego/bee/config"
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
"github.com/beego/bee/logger/colors"
|
||||
"github.com/beego/bee/utils"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
var (
|
||||
cmd *exec.Cmd
|
||||
state sync.Mutex
|
||||
eventTime = make(map[string]int64)
|
||||
scheduleTime time.Time
|
||||
watchExts = []string{".go"}
|
||||
watchExtsStatic = []string{".html", ".tpl", ".js", ".css"}
|
||||
ignoredFilesRegExps = []string{
|
||||
`.#(\w+).go`,
|
||||
`.(\w+).go.swp`,
|
||||
`(\w+).go~`,
|
||||
`(\w+).tmp`,
|
||||
}
|
||||
)
|
||||
|
||||
// NewWatcher starts an fsnotify Watcher on the specified paths
|
||||
func NewWatcher(paths []string, files []string, isgenerate bool) {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("Failed to create watcher: %s", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case e := <-watcher.Events:
|
||||
isBuild := true
|
||||
|
||||
if ifStaticFile(e.Name) && config.Conf.EnableReload {
|
||||
sendReload(e.String())
|
||||
continue
|
||||
}
|
||||
// Skip ignored files
|
||||
if shouldIgnoreFile(e.Name) {
|
||||
continue
|
||||
}
|
||||
if !shouldWatchFileWithExtension(e.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
mt := getFileModTime(e.Name)
|
||||
if t := eventTime[e.Name]; mt == t {
|
||||
beeLogger.Log.Infof(colors.Bold("Skipping: ")+"%s", e.String())
|
||||
isBuild = false
|
||||
}
|
||||
|
||||
eventTime[e.Name] = mt
|
||||
|
||||
if isBuild {
|
||||
beeLogger.Log.Infof("Event fired: %s", e)
|
||||
go func() {
|
||||
// Wait 1s before autobuild until there is no file change.
|
||||
scheduleTime = time.Now().Add(1 * time.Second)
|
||||
time.Sleep(scheduleTime.Sub(time.Now()))
|
||||
AutoBuild(files, isgenerate)
|
||||
}()
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
beeLogger.Log.Warnf("Watcher error: %s", err.Error()) // No need to exit here
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
beeLogger.Log.Info("Initializing watcher...")
|
||||
for _, path := range paths {
|
||||
beeLogger.Log.Infof(colors.Bold("Watching: ")+"%s", path)
|
||||
err = watcher.Add(path)
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("Failed to watch directory: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getFileModTime returns unix timestamp of `os.File.ModTime` for the given path.
|
||||
func getFileModTime(path string) int64 {
|
||||
path = strings.Replace(path, "\\", "/", -1)
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
beeLogger.Log.Errorf("Failed to open file on '%s': %s", path, err)
|
||||
return time.Now().Unix()
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
beeLogger.Log.Errorf("Failed to get file stats: %s", err)
|
||||
return time.Now().Unix()
|
||||
}
|
||||
|
||||
return fi.ModTime().Unix()
|
||||
}
|
||||
|
||||
// AutoBuild builds the specified set of files
|
||||
func AutoBuild(files []string, isgenerate bool) {
|
||||
state.Lock()
|
||||
defer state.Unlock()
|
||||
|
||||
os.Chdir(currpath)
|
||||
|
||||
cmdName := "go"
|
||||
if config.Conf.Gopm.Enable {
|
||||
cmdName = "gopm"
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
stderr bytes.Buffer
|
||||
stdout bytes.Buffer
|
||||
)
|
||||
// For applications use full import path like "github.com/.../.."
|
||||
// are able to use "go install" to reduce build time.
|
||||
if config.Conf.GoInstall {
|
||||
icmd := exec.Command(cmdName, "install", "-v")
|
||||
icmd.Stdout = os.Stdout
|
||||
icmd.Stderr = os.Stderr
|
||||
icmd.Env = append(os.Environ(), "GOGC=off")
|
||||
icmd.Run()
|
||||
}
|
||||
if config.Conf.Gopm.Install {
|
||||
icmd := exec.Command("go", "list", "./...")
|
||||
icmd.Stdout = &stdout
|
||||
icmd.Env = append(os.Environ(), "GOGC=off")
|
||||
err = icmd.Run()
|
||||
if err == nil {
|
||||
list := strings.Split(stdout.String(), "\n")[1:]
|
||||
for _, pkg := range list {
|
||||
if len(pkg) == 0 {
|
||||
continue
|
||||
}
|
||||
icmd = exec.Command(cmdName, "install", pkg)
|
||||
icmd.Stdout = os.Stdout
|
||||
icmd.Stderr = os.Stderr
|
||||
icmd.Env = append(os.Environ(), "GOGC=off")
|
||||
err = icmd.Run()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isgenerate {
|
||||
beeLogger.Log.Info("Generating the docs...")
|
||||
icmd := exec.Command("bee", "generate", "docs")
|
||||
icmd.Env = append(os.Environ(), "GOGC=off")
|
||||
err = icmd.Run()
|
||||
if err != nil {
|
||||
beeLogger.Log.Errorf("Failed to generate the docs.")
|
||||
return
|
||||
}
|
||||
beeLogger.Log.Success("Docs generated!")
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
appName := appname
|
||||
if runtime.GOOS == "windows" {
|
||||
appName += ".exe"
|
||||
}
|
||||
|
||||
args := []string{"build"}
|
||||
args = append(args, "-o", appName)
|
||||
if buildTags != "" {
|
||||
args = append(args, "-tags", buildTags)
|
||||
}
|
||||
args = append(args, files...)
|
||||
|
||||
bcmd := exec.Command(cmdName, args...)
|
||||
bcmd.Env = append(os.Environ(), "GOGC=off")
|
||||
bcmd.Stderr = &stderr
|
||||
err = bcmd.Run()
|
||||
if err != nil {
|
||||
beeLogger.Log.Errorf("Failed to build the application: %s", stderr.String())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
beeLogger.Log.Success("Built Successfully!")
|
||||
Restart(appname)
|
||||
}
|
||||
|
||||
// Kill kills the running command process
|
||||
func Kill() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
beeLogger.Log.Infof("Kill recover: %s", e)
|
||||
}
|
||||
}()
|
||||
if cmd != nil && cmd.Process != nil {
|
||||
err := cmd.Process.Kill()
|
||||
if err != nil {
|
||||
beeLogger.Log.Errorf("Error while killing cmd process: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restart kills the running command process and starts it again
|
||||
func Restart(appname string) {
|
||||
beeLogger.Log.Debugf("Kill running process", utils.FILE(), utils.LINE())
|
||||
Kill()
|
||||
go Start(appname)
|
||||
}
|
||||
|
||||
// Start starts the command process
|
||||
func Start(appname string) {
|
||||
beeLogger.Log.Infof("Restarting '%s'...", appname)
|
||||
if !strings.Contains(appname, "./") {
|
||||
appname = "./" + appname
|
||||
}
|
||||
|
||||
cmd = exec.Command(appname)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Args = append([]string{appname}, config.Conf.CmdArgs...)
|
||||
cmd.Env = append(os.Environ(), config.Conf.Envs...)
|
||||
|
||||
go cmd.Run()
|
||||
beeLogger.Log.Successf("'%s' is running...", appname)
|
||||
started <- true
|
||||
}
|
||||
|
||||
func ifStaticFile(filename string) bool {
|
||||
for _, s := range watchExtsStatic {
|
||||
if strings.HasSuffix(filename, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// shouldIgnoreFile ignores filenames generated by Emacs, Vim or SublimeText.
|
||||
// It returns true if the file should be ignored, false otherwise.
|
||||
func shouldIgnoreFile(filename string) bool {
|
||||
for _, regex := range ignoredFilesRegExps {
|
||||
r, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("Could not compile regular expression: %s", err)
|
||||
}
|
||||
if r.MatchString(filename) {
|
||||
return true
|
||||
}
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// shouldWatchFileWithExtension returns true if the name of the file
|
||||
// hash a suffix that should be watched.
|
||||
func shouldWatchFileWithExtension(name string) bool {
|
||||
for _, s := range watchExts {
|
||||
if strings.HasSuffix(name, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
72
cmd/commands/version/banner.go
Normal file
72
cmd/commands/version/banner.go
Normal file
@ -0,0 +1,72 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"text/template"
|
||||
|
||||
"time"
|
||||
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
)
|
||||
|
||||
// RuntimeInfo holds information about the current runtime.
|
||||
type RuntimeInfo struct {
|
||||
GoVersion string
|
||||
GOOS string
|
||||
GOARCH string
|
||||
NumCPU int
|
||||
GOPATH string
|
||||
GOROOT string
|
||||
Compiler string
|
||||
BeeVersion string
|
||||
BeegoVersion string
|
||||
}
|
||||
|
||||
// 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 {
|
||||
beeLogger.Log.Fatal("The input is nil")
|
||||
}
|
||||
|
||||
banner, err := ioutil.ReadAll(in)
|
||||
if err != nil {
|
||||
beeLogger.Log.Fatalf("Error while trying to read the banner: %s", err)
|
||||
}
|
||||
|
||||
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 {
|
||||
beeLogger.Log.Fatalf("Cannot parse the banner template: %s", err)
|
||||
}
|
||||
|
||||
err = t.Execute(out, RuntimeInfo{
|
||||
GetGoVersion(),
|
||||
runtime.GOOS,
|
||||
runtime.GOARCH,
|
||||
runtime.NumCPU(),
|
||||
os.Getenv("GOPATH"),
|
||||
runtime.GOROOT(),
|
||||
runtime.Compiler,
|
||||
version,
|
||||
GetBeegoVersion(),
|
||||
})
|
||||
if err != nil {
|
||||
beeLogger.Log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Now returns the current local time in the specified layout
|
||||
func Now(layout string) string {
|
||||
return time.Now().Format(layout)
|
||||
}
|
175
cmd/commands/version/version.go
Normal file
175
cmd/commands/version/version.go
Normal file
@ -0,0 +1,175 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
path "path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/bee/cmd/commands"
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
"github.com/beego/bee/logger/colors"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
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 = `______
|
||||
| ___ \
|
||||
| |_/ / ___ ___
|
||||
| ___ \ / _ \ / _ \
|
||||
| |_/ /| __/| __/
|
||||
\____/ \___| \___| v{{ .BeeVersion }}
|
||||
`
|
||||
|
||||
var CmdVersion = &commands.Command{
|
||||
UsageLine: "version",
|
||||
Short: "Prints the current Bee version",
|
||||
Long: `
|
||||
Prints the current Bee, Beego and Go version alongside the platform information.
|
||||
`,
|
||||
Run: versionCmd,
|
||||
}
|
||||
var outputFormat string
|
||||
|
||||
const version = "1.8.0"
|
||||
|
||||
func init() {
|
||||
fs := flag.NewFlagSet("version", flag.ContinueOnError)
|
||||
fs.StringVar(&outputFormat, "o", "", "Set the output format. Either json or yaml.")
|
||||
CmdVersion.Flag = *fs
|
||||
commands.AvailableCommands = append(commands.AvailableCommands, CmdVersion)
|
||||
}
|
||||
|
||||
func versionCmd(cmd *commands.Command, args []string) int {
|
||||
|
||||
cmd.Flag.Parse(args)
|
||||
stdout := cmd.Out()
|
||||
|
||||
if outputFormat != "" {
|
||||
runtimeInfo := RuntimeInfo{
|
||||
GetGoVersion(),
|
||||
runtime.GOOS,
|
||||
runtime.GOARCH,
|
||||
runtime.NumCPU(),
|
||||
os.Getenv("GOPATH"),
|
||||
runtime.GOROOT(),
|
||||
runtime.Compiler,
|
||||
version,
|
||||
GetBeegoVersion(),
|
||||
}
|
||||
switch outputFormat {
|
||||
case "json":
|
||||
{
|
||||
b, err := json.MarshalIndent(runtimeInfo, "", " ")
|
||||
if err != nil {
|
||||
beeLogger.Log.Error(err.Error())
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
return 0
|
||||
}
|
||||
case "yaml":
|
||||
{
|
||||
b, err := yaml.Marshal(&runtimeInfo)
|
||||
if err != nil {
|
||||
beeLogger.Log.Error(err.Error())
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
coloredBanner := fmt.Sprintf(verboseVersionBanner, "\x1b[35m", "\x1b[1m",
|
||||
"\x1b[0m", "\x1b[32m", "\x1b[1m", "\x1b[0m")
|
||||
InitBanner(stdout, bytes.NewBufferString(coloredBanner))
|
||||
return 0
|
||||
}
|
||||
|
||||
// ShowShortVersionBanner prints the short version banner.
|
||||
func ShowShortVersionBanner() {
|
||||
output := colors.NewColorWriter(os.Stdout)
|
||||
InitBanner(output, bytes.NewBufferString(colors.MagentaBold(shortVersionBanner)))
|
||||
}
|
||||
|
||||
func GetBeegoVersion() string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
re, err := regexp.Compile(`VERSION = "([0-9.]+)"`)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if gopath == "" {
|
||||
err = fmt.Errorf("You need to set GOPATH environment variable")
|
||||
return ""
|
||||
}
|
||||
wgopath := path.SplitList(gopath)
|
||||
for _, wg := range wgopath {
|
||||
wg, _ = path.EvalSymlinks(path.Join(wg, "src", "github.com", "astaxie", "beego"))
|
||||
filename := path.Join(wg, "beego.go")
|
||||
_, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
beeLogger.Log.Error("Error while getting stats of 'beego.go'")
|
||||
}
|
||||
fd, err := os.Open(filename)
|
||||
if err != nil {
|
||||
beeLogger.Log.Error("Error while reading 'beego.go'")
|
||||
continue
|
||||
}
|
||||
reader := bufio.NewReader(fd)
|
||||
for {
|
||||
byteLine, _, er := reader.ReadLine()
|
||||
if er != nil && er != io.EOF {
|
||||
return ""
|
||||
}
|
||||
if er == io.EOF {
|
||||
break
|
||||
}
|
||||
line := string(byteLine)
|
||||
s := re.FindStringSubmatch(line)
|
||||
if len(s) >= 2 {
|
||||
return s[1]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return "Beego is not installed. Please do consider installing it first: https://github.com/astaxie/beego"
|
||||
}
|
||||
|
||||
func GetGoVersion() string {
|
||||
var (
|
||||
cmdOut []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if cmdOut, err = exec.Command("go", "version").Output(); err != nil {
|
||||
beeLogger.Log.Fatalf("There was an error running 'go version' command: %s", err)
|
||||
}
|
||||
return strings.Split(string(cmdOut), " ")[2]
|
||||
}
|
Reference in New Issue
Block a user