1
0
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:
Sergey Lanzman
2017-03-07 01:58:53 +02:00
parent 74baba4f63
commit c538bfbc8f
47 changed files with 1838 additions and 2467 deletions

102
cmd/bee.go Normal file
View 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
View 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
View 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
View 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
View 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
}

View 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.")
}

View 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")
}
}

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

View 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

File diff suppressed because one or more lines are too long

577
cmd/commands/pack/pack.go Normal file
View 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
View 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
View 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
View 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
View 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
}

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

View 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]
}