1
0
mirror of https://github.com/beego/bee.git synced 2024-11-23 01:30:55 +00:00

Merge pull request #1 from beego/master

pull
This commit is contained in:
Daniel 2016-11-20 09:42:47 +08:00 committed by GitHub
commit d640b584a1
35 changed files with 2791 additions and 1165 deletions

2
.gitignore vendored
View File

@ -6,6 +6,7 @@
# Folders # Folders
_obj _obj
_test _test
.idea
# Architecture specific extensions/prefixes # Architecture specific extensions/prefixes
*.[568vq] *.[568vq]
@ -27,3 +28,4 @@ _testmain.go
.DS_Store .DS_Store
bee bee
*.exe~ *.exe~
.goxc.local.json

14
.goxc.json Normal file
View File

@ -0,0 +1,14 @@
{
"Tasks": [
"default",
"publish-github"
],
"PackageVersion": "0.0.1",
"TaskSettings": {
"publish-github": {
"owner": "beego",
"repository": "bee"
}
},
"ConfigVersion": "0.9"
}

6
.travis.yml Normal file
View File

@ -0,0 +1,6 @@
language: go
go:
- 1.5.3
- 1.4.3
- 1.3.3

15
Beefile Normal file
View File

@ -0,0 +1,15 @@
version: 0
gopm:
enable: false
install: false
go_install: false
watch_ext: []
dir_structure:
watch_all: false
controllers: ""
models: ""
others: []
cmd_args: []
envs: []
database:
driver: "mysql"

20
Makefile Normal file
View File

@ -0,0 +1,20 @@
.PHONY: all test clean build install
GOFLAGS ?= $(GOFLAGS:)
all: install test
build:
go build $(GOFLAGS) ./...
install:
go get $(GOFLAGS) ./...
test: install
go test $(GOFLAGS) ./...
bench: install
go test -run=NONE -bench=. $(GOFLAGS) ./...
clean:
go clean $(GOFLAGS) -i ./...

519
README.md
View File

@ -1,18 +1,17 @@
bee bee
=== ===
[![Build Status](https://drone.io/github.com/beego/bee/status.png)](https://drone.io/github.com/beego/bee/latest) Bee is a command-line tool facilitating development of Beego-based application.
Bee is a command line tool facilitating development with beego framework. [![Build Status](https://drone.io/github.com/beego/bee/status.png)](https://drone.io/github.com/beego/bee/latest)
## Requirements ## Requirements
- Go version >= 1.1. - Go version >= 1.3.
## Installation ## Installation
Begin by installing `bee` using `go get` command. To install `bee` use the `go get` command:
```bash ```bash
go get github.com/beego/bee go get github.com/beego/bee
@ -21,7 +20,7 @@ go get github.com/beego/bee
Then you can add `bee` binary to PATH environment variable in your `~/.bashrc` or `~/.bash_profile` file: Then you can add `bee` binary to PATH environment variable in your `~/.bashrc` or `~/.bash_profile` file:
```bash ```bash
export PATH=$PATH:<your_main_gopath>/bin/bee export PATH=$PATH:<your_main_gopath>/bin
``` ```
> If you already have `bee` installed, updating `bee` is simple: > If you already have `bee` installed, updating `bee` is simple:
@ -32,242 +31,225 @@ go get -u github.com/beego/bee
## Basic commands ## Basic commands
Bee provides a variety of commands which can be helpful at various stage of development. The top level commands include: Bee provides a variety of commands which can be helpful at various stages of development. The top level commands include:
```base
new create an application base on beego framework
run run the app which can hot compile
pack compress an beego project
api create an api application base on beego framework
bale packs non-Go files to Go source files
version show the bee & beego version
generate source code generator
migrate run database migrations
hprose create an rpc application use hprose base on beego framework
``` ```
## bee version new Create a Beego application
run Run the app and start a Web server for development
pack Compress a beego project into a single file
api Create an API beego application
hprose Create an rpc application use hprose base on beego framework
bale Packs non-Go files to Go source files
version Prints the current Bee version
generate Source code generator
migrate Run database migrations
fix Fix the Beego application to make it compatible with Beego 1.6
```
### bee version
The first command is the easiest: displaying which version of `bee`, `beego` and `go` is installed on your machine: To display the current version of `bee`, `beego` and `go` installed on your machine:
```bash ```bash
$ bee version $ bee version
bee :1.2.2 ______
beego :1.4.0 | ___ \
Go :go version go1.2.1 linux/amd64 | |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v1.5.0
├── Beego : 1.7.0
├── GoVersion : go1.6.2
├── GOOS : windows
├── GOARCH : amd64
├── NumCPU : 4
├── GOPATH : C:\Users\beeuser\go
├── GOROOT : C:\go
├── Compiler : gc
└── Date : Monday, 22 Aug 2016
``` ```
## bee new ### bee new
Creating a new beego web application is no big deal, too. To create a new Beego web application:
```bash ```bash
$ bee new myapp $ bee new my-web-app
[INFO] Creating application... ______
/home/zheng/gopath/src/myapp/ | ___ \
/home/zheng/gopath/src/myapp/conf/ | |_/ / ___ ___
/home/zheng/gopath/src/myapp/controllers/ | ___ \ / _ \ / _ \
/home/zheng/gopath/src/myapp/models/ | |_/ /| __/| __/
/home/zheng/gopath/src/myapp/routers/ \____/ \___| \___| v1.5.0
/home/zheng/gopath/src/myapp/tests/ 2016/08/22 14:53:45 [INFO] Creating application...
/home/zheng/gopath/src/myapp/static/ create C:\Users\beeuser\go\src\github.com\user\my-web-app\
/home/zheng/gopath/src/myapp/static/js/ create C:\Users\beeuser\go\src\github.com\user\my-web-app\conf\
/home/zheng/gopath/src/myapp/static/css/ create C:\Users\beeuser\go\src\github.com\user\my-web-app\controllers\
/home/zheng/gopath/src/myapp/static/img/ create C:\Users\beeuser\go\src\github.com\user\my-web-app\models\
/home/zheng/gopath/src/myapp/views/ create C:\Users\beeuser\go\src\github.com\user\my-web-app\routers\
/home/zheng/gopath/src/myapp/conf/app.conf create C:\Users\beeuser\go\src\github.com\user\my-web-app\tests\
/home/zheng/gopath/src/myapp/controllers/default.go create C:\Users\beeuser\go\src\github.com\user\my-web-app\static\
/home/zheng/gopath/src/myapp/views/index.tpl create C:\Users\beeuser\go\src\github.com\user\my-web-app\static\js\
/home/zheng/gopath/src/myapp/routers/router.go create C:\Users\beeuser\go\src\github.com\user\my-web-app\static\css\
/home/zheng/gopath/src/myapp/tests/default_test.go create C:\Users\beeuser\go\src\github.com\user\my-web-app\static\img\
/home/zheng/gopath/src/myapp/main.go create C:\Users\beeuser\go\src\github.com\user\my-web-app\views\
2014/08/29 15:45:47 [SUCC] New application successfully created! create C:\Users\beeuser\go\src\github.com\user\my-web-app\conf\app.conf
create C:\Users\beeuser\go\src\github.com\user\my-web-app\controllers\default.go
create C:\Users\beeuser\go\src\github.com\user\my-web-app\views\index.tpl
create C:\Users\beeuser\go\src\github.com\user\my-web-app\routers\router.go
create C:\Users\beeuser\go\src\github.com\user\my-web-app\tests\default_test.go
create C:\Users\beeuser\go\src\github.com\user\my-web-app\main.go
2016/08/22 14:53:45 [SUCC] New application successfully created!
``` ```
## bee run For more information on the usage, run `bee help new`.
To run the application we just created, navigate to the application folder and execute `bee run`. ### bee run
To run the application we just created, you can navigate to the application folder and execute:
```bash ```bash
$ cd myapp $ cd my-web-app && bee run
$ bee run
``` ```
## bee pack Or from anywhere in your machine:
```
$ bee run github.com/user/my-web-app
```
For more information on the usage, run `bee help run`.
### bee pack
To compress a Beego application into a single deployable file:
```bash ```bash
usage: bee pack $ bee pack
______
compress an beego project | ___ \
| |_/ / ___ ___
-p app path. default is current path | ___ \ / _ \ / _ \
-b build specify platform app. default true | |_/ /| __/| __/
-ba additional args of go build \____/ \___| \___| v1.5.0
-be=[] additional ENV Variables of go build. eg: GOARCH=arm 2016/08/22 15:11:01 Packaging application: C:\Users\beeuser\go\src\github.com\user\my-web-app
-o compressed file output dir. default use current path 2016/08/22 15:11:01 Building application...
-f="" format. [ tar.gz / zip ]. default tar.gz 2016/08/22 15:11:01 Env: GOOS=windows GOARCH=amd64
-exp="" relpath exclude prefix. default: . 2016/08/22 15:11:08 Build successful
-exs="" relpath exclude suffix. default: .go:.DS_Store:.tmp 2016/08/22 15:11:08 Excluding relpath prefix: .
all path use : as separator 2016/08/22 15:11:08 Excluding relpath suffix: .go:.DS_Store:.tmp
-exr=[] file/directory name exclude by Regexp. default: ^. 2016/08/22 15:11:10 Writing to output: `C:\Users\beeuser\go\src\github.com\user\my-web-app\my-web-app.tar.gz`
-fs=false follow symlink. default false
-ss=false skip symlink. default false
default embed symlink into compressed file
-v=false verbose
``` ```
## bee api For more information on the usage, run `bee help pack`.
### bee api
To create a Beego API application:
```bash ```bash
usage: bee api [appname] $ bee api my-api
______
create an api application base on beego framework | ___ \
| |_/ / ___ ___
bee api [appname] [-tables=""] [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"] | ___ \ / _ \ / _ \
-tables: a list of table names separated by ',', default is empty, indicating all tables | |_/ /| __/| __/
-driver: [mysql | postgres | sqlite], the default is mysql \____/ \___| \___| v1.5.0
-conn: the connection string used by the driver, the default is '' 2016/08/22 15:14:10 [INFO] Creating API...
e.g. for mysql: root:@tcp(127.0.0.1:3306)/test create C:\Users\beeuser\go\src\github.com\user\my-api
e.g. for postgres: postgres://postgres:postgres@127.0.0.1:5432/postgres create C:\Users\beeuser\go\src\github.com\user\my-api\conf
create C:\Users\beeuser\go\src\github.com\user\my-api\controllers
if conn is empty will create a example api application. otherwise generate api application based on an existing database. create C:\Users\beeuser\go\src\github.com\user\my-api\tests
create C:\Users\beeuser\go\src\github.com\user\my-api\conf\app.conf
In the current path, will create a folder named [appname] create C:\Users\beeuser\go\src\github.com\user\my-api\models
create C:\Users\beeuser\go\src\github.com\user\my-api\routers\
In the appname folder has the follow struct: create C:\Users\beeuser\go\src\github.com\user\my-api\controllers\object.go
create C:\Users\beeuser\go\src\github.com\user\my-api\controllers\user.go
├── conf create C:\Users\beeuser\go\src\github.com\user\my-api\tests\default_test.go
│ └── app.conf create C:\Users\beeuser\go\src\github.com\user\my-api\routers\router.go
├── controllers create C:\Users\beeuser\go\src\github.com\user\my-api\models\object.go
│ └── object.go create C:\Users\beeuser\go\src\github.com\user\my-api\models\user.go
│ └── user.go create C:\Users\beeuser\go\src\github.com\user\my-api\main.go
├── routers 2016/08/22 15:14:10 [SUCC] New API successfully created!
│ └── router.go
├── tests
│ └── default_test.go
├── main.go
└── models
└── object.go
└── user.go
``` ```
## bee hprose For more information on the usage, run `bee help api`.
### bee hprose
To create an Hprose RPC application based on Beego:
```bash ```bash
usage: bee hprose [appname] $ bee hprose my-rpc-app
______
create an rpc application use hprose base on beego framework | ___ \
| |_/ / ___ ___
bee hprose [appname] [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test] | ___ \ / _ \ / _ \
-tables: a list of table names separated by ',', default is empty, indicating all tables | |_/ /| __/| __/
-driver: [mysql | postgres | sqlite], the default is mysql \____/ \___| \___| v1.5.0
-conn: the connection string used by the driver, the default is '' 2016/08/22 16:09:13 [INFO] Creating Hprose application...
e.g. for mysql: root:@tcp(127.0.0.1:3306)/test create C:\Users\beeuser\go\src\github.com\user\my-rpc-app
e.g. for postgres: postgres://postgres:postgres@127.0.0.1:5432/postgres create C:\Users\beeuser\go\src\github.com\user\my-rpc-app\conf
create C:\Users\beeuser\go\src\github.com\user\my-rpc-app\conf\app.conf
if conn is empty will create a example rpc application. otherwise generate rpc application use hprose based on an existing database. create C:\Users\beeuser\go\src\github.com\user\my-rpc-app\models
create C:\Users\beeuser\go\src\github.com\user\my-rpc-app\models\object.go
In the current path, will create a folder named [appname] create C:\Users\beeuser\go\src\github.com\user\my-rpc-app\models\user.go
create C:\Users\beeuser\go\src\github.com\user\my-rpc-app\main.go
In the appname folder has the follow struct: 2016/08/22 16:09:13 [SUCC] New Hprose application successfully created!
├── conf
│ └── app.conf
├── main.go
└── models
└── object.go
└── user.go
``` ```
## bee bale For more information on the usage, run `bee help hprose`.
### bee bale
To pack all the static files into Go source files:
```bash ```bash
usage: bee bale $ bee bale
______
bale packs non-Go files to Go source files and | ___ \
| |_/ / ___ ___
auto-generate unpack function to main package then run it | ___ \ / _ \ / _ \
| |_/ /| __/| __/
during the runtime. \____/ \___| \___| v1.5.0
2016/08/22 16:37:24 [INFO] Detected bee.json
This is mainly used for zealots who are requiring 100% Go code. 2016/08/22 16:37:24 [INFO] Packaging directory(static/js)
2016/08/22 16:37:24 [INFO] Packaging directory(static/css)
2016/08/22 16:37:24 [SUCC] Baled resources successfully!
``` ```
## bee migrate For more information on the usage, run `bee help bale`.
### bee migrate
For database migrations, use `bee migrate`.
For more information on the usage, run `bee help migrate`.
### bee generate
Bee also comes with a source code generator which speeds up the development.
For example, to generate a new controller named `hello`:
```bash ```bash
usage: bee migrate [Command] $ bee generate controller hello
______
bee migrate [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"] | ___ \
run all outstanding migrations | |_/ / ___ ___
-driver: [mysql | postgresql | sqlite], the default is mysql | ___ \ / _ \ / _ \
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test | |_/ /| __/| __/
\____/ \___| \___| v1.5.0
bee migrate rollback [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"] 2016/08/22 16:55:30 [INFO] Using 'Hello' as controller name
rollback the last migration operation 2016/08/22 16:55:30 [INFO] Using 'controllers' as package name
-driver: [mysql | postgresql | sqlite], the default is mysql create C:\Users\beeuser\go\src\github.com\user\my-web-app/controllers/hello.go
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test 2016/08/22 16:55:30 [SUCC] Controller successfully generated!
bee migrate reset [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"]
rollback all migrations
-driver: [mysql | postgresql | sqlite], the default is mysql
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test
bee migrate refresh [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"]
rollback all migrations and run them all again
-driver: [mysql | postgresql | sqlite], the default is mysql
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test
```
## bee generate
Bee also comes with a souce code generator which speeds up the development.
```bash
usage: bee generate [Command]
bee generate scaffold [scaffoldname] [-fields=""] [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"]
The generate scaffold command will do a number of things for you.
-fields: a list of table fields. Format: field:type, ...
-driver: [mysql | postgres | sqlite], the default is mysql
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test
example: bee generate scaffold post -fields="title:string,body:text"
bee generate model [modelname] [-fields=""]
generate RESTFul model based on fields
-fields: a list of table fields. Format: field:type, ...
bee generate controller [controllerfile]
generate RESTFul controllers
bee generate view [viewpath]
generate CRUD view in viewpath
bee generate migration [migrationfile] [-fields=""]
generate migration file for making database schema update
-fields: a list of table fields. Format: field:type, ...
bee generate docs
generate swagger doc file
bee generate test [routerfile]
generate testcase
bee generate appcode [-tables=""] [-driver="mysql"] [-conn="root:@tcp(127.0.0.1:3306)/test"] [-level=3]
generate appcode based on an existing database
-tables: a list of table names separated by ',', default is empty, indicating all tables
-driver: [mysql | postgres | sqlite], the default is mysql
-conn: the connection string used by the driver.
default for mysql: root:@tcp(127.0.0.1:3306)/test
default for postgres: postgres://postgres:postgres@127.0.0.1:5432/postgres
-level: [1 | 2 | 3], 1 = models; 2 = models,controllers; 3 = models,controllers,router
``` ```
For more information on the usage, run `bee help generate`.
## Shortcuts ## Shortcuts
Because you'll likely type these generator commands over and over, it makes sense to create aliases. Because you'll likely type these generator commands over and over, it makes sense to create aliases:
```bash ```bash
# Generator Stuff # Generator Stuff
@ -278,32 +260,135 @@ alias g:v="bee generate view"
alias g:mi="bee generate migration" alias g:mi="bee generate migration"
``` ```
These can be stored in, for example, your `~/.bash_profile` or `~/.bashrc` files. These can be stored , for example, in your `~/.bash_profile` or `~/.bashrc` files.
## Help ## Help
If you happend to forget the usage of a command, you can always find the usage information by `bee help <command>`. To print more information on the usage of a particular command, use `bee help <command>`.
For instance, to get more information about the `run` command: For instance, to get more information about the `run` command:
```bash ```bash
$ bee help run $ bee help run
usage: bee run [appname] [watchall] [-main=*.go] [-downdoc=true] [-gendoc=true] usage: bee run [appname] [watchall] [-main=*.go] [-downdoc=true] [-gendoc=true] [-vendor=true] [-e=folderToExclude] [-tags=goBuildTags]
start the appname throw exec.Command Run command will supervise the file system of the beego project using inotify,
it will recompile and restart the app after any modifications.
```
then start a inotify watch for current dir ## Contributing
Bug reports, feature requests and pull requests are always welcome.
when the file has changed bee will auto go build and restart the app We work on two branches: `master` for stable, released code and `develop`, a development branch.
It might be important to distinguish them when you are reading the commit history searching for a feature or a bugfix,
or when you are unsure of where to base your work from when contributing.
file changed ### Found a bug?
|
check if it's go file Please [submit an issue][new-issue] on GitHub and we will follow up.
| Even better, we would appreciate a [Pull Request][new-pr] with a fix for it!
yes no
| | - If the bug was found in a release, it is best to base your work on `master` and submit your PR against it.
go build do nothing - If the bug was found on `develop` (the development branch), base your work on `develop` and submit your PR against it.
|
restart app Please follow the [Pull Request Guidelines][new-pr].
### Want a feature?
Feel free to request a feature by [submitting an issue][new-issue] on GitHub and open the discussion.
If you'd like to implement a new feature, please consider opening an issue first to talk about it.
It may be that somebody is already working on it, or that there are particular issues that you should be aware of
before implementing the change. If you are about to open a Pull Request, please make sure to follow the [submissions guidelines][new-pr].
## Submission Guidelines
### Submitting an issue
Before you submit an issue, search the archive, maybe you will find that a similar one already exists.
If you are submitting an issue for a bug, please include the following:
- An overview of the issue
- Your use case (why is this a bug for you?)
- The version of `bee` you are running (include the output of `bee version`)
- Steps to reproduce the issue
- Eventually, logs from your application.
- Ideally, a suggested fix
The more information you give us, the more able to help we will be!
### Submitting a Pull Request
- First of all, make sure to base your work on the `develop` branch (the development branch):
```
# a bugfix branch for develop would be prefixed by fix/
# a bugfix branch for master would be prefixed by hotfix/
$ git checkout -b feature/my-feature develop
```
- Please create commits containing **related changes**. For example, two different bugfixes should produce two separate commits.
A feature should be made of commits splitted by **logical chunks** (no half-done changes). Use your best judgement as to
how many commits your changes require.
- Write insightful and descriptive commit messages. It lets us and future contributors quickly understand your changes
without having to read your changes. Please provide a summary in the first line (50-72 characters) and eventually,
go to greater lengths in your message's body. A good example can be found in [Angular commit message format](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit-message-format).
- Please **include the appropriate test cases** for your patch.
- Make sure all tests pass before submitting your changes.
- Rebase your commits. It may be that new commits have been introduced on `develop`.
Rebasing will update your branch with the most recent code and make your changes easier to review:
```
$ git fetch
$ git rebase origin/develop
```
- Push your changes:
```
$ git push origin -u feature/my-feature
```
- Open a pull request against the `develop` branch.
- If we suggest changes:
- Please make the required updates (after discussion if any)
- Only create new commits if it makes sense. Generally, you will want to amend your latest commit or rebase your branch after the new changes:
```
$ git rebase -i develop
# choose which commits to edit and perform the updates
```
- Re-run the tests
- Force push to your branch:
```
$ git push origin feature/my-feature -f
```
[new-issue]: #submitting-an-issue
[new-pr]: #submitting-a-pull-request
## Licence
```text
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.
``` ```

202
apiapp.go
View File

@ -69,16 +69,15 @@ EnableDocs = true
var apiMaingo = `package main var apiMaingo = `package main
import ( import (
_ "{{.Appname}}/docs"
_ "{{.Appname}}/routers" _ "{{.Appname}}/routers"
"github.com/astaxie/beego" "github.com/astaxie/beego"
) )
func main() { func main() {
if beego.RunMode == "dev" { if beego.BConfig.RunMode == "dev" {
beego.DirectoryIndex = true beego.BConfig.WebConfig.DirectoryIndex = true
beego.StaticDir["/swagger"] = "swagger" beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
} }
beego.Run() beego.Run()
} }
@ -87,7 +86,6 @@ func main() {
var apiMainconngo = `package main var apiMainconngo = `package main
import ( import (
_ "{{.Appname}}/docs"
_ "{{.Appname}}/routers" _ "{{.Appname}}/routers"
"github.com/astaxie/beego" "github.com/astaxie/beego"
@ -100,9 +98,9 @@ func init() {
} }
func main() { func main() {
if beego.RunMode == "dev" { if beego.BConfig.RunMode == "dev" {
beego.DirectoryIndex = true beego.BConfig.WebConfig.DirectoryIndex = true
beego.StaticDir["/swagger"] = "swagger" beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
} }
beego.Run() beego.Run()
} }
@ -298,7 +296,7 @@ type ObjectController struct {
beego.Controller beego.Controller
} }
// @Title create // @Title Create
// @Description create object // @Description create object
// @Param body body models.Object true "The object content" // @Param body body models.Object true "The object content"
// @Success 200 {string} models.Object.Id // @Success 200 {string} models.Object.Id
@ -309,7 +307,7 @@ func (o *ObjectController) Post() {
json.Unmarshal(o.Ctx.Input.RequestBody, &ob) json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
objectid := models.AddOne(ob) objectid := models.AddOne(ob)
o.Data["json"] = map[string]string{"ObjectId": objectid} o.Data["json"] = map[string]string{"ObjectId": objectid}
o.ServeJson() o.ServeJSON()
} }
// @Title Get // @Title Get
@ -319,16 +317,16 @@ func (o *ObjectController) Post() {
// @Failure 403 :objectId is empty // @Failure 403 :objectId is empty
// @router /:objectId [get] // @router /:objectId [get]
func (o *ObjectController) Get() { func (o *ObjectController) Get() {
objectId := o.Ctx.Input.Params[":objectId"] objectId := o.Ctx.Input.Param(":objectId")
if objectId != "" { if objectId != "" {
ob, err := models.GetOne(objectId) ob, err := models.GetOne(objectId)
if err != nil { if err != nil {
o.Data["json"] = err o.Data["json"] = err.Error()
} else { } else {
o.Data["json"] = ob o.Data["json"] = ob
} }
} }
o.ServeJson() o.ServeJSON()
} }
// @Title GetAll // @Title GetAll
@ -339,10 +337,10 @@ func (o *ObjectController) Get() {
func (o *ObjectController) GetAll() { func (o *ObjectController) GetAll() {
obs := models.GetAll() obs := models.GetAll()
o.Data["json"] = obs o.Data["json"] = obs
o.ServeJson() o.ServeJSON()
} }
// @Title update // @Title Update
// @Description update the object // @Description update the object
// @Param objectId path string true "The objectid you want to update" // @Param objectId path string true "The objectid you want to update"
// @Param body body models.Object true "The body" // @Param body body models.Object true "The body"
@ -350,30 +348,30 @@ func (o *ObjectController) GetAll() {
// @Failure 403 :objectId is empty // @Failure 403 :objectId is empty
// @router /:objectId [put] // @router /:objectId [put]
func (o *ObjectController) Put() { func (o *ObjectController) Put() {
objectId := o.Ctx.Input.Params[":objectId"] objectId := o.Ctx.Input.Param(":objectId")
var ob models.Object var ob models.Object
json.Unmarshal(o.Ctx.Input.RequestBody, &ob) json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
err := models.Update(objectId, ob.Score) err := models.Update(objectId, ob.Score)
if err != nil { if err != nil {
o.Data["json"] = err o.Data["json"] = err.Error()
} else { } else {
o.Data["json"] = "update success!" o.Data["json"] = "update success!"
} }
o.ServeJson() o.ServeJSON()
} }
// @Title delete // @Title Delete
// @Description delete the object // @Description delete the object
// @Param objectId path string true "The objectId you want to delete" // @Param objectId path string true "The objectId you want to delete"
// @Success 200 {string} delete success! // @Success 200 {string} delete success!
// @Failure 403 objectId is empty // @Failure 403 objectId is empty
// @router /:objectId [delete] // @router /:objectId [delete]
func (o *ObjectController) Delete() { func (o *ObjectController) Delete() {
objectId := o.Ctx.Input.Params[":objectId"] objectId := o.Ctx.Input.Param(":objectId")
models.Delete(objectId) models.Delete(objectId)
o.Data["json"] = "delete success!" o.Data["json"] = "delete success!"
o.ServeJson() o.ServeJSON()
} }
` `
@ -391,7 +389,7 @@ type UserController struct {
beego.Controller beego.Controller
} }
// @Title createUser // @Title CreateUser
// @Description create users // @Description create users
// @Param body body models.User true "body for user content" // @Param body body models.User true "body for user content"
// @Success 200 {int} models.User.Id // @Success 200 {int} models.User.Id
@ -402,17 +400,17 @@ func (u *UserController) Post() {
json.Unmarshal(u.Ctx.Input.RequestBody, &user) json.Unmarshal(u.Ctx.Input.RequestBody, &user)
uid := models.AddUser(user) uid := models.AddUser(user)
u.Data["json"] = map[string]string{"uid": uid} u.Data["json"] = map[string]string{"uid": uid}
u.ServeJson() u.ServeJSON()
} }
// @Title Get // @Title GetAll
// @Description get all Users // @Description get all Users
// @Success 200 {object} models.User // @Success 200 {object} models.User
// @router / [get] // @router / [get]
func (u *UserController) GetAll() { func (u *UserController) GetAll() {
users := models.GetAllUsers() users := models.GetAllUsers()
u.Data["json"] = users u.Data["json"] = users
u.ServeJson() u.ServeJSON()
} }
// @Title Get // @Title Get
@ -426,15 +424,15 @@ func (u *UserController) Get() {
if uid != "" { if uid != "" {
user, err := models.GetUser(uid) user, err := models.GetUser(uid)
if err != nil { if err != nil {
u.Data["json"] = err u.Data["json"] = err.Error()
} else { } else {
u.Data["json"] = user u.Data["json"] = user
} }
} }
u.ServeJson() u.ServeJSON()
} }
// @Title update // @Title Update
// @Description update the user // @Description update the user
// @Param uid path string true "The uid you want to update" // @Param uid path string true "The uid you want to update"
// @Param body body models.User true "body for user content" // @Param body body models.User true "body for user content"
@ -448,15 +446,15 @@ func (u *UserController) Put() {
json.Unmarshal(u.Ctx.Input.RequestBody, &user) json.Unmarshal(u.Ctx.Input.RequestBody, &user)
uu, err := models.UpdateUser(uid, &user) uu, err := models.UpdateUser(uid, &user)
if err != nil { if err != nil {
u.Data["json"] = err u.Data["json"] = err.Error()
} else { } else {
u.Data["json"] = uu u.Data["json"] = uu
} }
} }
u.ServeJson() u.ServeJSON()
} }
// @Title delete // @Title Delete
// @Description delete the user // @Description delete the user
// @Param uid path string true "The uid you want to delete" // @Param uid path string true "The uid you want to delete"
// @Success 200 {string} delete success! // @Success 200 {string} delete success!
@ -466,10 +464,10 @@ func (u *UserController) Delete() {
uid := u.GetString(":uid") uid := u.GetString(":uid")
models.DeleteUser(uid) models.DeleteUser(uid)
u.Data["json"] = "delete success!" u.Data["json"] = "delete success!"
u.ServeJson() u.ServeJSON()
} }
// @Title login // @Title Login
// @Description Logs user into the system // @Description Logs user into the system
// @Param username query string true "The username for login" // @Param username query string true "The username for login"
// @Param password query string true "The password for login" // @Param password query string true "The password for login"
@ -484,7 +482,7 @@ func (u *UserController) Login() {
} else { } else {
u.Data["json"] = "user not exist" u.Data["json"] = "user not exist"
} }
u.ServeJson() u.ServeJSON()
} }
// @Title logout // @Title logout
@ -493,7 +491,7 @@ func (u *UserController) Login() {
// @router /logout [get] // @router /logout [get]
func (u *UserController) Logout() { func (u *UserController) Logout() {
u.Data["json"] = "logout success" u.Data["json"] = "logout success"
u.ServeJson() u.ServeJSON()
} }
` `
@ -546,14 +544,19 @@ func init() {
} }
func createapi(cmd *Command, args []string) int { func createapi(cmd *Command, args []string) int {
curpath, _ := os.Getwd() ShowShortVersionBanner()
w := NewColorWriter(os.Stdout)
if len(args) < 1 { if len(args) < 1 {
ColorLog("[ERRO] Argument [appname] is missing\n") ColorLog("[ERRO] Argument [appname] is missing\n")
os.Exit(2) os.Exit(2)
} }
if len(args) > 1 { if len(args) > 1 {
cmd.Flag.Parse(args[1:]) cmd.Flag.Parse(args[1:])
} }
apppath, packpath, err := checkEnv(args[0]) apppath, packpath, err := checkEnv(args[0])
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -564,23 +567,23 @@ func createapi(cmd *Command, args []string) int {
} }
if conn == "" { if conn == "" {
} }
os.MkdirAll(apppath, 0755)
fmt.Println("create app folder:", apppath)
os.Mkdir(path.Join(apppath, "conf"), 0755)
fmt.Println("create conf:", path.Join(apppath, "conf"))
os.Mkdir(path.Join(apppath, "controllers"), 0755)
fmt.Println("create controllers:", path.Join(apppath, "controllers"))
os.Mkdir(path.Join(apppath, "docs"), 0755)
fmt.Println("create docs:", path.Join(apppath, "docs"))
os.Mkdir(path.Join(apppath, "tests"), 0755)
fmt.Println("create tests:", path.Join(apppath, "tests"))
fmt.Println("create conf app.conf:", path.Join(apppath, "conf", "app.conf")) ColorLog("[INFO] Creating API...\n")
writetofile(path.Join(apppath, "conf", "app.conf"),
strings.Replace(apiconf, "{{.Appname}}", args[0], -1)) os.MkdirAll(apppath, 0755)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath, "\x1b[0m")
os.Mkdir(path.Join(apppath, "conf"), 0755)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf"), "\x1b[0m")
os.Mkdir(path.Join(apppath, "controllers"), 0755)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers"), "\x1b[0m")
os.Mkdir(path.Join(apppath, "tests"), 0755)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests"), "\x1b[0m")
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
WriteToFile(path.Join(apppath, "conf", "app.conf"),
strings.Replace(apiconf, "{{.Appname}}", path.Base(args[0]), -1))
if conn != "" { if conn != "" {
fmt.Println("create main.go:", path.Join(apppath, "main.go")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
maingoContent := strings.Replace(apiMainconngo, "{{.Appname}}", packpath, -1) maingoContent := strings.Replace(apiMainconngo, "{{.Appname}}", packpath, -1)
maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(driver), -1) maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(driver), -1)
if driver == "mysql" { if driver == "mysql" {
@ -588,7 +591,7 @@ func createapi(cmd *Command, args []string) int {
} else if driver == "postgres" { } else if driver == "postgres" {
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1) maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1)
} }
writetofile(path.Join(apppath, "main.go"), WriteToFile(path.Join(apppath, "main.go"),
strings.Replace( strings.Replace(
maingoContent, maingoContent,
"{{.conn}}", "{{.conn}}",
@ -599,90 +602,73 @@ func createapi(cmd *Command, args []string) int {
ColorLog("[INFO] Using '%s' as 'driver'\n", driver) ColorLog("[INFO] Using '%s' as 'driver'\n", driver)
ColorLog("[INFO] Using '%s' as 'conn'\n", conn) ColorLog("[INFO] Using '%s' as 'conn'\n", conn)
ColorLog("[INFO] Using '%s' as 'tables'\n", tables) ColorLog("[INFO] Using '%s' as 'tables'\n", tables)
generateAppcode(string(driver), string(conn), "3", string(tables), path.Join(curpath, args[0])) generateAppcode(string(driver), string(conn), "3", string(tables), apppath)
} else { } else {
os.Mkdir(path.Join(apppath, "models"), 0755) os.Mkdir(path.Join(apppath, "models"), 0755)
fmt.Println("create models:", path.Join(apppath, "models")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models"), "\x1b[0m")
os.Mkdir(path.Join(apppath, "routers"), 0755) os.Mkdir(path.Join(apppath, "routers"), 0755)
fmt.Println(path.Join(apppath, "routers") + string(path.Separator)) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers")+string(path.Separator), "\x1b[0m")
fmt.Println("create controllers object.go:", path.Join(apppath, "controllers", "object.go")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "object.go"), "\x1b[0m")
writetofile(path.Join(apppath, "controllers", "object.go"), WriteToFile(path.Join(apppath, "controllers", "object.go"),
strings.Replace(apiControllers, "{{.Appname}}", packpath, -1)) strings.Replace(apiControllers, "{{.Appname}}", packpath, -1))
fmt.Println("create controllers user.go:", path.Join(apppath, "controllers", "user.go")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "user.go"), "\x1b[0m")
writetofile(path.Join(apppath, "controllers", "user.go"), WriteToFile(path.Join(apppath, "controllers", "user.go"),
strings.Replace(apiControllers2, "{{.Appname}}", packpath, -1)) strings.Replace(apiControllers2, "{{.Appname}}", packpath, -1))
fmt.Println("create tests default.go:", path.Join(apppath, "tests", "default_test.go")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests", "default_test.go"), "\x1b[0m")
writetofile(path.Join(apppath, "tests", "default_test.go"), WriteToFile(path.Join(apppath, "tests", "default_test.go"),
strings.Replace(apiTests, "{{.Appname}}", packpath, -1)) strings.Replace(apiTests, "{{.Appname}}", packpath, -1))
fmt.Println("create routers router.go:", path.Join(apppath, "routers", "router.go")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers", "router.go"), "\x1b[0m")
writetofile(path.Join(apppath, "routers", "router.go"), WriteToFile(path.Join(apppath, "routers", "router.go"),
strings.Replace(apirouter, "{{.Appname}}", packpath, -1)) strings.Replace(apirouter, "{{.Appname}}", packpath, -1))
fmt.Println("create models object.go:", path.Join(apppath, "models", "object.go")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "object.go"), "\x1b[0m")
writetofile(path.Join(apppath, "models", "object.go"), apiModels) WriteToFile(path.Join(apppath, "models", "object.go"), apiModels)
fmt.Println("create models user.go:", path.Join(apppath, "models", "user.go")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "user.go"), "\x1b[0m")
writetofile(path.Join(apppath, "models", "user.go"), apiModels2) WriteToFile(path.Join(apppath, "models", "user.go"), apiModels2)
fmt.Println("create docs doc.go:", path.Join(apppath, "docs", "doc.go")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
writetofile(path.Join(apppath, "docs", "doc.go"), "package docs") WriteToFile(path.Join(apppath, "main.go"),
fmt.Println("create main.go:", path.Join(apppath, "main.go"))
writetofile(path.Join(apppath, "main.go"),
strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1)) strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1))
} }
ColorLog("[SUCC] New API successfully created!\n")
return 0 return 0
} }
func checkEnv(appname string) (apppath, packpath string, err error) { func checkEnv(appname string) (apppath, packpath string, err error) {
curpath, err := os.Getwd() gps := GetGOPATHs()
if err != nil { if len(gps) == 0 {
return ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty")
os.Exit(2)
} }
currpath, _ := os.Getwd()
gopath := os.Getenv("GOPATH") currpath = path.Join(currpath, appname)
Debugf("gopath:%s", gopath) for _, gpath := range gps {
if gopath == "" { gsrcpath := path.Join(gpath, "src")
err = fmt.Errorf("you should set GOPATH in the env") if strings.HasPrefix(currpath, gsrcpath) {
return packpath = strings.Replace(currpath[len(gsrcpath)+1:], string(path.Separator), "/", -1)
} return currpath, packpath, nil
appsrcpath := ""
haspath := false
wgopath := path.SplitList(gopath)
for _, wg := range wgopath {
wg = path.Join(wg, "src")
if strings.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) {
haspath = true
appsrcpath = wg
break
}
wg, _ = path.EvalSymlinks(wg)
if strings.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) {
haspath = true
appsrcpath = wg
break
} }
} }
if !haspath { // In case of multiple paths in the GOPATH, by default
err = fmt.Errorf("can't create application outside of GOPATH `%s`\n"+ // we use the first path
"you first should `cd $GOPATH%ssrc` then use create\n", gopath, string(path.Separator)) gopath := gps[0]
return ColorLog("[%s]You current workdir is not a $GOPATH/src, bee will create the application in GOPATH: %s\n", WARN, gopath)
} Debugf("GOPATH: %s", gopath)
apppath = path.Join(curpath, appname)
gosrcpath := path.Join(gopath, "src")
apppath = path.Join(gosrcpath, appname)
if _, e := os.Stat(apppath); os.IsNotExist(e) == false { if _, e := os.Stat(apppath); os.IsNotExist(e) == false {
err = fmt.Errorf("path `%s` exists, can not create app without remove it\n", apppath) err = fmt.Errorf("Cannot create application without removing '%s' first.", apppath)
ColorLog("[ERRO] Path '%s' already exists\n", apppath)
return return
} }
packpath = strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/") packpath = strings.Join(strings.Split(apppath[len(gosrcpath)+1:], string(path.Separator)), "/")
return return
} }

16
bale.go
View File

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

73
banner.go Normal file
View File

@ -0,0 +1,73 @@
package main
import (
"io"
"io/ioutil"
"os"
"runtime"
"text/template"
"time"
)
type vars struct {
GoVersion string
GOOS string
GOARCH string
NumCPU int
GOPATH string
GOROOT string
Compiler string
BeeVersion string
BeegoVersion string
}
// Now returns the current local time in the specified layout
func Now(layout string) string {
return time.Now().Format(layout)
}
// InitBanner loads the banner and prints it to output
// All errors are ignored, the application will not
// print the banner in case of error.
func InitBanner(out io.Writer, in io.Reader) {
if in == nil {
ColorLog("[ERRO] The input is nil\n")
os.Exit(2)
}
banner, err := ioutil.ReadAll(in)
if err != nil {
ColorLog("[ERRO] Error trying to read the banner\n")
ColorLog("[HINT] %v\n", err)
os.Exit(2)
}
show(out, string(banner))
}
func show(out io.Writer, content string) {
t, err := template.New("banner").
Funcs(template.FuncMap{"Now": Now}).
Parse(content)
if err != nil {
ColorLog("[ERRO] Cannot parse the banner template\n")
ColorLog("[HINT] %v\n", err)
os.Exit(2)
}
err = t.Execute(out, vars{
runtime.Version(),
runtime.GOOS,
runtime.GOARCH,
runtime.NumCPU(),
os.Getenv("GOPATH"),
runtime.GOROOT(),
runtime.Compiler,
version,
getBeegoVersion(),
})
if err != nil {
panic(err)
}
}

3
bee.go
View File

@ -25,7 +25,7 @@ import (
"strings" "strings"
) )
const version = "1.3.0" const version = "1.5.2"
type Command struct { type Command struct {
// Run runs the command. // Run runs the command.
@ -85,6 +85,7 @@ var commands = []*Command{
cmdGenerate, cmdGenerate,
//cmdRundocs, //cmdRundocs,
cmdMigrate, cmdMigrate,
cmdFix,
} }
func main() { func main() {

View File

@ -251,7 +251,6 @@ func commentAnnotations(src string) []Annotation {
annotations = append(annotations, Annotation{Kind: CommentAnnotation, Pos: int16(p), End: int16(e)}) annotations = append(annotations, Annotation{Kind: CommentAnnotation, Pos: int16(p), End: int16(e)})
} }
} }
return nil
} }
type sliceWriter struct{ p *[]byte } type sliceWriter struct{ p *[]byte }

51
color.go Normal file
View File

@ -0,0 +1,51 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
import "io"
type outputMode int
// DiscardNonColorEscSeq supports the divided color escape sequence.
// But non-color escape sequence is not output.
// Please use the OutputNonColorEscSeq If you want to output a non-color
// escape sequences such as ncurses. However, it does not support the divided
// color escape sequence.
const (
_ outputMode = iota
DiscardNonColorEscSeq
OutputNonColorEscSeq
)
// NewColorWriter creates and initializes a new ansiColorWriter
// using io.Writer w as its initial contents.
// In the console of Windows, which change the foreground and background
// colors of the text by the escape sequence.
// In the console of other systems, which writes to w all text.
func NewColorWriter(w io.Writer) io.Writer {
return NewModeColorWriter(w, DiscardNonColorEscSeq)
}
// NewModeColorWriter create and initializes a new ansiColorWriter
// by specifying the outputMode.
func NewModeColorWriter(w io.Writer, mode outputMode) io.Writer {
if _, ok := w.(*colorWriter); !ok {
return &colorWriter{
w: w,
mode: mode,
}
}
return w
}

28
colorwriter.go Normal file
View File

@ -0,0 +1,28 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// +build !windows
package main
import "io"
type colorWriter struct {
w io.Writer
mode outputMode
}
func (cw *colorWriter) Write(p []byte) (int, error) {
return cw.w.Write(p)
}

427
colorwriter_windows.go Normal file
View File

@ -0,0 +1,427 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// +build windows
package main
import (
"bytes"
"io"
"strings"
"syscall"
"unsafe"
)
type csiState int
const (
outsideCsiCode csiState = iota
firstCsiCode
secondCsiCode
)
type parseResult int
const (
noConsole parseResult = iota
changedColor
unknown
)
type colorWriter struct {
w io.Writer
mode outputMode
state csiState
paramStartBuf bytes.Buffer
paramBuf bytes.Buffer
}
const (
firstCsiChar byte = '\x1b'
secondeCsiChar byte = '['
separatorChar byte = ';'
sgrCode byte = 'm'
)
const (
foregroundBlue = uint16(0x0001)
foregroundGreen = uint16(0x0002)
foregroundRed = uint16(0x0004)
foregroundIntensity = uint16(0x0008)
backgroundBlue = uint16(0x0010)
backgroundGreen = uint16(0x0020)
backgroundRed = uint16(0x0040)
backgroundIntensity = uint16(0x0080)
underscore = uint16(0x8000)
foregroundMask = foregroundBlue | foregroundGreen | foregroundRed | foregroundIntensity
backgroundMask = backgroundBlue | backgroundGreen | backgroundRed | backgroundIntensity
)
const (
ansiReset = "0"
ansiIntensityOn = "1"
ansiIntensityOff = "21"
ansiUnderlineOn = "4"
ansiUnderlineOff = "24"
ansiBlinkOn = "5"
ansiBlinkOff = "25"
ansiForegroundBlack = "30"
ansiForegroundRed = "31"
ansiForegroundGreen = "32"
ansiForegroundYellow = "33"
ansiForegroundBlue = "34"
ansiForegroundMagenta = "35"
ansiForegroundCyan = "36"
ansiForegroundWhite = "37"
ansiForegroundDefault = "39"
ansiBackgroundBlack = "40"
ansiBackgroundRed = "41"
ansiBackgroundGreen = "42"
ansiBackgroundYellow = "43"
ansiBackgroundBlue = "44"
ansiBackgroundMagenta = "45"
ansiBackgroundCyan = "46"
ansiBackgroundWhite = "47"
ansiBackgroundDefault = "49"
ansiLightForegroundGray = "90"
ansiLightForegroundRed = "91"
ansiLightForegroundGreen = "92"
ansiLightForegroundYellow = "93"
ansiLightForegroundBlue = "94"
ansiLightForegroundMagenta = "95"
ansiLightForegroundCyan = "96"
ansiLightForegroundWhite = "97"
ansiLightBackgroundGray = "100"
ansiLightBackgroundRed = "101"
ansiLightBackgroundGreen = "102"
ansiLightBackgroundYellow = "103"
ansiLightBackgroundBlue = "104"
ansiLightBackgroundMagenta = "105"
ansiLightBackgroundCyan = "106"
ansiLightBackgroundWhite = "107"
)
type drawType int
const (
foreground drawType = iota
background
)
type winColor struct {
code uint16
drawType drawType
}
var colorMap = map[string]winColor{
ansiForegroundBlack: {0, foreground},
ansiForegroundRed: {foregroundRed, foreground},
ansiForegroundGreen: {foregroundGreen, foreground},
ansiForegroundYellow: {foregroundRed | foregroundGreen, foreground},
ansiForegroundBlue: {foregroundBlue, foreground},
ansiForegroundMagenta: {foregroundRed | foregroundBlue, foreground},
ansiForegroundCyan: {foregroundGreen | foregroundBlue, foreground},
ansiForegroundWhite: {foregroundRed | foregroundGreen | foregroundBlue, foreground},
ansiForegroundDefault: {foregroundRed | foregroundGreen | foregroundBlue, foreground},
ansiBackgroundBlack: {0, background},
ansiBackgroundRed: {backgroundRed, background},
ansiBackgroundGreen: {backgroundGreen, background},
ansiBackgroundYellow: {backgroundRed | backgroundGreen, background},
ansiBackgroundBlue: {backgroundBlue, background},
ansiBackgroundMagenta: {backgroundRed | backgroundBlue, background},
ansiBackgroundCyan: {backgroundGreen | backgroundBlue, background},
ansiBackgroundWhite: {backgroundRed | backgroundGreen | backgroundBlue, background},
ansiBackgroundDefault: {0, background},
ansiLightForegroundGray: {foregroundIntensity, foreground},
ansiLightForegroundRed: {foregroundIntensity | foregroundRed, foreground},
ansiLightForegroundGreen: {foregroundIntensity | foregroundGreen, foreground},
ansiLightForegroundYellow: {foregroundIntensity | foregroundRed | foregroundGreen, foreground},
ansiLightForegroundBlue: {foregroundIntensity | foregroundBlue, foreground},
ansiLightForegroundMagenta: {foregroundIntensity | foregroundRed | foregroundBlue, foreground},
ansiLightForegroundCyan: {foregroundIntensity | foregroundGreen | foregroundBlue, foreground},
ansiLightForegroundWhite: {foregroundIntensity | foregroundRed | foregroundGreen | foregroundBlue, foreground},
ansiLightBackgroundGray: {backgroundIntensity, background},
ansiLightBackgroundRed: {backgroundIntensity | backgroundRed, background},
ansiLightBackgroundGreen: {backgroundIntensity | backgroundGreen, background},
ansiLightBackgroundYellow: {backgroundIntensity | backgroundRed | backgroundGreen, background},
ansiLightBackgroundBlue: {backgroundIntensity | backgroundBlue, background},
ansiLightBackgroundMagenta: {backgroundIntensity | backgroundRed | backgroundBlue, background},
ansiLightBackgroundCyan: {backgroundIntensity | backgroundGreen | backgroundBlue, background},
ansiLightBackgroundWhite: {backgroundIntensity | backgroundRed | backgroundGreen | backgroundBlue, background},
}
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
defaultAttr *textAttributes
)
func init() {
screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout))
if screenInfo != nil {
colorMap[ansiForegroundDefault] = winColor{
screenInfo.WAttributes & (foregroundRed | foregroundGreen | foregroundBlue),
foreground,
}
colorMap[ansiBackgroundDefault] = winColor{
screenInfo.WAttributes & (backgroundRed | backgroundGreen | backgroundBlue),
background,
}
defaultAttr = convertTextAttr(screenInfo.WAttributes)
}
}
type coord struct {
X, Y int16
}
type smallRect struct {
Left, Top, Right, Bottom int16
}
type consoleScreenBufferInfo struct {
DwSize coord
DwCursorPosition coord
WAttributes uint16
SrWindow smallRect
DwMaximumWindowSize coord
}
func getConsoleScreenBufferInfo(hConsoleOutput uintptr) *consoleScreenBufferInfo {
var csbi consoleScreenBufferInfo
ret, _, _ := procGetConsoleScreenBufferInfo.Call(
hConsoleOutput,
uintptr(unsafe.Pointer(&csbi)))
if ret == 0 {
return nil
}
return &csbi
}
func setConsoleTextAttribute(hConsoleOutput uintptr, wAttributes uint16) bool {
ret, _, _ := procSetConsoleTextAttribute.Call(
hConsoleOutput,
uintptr(wAttributes))
return ret != 0
}
type textAttributes struct {
foregroundColor uint16
backgroundColor uint16
foregroundIntensity uint16
backgroundIntensity uint16
underscore uint16
otherAttributes uint16
}
func convertTextAttr(winAttr uint16) *textAttributes {
fgColor := winAttr & (foregroundRed | foregroundGreen | foregroundBlue)
bgColor := winAttr & (backgroundRed | backgroundGreen | backgroundBlue)
fgIntensity := winAttr & foregroundIntensity
bgIntensity := winAttr & backgroundIntensity
underline := winAttr & underscore
otherAttributes := winAttr &^ (foregroundMask | backgroundMask | underscore)
return &textAttributes{fgColor, bgColor, fgIntensity, bgIntensity, underline, otherAttributes}
}
func convertWinAttr(textAttr *textAttributes) uint16 {
var winAttr uint16
winAttr |= textAttr.foregroundColor
winAttr |= textAttr.backgroundColor
winAttr |= textAttr.foregroundIntensity
winAttr |= textAttr.backgroundIntensity
winAttr |= textAttr.underscore
winAttr |= textAttr.otherAttributes
return winAttr
}
func changeColor(param []byte) parseResult {
screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout))
if screenInfo == nil {
return noConsole
}
winAttr := convertTextAttr(screenInfo.WAttributes)
strParam := string(param)
if len(strParam) <= 0 {
strParam = "0"
}
csiParam := strings.Split(strParam, string(separatorChar))
for _, p := range csiParam {
c, ok := colorMap[p]
switch {
case !ok:
switch p {
case ansiReset:
winAttr.foregroundColor = defaultAttr.foregroundColor
winAttr.backgroundColor = defaultAttr.backgroundColor
winAttr.foregroundIntensity = defaultAttr.foregroundIntensity
winAttr.backgroundIntensity = defaultAttr.backgroundIntensity
winAttr.underscore = 0
winAttr.otherAttributes = 0
case ansiIntensityOn:
winAttr.foregroundIntensity = foregroundIntensity
case ansiIntensityOff:
winAttr.foregroundIntensity = 0
case ansiUnderlineOn:
winAttr.underscore = underscore
case ansiUnderlineOff:
winAttr.underscore = 0
case ansiBlinkOn:
winAttr.backgroundIntensity = backgroundIntensity
case ansiBlinkOff:
winAttr.backgroundIntensity = 0
default:
// unknown code
}
case c.drawType == foreground:
winAttr.foregroundColor = c.code
case c.drawType == background:
winAttr.backgroundColor = c.code
}
}
winTextAttribute := convertWinAttr(winAttr)
setConsoleTextAttribute(uintptr(syscall.Stdout), winTextAttribute)
return changedColor
}
func parseEscapeSequence(command byte, param []byte) parseResult {
if defaultAttr == nil {
return noConsole
}
switch command {
case sgrCode:
return changeColor(param)
default:
return unknown
}
}
func (cw *colorWriter) flushBuffer() (int, error) {
return cw.flushTo(cw.w)
}
func (cw *colorWriter) resetBuffer() (int, error) {
return cw.flushTo(nil)
}
func (cw *colorWriter) flushTo(w io.Writer) (int, error) {
var n1, n2 int
var err error
startBytes := cw.paramStartBuf.Bytes()
cw.paramStartBuf.Reset()
if w != nil {
n1, err = cw.w.Write(startBytes)
if err != nil {
return n1, err
}
} else {
n1 = len(startBytes)
}
paramBytes := cw.paramBuf.Bytes()
cw.paramBuf.Reset()
if w != nil {
n2, err = cw.w.Write(paramBytes)
if err != nil {
return n1 + n2, err
}
} else {
n2 = len(paramBytes)
}
return n1 + n2, nil
}
func isParameterChar(b byte) bool {
return ('0' <= b && b <= '9') || b == separatorChar
}
func (cw *colorWriter) Write(p []byte) (int, error) {
r, nw, first, last := 0, 0, 0, 0
if cw.mode != DiscardNonColorEscSeq {
cw.state = outsideCsiCode
cw.resetBuffer()
}
var err error
for i, ch := range p {
switch cw.state {
case outsideCsiCode:
if ch == firstCsiChar {
cw.paramStartBuf.WriteByte(ch)
cw.state = firstCsiCode
}
case firstCsiCode:
switch ch {
case firstCsiChar:
cw.paramStartBuf.WriteByte(ch)
break
case secondeCsiChar:
cw.paramStartBuf.WriteByte(ch)
cw.state = secondCsiCode
last = i - 1
default:
cw.resetBuffer()
cw.state = outsideCsiCode
}
case secondCsiCode:
if isParameterChar(ch) {
cw.paramBuf.WriteByte(ch)
} else {
nw, err = cw.w.Write(p[first:last])
r += nw
if err != nil {
return r, err
}
first = i + 1
result := parseEscapeSequence(ch, cw.paramBuf.Bytes())
if result == noConsole || (cw.mode == OutputNonColorEscSeq && result == unknown) {
cw.paramBuf.WriteByte(ch)
nw, err := cw.flushBuffer()
if err != nil {
return r, err
}
r += nw
} else {
n, _ := cw.resetBuffer()
// Add one more to the size of the buffer for the last ch
r += n + 1
}
cw.state = outsideCsiCode
}
default:
cw.state = outsideCsiCode
}
}
if cw.mode != DiscardNonColorEscSeq || cw.state == outsideCsiCode {
nw, err = cw.w.Write(p[first:len(p)])
r += nw
}
return r, err
}

46
conf.go
View File

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

223
fix.go Normal file
View File

@ -0,0 +1,223 @@
package main
import (
"go/parser"
"go/token"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"fmt"
)
var cmdFix = &Command{
UsageLine: "fix",
Short: "fix the beego application to make it compatible with beego 1.6",
Long: `
As from beego1.6, there's some incompatible code with the old version.
bee fix help to upgrade the application to beego 1.6
`,
}
func init() {
cmdFix.Run = runFix
}
func runFix(cmd *Command, args []string) int {
ShowShortVersionBanner()
ColorLog("[INFO] Upgrading the application...\n")
dir, err := os.Getwd()
if err != nil {
ColorLog("[ERRO] GetCurrent Path:%s\n", 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.Println("\tfix\t", path)
if err != nil {
ColorLog("[ERRO] Could not fix file: %s\n", err)
}
return err
})
ColorLog("[INFO] Upgrade done!\n")
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)
}

46
g.go
View File

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

View File

@ -29,9 +29,9 @@ import (
) )
const ( const (
O_MODEL byte = 1 << iota OModel byte = 1 << iota
O_CONTROLLER OController
O_ROUTER ORouter
) )
// DbTransformer has method to reverse engineer a database schema to restful api code // DbTransformer has method to reverse engineer a database schema to restful api code
@ -118,6 +118,7 @@ var typeMappingPostgres = map[string]string{
"time": "time.Time", "time": "time.Time",
"timestamp": "time.Time", "timestamp": "time.Time",
"timestamp without time zone": "time.Time", "timestamp without time zone": "time.Time",
"timestamp with time zone": "time.Time",
"interval": "string", // time interval, string for now "interval": "string", // time interval, string for now
"real": "float32", // float & decimal "real": "float32", // float & decimal
"double precision": "float64", "double precision": "float64",
@ -130,6 +131,7 @@ var typeMappingPostgres = map[string]string{
"USER-DEFINED": "string", // user defined "USER-DEFINED": "string", // user defined
"uuid": "string", // uuid "uuid": "string", // uuid
"json": "string", // json "json": "string", // json
"jsonb": "string",
} }
// Table represent a table in a database // Table represent a table in a database
@ -257,11 +259,11 @@ func generateAppcode(driver, connStr, level, tables, currpath string) {
var mode byte var mode byte
switch level { switch level {
case "1": case "1":
mode = O_MODEL mode = OModel
case "2": case "2":
mode = O_MODEL | O_CONTROLLER mode = OModel | OController
case "3": case "3":
mode = O_MODEL | O_CONTROLLER | O_ROUTER mode = OModel | OController | ORouter
default: default:
ColorLog("[ERRO] Invalid 'level' option: %s\n", level) ColorLog("[ERRO] Invalid 'level' option: %s\n", level)
ColorLog("[HINT] Level must be either 1, 2 or 3\n") ColorLog("[HINT] Level must be either 1, 2 or 3\n")
@ -290,7 +292,7 @@ func generateAppcode(driver, connStr, level, tables, currpath string) {
// Generate takes table, column and foreign key information from database connection // Generate takes table, column and foreign key information from database connection
// and generate corresponding golang source files // and generate corresponding golang source files
func gen(dbms, connStr string, mode byte, selectedTableNames map[string]bool, currpath string) { func gen(dbms, connStr string, mode byte, selectedTableNames map[string]bool, apppath string) {
db, err := sql.Open(dbms, connStr) db, err := sql.Open(dbms, connStr)
if err != nil { if err != nil {
ColorLog("[ERRO] Could not connect to %s database: %s, %s\n", dbms, connStr, err) ColorLog("[ERRO] Could not connect to %s database: %s, %s\n", dbms, connStr, err)
@ -302,11 +304,11 @@ func gen(dbms, connStr string, mode byte, selectedTableNames map[string]bool, cu
tableNames := trans.GetTableNames(db) tableNames := trans.GetTableNames(db)
tables := getTableObjects(tableNames, db, trans) tables := getTableObjects(tableNames, db, trans)
mvcPath := new(MvcPath) mvcPath := new(MvcPath)
mvcPath.ModelPath = path.Join(currpath, "models") mvcPath.ModelPath = path.Join(apppath, "models")
mvcPath.ControllerPath = path.Join(currpath, "controllers") mvcPath.ControllerPath = path.Join(apppath, "controllers")
mvcPath.RouterPath = path.Join(currpath, "routers") mvcPath.RouterPath = path.Join(apppath, "routers")
createPaths(mode, mvcPath) createPaths(mode, mvcPath)
pkgPath := getPackagePath(currpath) pkgPath := getPackagePath(apppath)
writeSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames) writeSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames)
} else { } else {
ColorLog("[ERRO] Generating app code from %s database is not supported yet.\n", dbms) ColorLog("[ERRO] Generating app code from %s database is not supported yet.\n", dbms)
@ -497,16 +499,15 @@ func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[strin
} }
} }
// getGoDataType maps an SQL data type to Golang data type // GetGoDataType maps an SQL data type to Golang data type
func (*MysqlDB) GetGoDataType(sqlType string) (goType string) { func (*MysqlDB) GetGoDataType(sqlType string) (goType string) {
var typeMapping = map[string]string{} var typeMapping = map[string]string{}
typeMapping = typeMappingMysql typeMapping = typeMappingMysql
if v, ok := typeMapping[sqlType]; ok { if v, ok := typeMapping[sqlType]; ok {
return v return v
} else {
ColorLog("[ERRO] data type (%s) not found!\n", sqlType)
os.Exit(2)
} }
ColorLog("[ERRO] data type (%s) not found!\n", sqlType)
os.Exit(2)
return goType return goType
} }
@ -514,7 +515,9 @@ func (*MysqlDB) GetGoDataType(sqlType string) (goType string) {
func (*PostgresDB) GetTableNames(db *sql.DB) (tables []string) { func (*PostgresDB) GetTableNames(db *sql.DB) (tables []string) {
rows, err := db.Query(` rows, err := db.Query(`
SELECT table_name FROM information_schema.tables SELECT table_name FROM information_schema.tables
WHERE table_catalog = current_database() and table_schema = 'public'`) WHERE table_catalog = current_database() AND
table_type = 'BASE TABLE' AND
table_schema NOT IN ('pg_catalog', 'information_schema')`)
if err != nil { if err != nil {
ColorLog("[ERRO] Could not show tables: %s\n", err) ColorLog("[ERRO] Could not show tables: %s\n", err)
ColorLog("[HINT] Check your connection string\n") ColorLog("[HINT] Check your connection string\n")
@ -549,8 +552,10 @@ func (*PostgresDB) GetConstraints(db *sql.DB, table *Table, blackList map[string
INNER JOIN INNER JOIN
information_schema.constraint_column_usage cu ON cu.constraint_name = c.constraint_name information_schema.constraint_column_usage cu ON cu.constraint_name = c.constraint_name
WHERE WHERE
c.table_catalog = current_database() AND c.table_schema = 'public' AND c.table_name = $1 c.table_catalog = current_database() AND c.table_schema NOT IN ('pg_catalog', 'information_schema')
AND u.table_catalog = current_database() AND u.table_schema = 'public' AND u.table_name = $2`, AND c.table_name = $1
AND u.table_catalog = current_database() AND u.table_schema NOT IN ('pg_catalog', 'information_schema')
AND u.table_name = $2`,
table.Name, table.Name) // u.position_in_unique_constraint, table.Name, table.Name) // u.position_in_unique_constraint,
if err != nil { if err != nil {
ColorLog("[ERRO] Could not query INFORMATION_SCHEMA for PK/UK/FK information: %s\n", err) ColorLog("[ERRO] Could not query INFORMATION_SCHEMA for PK/UK/FK information: %s\n", err)
@ -606,7 +611,8 @@ func (postgresDB *PostgresDB) GetColumns(db *sql.DB, table *Table, blackList map
FROM FROM
information_schema.columns information_schema.columns
WHERE WHERE
table_catalog = current_database() AND table_schema = 'public' AND table_name = $1`, table_catalog = current_database() AND table_schema NOT IN ('pg_catalog', 'information_schema')
AND table_name = $1`,
table.Name) table.Name)
defer colDefRows.Close() defer colDefRows.Close()
for colDefRows.Next() { for colDefRows.Next() {
@ -682,25 +688,26 @@ func (postgresDB *PostgresDB) GetColumns(db *sql.DB, table *Table, blackList map
table.Columns = append(table.Columns, col) table.Columns = append(table.Columns, col)
} }
} }
// GetGoDataType returns the Go type from the mapped Postgres type
func (*PostgresDB) GetGoDataType(sqlType string) (goType string) { func (*PostgresDB) GetGoDataType(sqlType string) (goType string) {
if v, ok := typeMappingPostgres[sqlType]; ok { if v, ok := typeMappingPostgres[sqlType]; ok {
return v return v
} else {
ColorLog("[ERRO] data type (%s) not found!\n", sqlType)
os.Exit(2)
} }
ColorLog("[ERRO] data type (%s) not found!\n", sqlType)
os.Exit(2)
return goType return goType
} }
// deleteAndRecreatePaths removes several directories completely // deleteAndRecreatePaths removes several directories completely
func createPaths(mode byte, paths *MvcPath) { func createPaths(mode byte, paths *MvcPath) {
if (mode & O_MODEL) == O_MODEL { if (mode & OModel) == OModel {
os.Mkdir(paths.ModelPath, 0777) os.Mkdir(paths.ModelPath, 0777)
} }
if (mode & O_CONTROLLER) == O_CONTROLLER { if (mode & OController) == OController {
os.Mkdir(paths.ControllerPath, 0777) os.Mkdir(paths.ControllerPath, 0777)
} }
if (mode & O_ROUTER) == O_ROUTER { if (mode & ORouter) == ORouter {
os.Mkdir(paths.RouterPath, 0777) os.Mkdir(paths.RouterPath, 0777)
} }
} }
@ -709,15 +716,15 @@ func createPaths(mode byte, paths *MvcPath) {
// It will wipe the following directories and recreate them:./models, ./controllers, ./routers // It will wipe the following directories and recreate them:./models, ./controllers, ./routers
// Newly geneated files will be inside these folders. // Newly geneated files will be inside these folders.
func writeSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) { func writeSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) {
if (O_MODEL & mode) == O_MODEL { if (OModel & mode) == OModel {
ColorLog("[INFO] Creating model files...\n") ColorLog("[INFO] Creating model files...\n")
writeModelFiles(tables, paths.ModelPath, selectedTables) writeModelFiles(tables, paths.ModelPath, selectedTables)
} }
if (O_CONTROLLER & mode) == O_CONTROLLER { if (OController & mode) == OController {
ColorLog("[INFO] Creating controller files...\n") ColorLog("[INFO] Creating controller files...\n")
writeControllerFiles(tables, paths.ControllerPath, selectedTables, pkgPath) writeControllerFiles(tables, paths.ControllerPath, selectedTables, pkgPath)
} }
if (O_ROUTER & mode) == O_ROUTER { if (ORouter & mode) == ORouter {
ColorLog("[INFO] Creating router files...\n") ColorLog("[INFO] Creating router files...\n")
writeRouterFile(tables, paths.RouterPath, selectedTables, pkgPath) writeRouterFile(tables, paths.RouterPath, selectedTables, pkgPath)
} }
@ -725,6 +732,8 @@ func writeSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath
// writeModelFiles generates model files // writeModelFiles generates model files
func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bool) { func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bool) {
w := NewColorWriter(os.Stdout)
for _, tb := range tables { for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it // if selectedTables map is not nil and this table is not selected, ignore it
if selectedTables != nil { if selectedTables != nil {
@ -737,7 +746,7 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
var f *os.File var f *os.File
var err error var err error
if isExist(fpath) { if isExist(fpath) {
ColorLog("[WARN] %v is exist, do you want to overwrite it? Yes or No?\n", fpath) ColorLog("[WARN] '%v' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if askForConfirmation() { if askForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil { if err != nil {
@ -745,7 +754,7 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
continue continue
} }
} else { } else {
ColorLog("[WARN] skip create file\n") ColorLog("[WARN] Skipped create file '%s'\n", fpath)
continue continue
} }
} else { } else {
@ -757,9 +766,9 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
} }
template := "" template := ""
if tb.Pk == "" { if tb.Pk == "" {
template = STRUCT_MODEL_TPL template = StructModelTPL
} else { } else {
template = MODEL_TPL template = ModelTPL
} }
fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1) fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1)
fileStr = strings.Replace(fileStr, "{{modelName}}", camelCase(tb.Name), -1) fileStr = strings.Replace(fileStr, "{{modelName}}", camelCase(tb.Name), -1)
@ -777,14 +786,16 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
ColorLog("[ERRO] Could not write model file to %s\n", fpath) ColorLog("[ERRO] Could not write model file to %s\n", fpath)
os.Exit(2) os.Exit(2)
} }
f.Close() CloseFile(f)
ColorLog("[INFO] model => %s\n", fpath) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath) formatSourceCode(fpath)
} }
} }
// writeControllerFiles generates controller files // writeControllerFiles generates controller files
func writeControllerFiles(tables []*Table, cPath string, selectedTables map[string]bool, pkgPath string) { func writeControllerFiles(tables []*Table, cPath string, selectedTables map[string]bool, pkgPath string) {
w := NewColorWriter(os.Stdout)
for _, tb := range tables { for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it // if selectedTables map is not nil and this table is not selected, ignore it
if selectedTables != nil { if selectedTables != nil {
@ -800,7 +811,7 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri
var f *os.File var f *os.File
var err error var err error
if isExist(fpath) { if isExist(fpath) {
ColorLog("[WARN] %v is exist, do you want to overwrite it? Yes or No?\n", fpath) ColorLog("[WARN] '%v' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if askForConfirmation() { if askForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil { if err != nil {
@ -808,7 +819,7 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri
continue continue
} }
} else { } else {
ColorLog("[WARN] skip create file\n") ColorLog("[WARN] Skipped create file '%s'\n", fpath)
continue continue
} }
} else { } else {
@ -818,20 +829,22 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri
continue continue
} }
} }
fileStr := strings.Replace(CTRL_TPL, "{{ctrlName}}", camelCase(tb.Name), -1) fileStr := strings.Replace(CtrlTPL, "{{ctrlName}}", camelCase(tb.Name), -1)
fileStr = strings.Replace(fileStr, "{{pkgPath}}", pkgPath, -1) fileStr = strings.Replace(fileStr, "{{pkgPath}}", pkgPath, -1)
if _, err := f.WriteString(fileStr); err != nil { if _, err := f.WriteString(fileStr); err != nil {
ColorLog("[ERRO] Could not write controller file to %s\n", fpath) ColorLog("[ERRO] Could not write controller file to %s\n", fpath)
os.Exit(2) os.Exit(2)
} }
f.Close() CloseFile(f)
ColorLog("[INFO] controller => %s\n", fpath) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath) formatSourceCode(fpath)
} }
} }
// writeRouterFile generates router file // writeRouterFile generates router file
func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bool, pkgPath string) { func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bool, pkgPath string) {
w := NewColorWriter(os.Stdout)
var nameSpaces []string var nameSpaces []string
for _, tb := range tables { for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it // if selectedTables map is not nil and this table is not selected, ignore it
@ -843,19 +856,19 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo
if tb.Pk == "" { if tb.Pk == "" {
continue continue
} }
// add name spaces // add namespaces
nameSpace := strings.Replace(NAMESPACE_TPL, "{{nameSpace}}", tb.Name, -1) nameSpace := strings.Replace(NamespaceTPL, "{{nameSpace}}", tb.Name, -1)
nameSpace = strings.Replace(nameSpace, "{{ctrlName}}", camelCase(tb.Name), -1) nameSpace = strings.Replace(nameSpace, "{{ctrlName}}", camelCase(tb.Name), -1)
nameSpaces = append(nameSpaces, nameSpace) nameSpaces = append(nameSpaces, nameSpace)
} }
// add export controller // add export controller
fpath := path.Join(rPath, "router.go") fpath := path.Join(rPath, "router.go")
routerStr := strings.Replace(ROUTER_TPL, "{{nameSpaces}}", strings.Join(nameSpaces, ""), 1) routerStr := strings.Replace(RouterTPL, "{{nameSpaces}}", strings.Join(nameSpaces, ""), 1)
routerStr = strings.Replace(routerStr, "{{pkgPath}}", pkgPath, 1) routerStr = strings.Replace(routerStr, "{{pkgPath}}", pkgPath, 1)
var f *os.File var f *os.File
var err error var err error
if isExist(fpath) { if isExist(fpath) {
ColorLog("[WARN] %v is exist, do you want to overwrite it? Yes or No?\n", fpath) ColorLog("[WARN] '%v' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if askForConfirmation() { if askForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil { if err != nil {
@ -863,7 +876,7 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo
return return
} }
} else { } else {
ColorLog("[WARN] skip create file\n") ColorLog("[WARN] Skipped create file '%s'\n", fpath)
return return
} }
} else { } else {
@ -874,11 +887,11 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo
} }
} }
if _, err := f.WriteString(routerStr); err != nil { if _, err := f.WriteString(routerStr); err != nil {
ColorLog("[ERRO] Could not write router file to %s\n", fpath) ColorLog("[ERRO] Could not write router file to '%s'\n", fpath)
os.Exit(2) os.Exit(2)
} }
f.Close() CloseFile(f)
ColorLog("[INFO] router => %s\n", fpath) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath) formatSourceCode(fpath)
} }
@ -961,7 +974,7 @@ func getPackagePath(curpath string) (packpath string) {
gopath := os.Getenv("GOPATH") gopath := os.Getenv("GOPATH")
Debugf("gopath:%s", gopath) Debugf("gopath:%s", gopath)
if gopath == "" { if gopath == "" {
ColorLog("[ERRO] you should set GOPATH in the env") ColorLog("[ERRO] You should set GOPATH in the env")
os.Exit(2) os.Exit(2)
} }
@ -983,17 +996,23 @@ func getPackagePath(curpath string) (packpath string) {
ColorLog("[ERRO] Can't generate application code outside of GOPATH '%s'\n", gopath) ColorLog("[ERRO] Can't generate application code outside of GOPATH '%s'\n", gopath)
os.Exit(2) os.Exit(2)
} }
if curpath == appsrcpath {
ColorLog("[ERRO] Can't generate application code outside of application PATH \n")
os.Exit(2)
}
packpath = strings.Join(strings.Split(curpath[len(appsrcpath)+1:], string(filepath.Separator)), "/") packpath = strings.Join(strings.Split(curpath[len(appsrcpath)+1:], string(filepath.Separator)), "/")
return return
} }
const ( const (
STRUCT_MODEL_TPL = `package models StructModelTPL = `package models
{{importTimePkg}} {{importTimePkg}}
{{modelStruct}} {{modelStruct}}
` `
MODEL_TPL = `package models ModelTPL = `package models
import ( import (
"errors" "errors"
@ -1086,7 +1105,7 @@ func GetAll{{modelName}}(query map[string]string, fields []string, sortby []stri
var l []{{modelName}} var l []{{modelName}}
qs = qs.OrderBy(sortFields...) qs = qs.OrderBy(sortFields...)
if _, err := qs.Limit(limit, offset).All(&l, fields...); err == nil { if _, err = qs.Limit(limit, offset).All(&l, fields...); err == nil {
if len(fields) == 0 { if len(fields) == 0 {
for _, v := range l { for _, v := range l {
ml = append(ml, v) ml = append(ml, v)
@ -1137,7 +1156,7 @@ func Delete{{modelName}}(id int) (err error) {
return return
} }
` `
CTRL_TPL = `package controllers CtrlTPL = `package controllers
import ( import (
"{{pkgPath}}/models" "{{pkgPath}}/models"
@ -1149,11 +1168,12 @@ import (
"github.com/astaxie/beego" "github.com/astaxie/beego"
) )
// oprations for {{ctrlName}} // {{ctrlName}}Controller oprations for {{ctrlName}}
type {{ctrlName}}Controller struct { type {{ctrlName}}Controller struct {
beego.Controller beego.Controller
} }
// URLMapping ...
func (c *{{ctrlName}}Controller) URLMapping() { func (c *{{ctrlName}}Controller) URLMapping() {
c.Mapping("Post", c.Post) c.Mapping("Post", c.Post)
c.Mapping("GetOne", c.GetOne) c.Mapping("GetOne", c.GetOne)
@ -1162,31 +1182,37 @@ func (c *{{ctrlName}}Controller) URLMapping() {
c.Mapping("Delete", c.Delete) c.Mapping("Delete", c.Delete)
} }
// Post ...
// @Title Post // @Title Post
// @Description create {{ctrlName}} // @Description create {{ctrlName}}
// @Param body body models.{{ctrlName}} true "body for {{ctrlName}} content" // @Param body body models.{{ctrlName}} true "body for {{ctrlName}} content"
// @Success 200 {int} models.{{ctrlName}}.Id // @Success 201 {int} models.{{ctrlName}}
// @Failure 403 body is empty // @Failure 403 body is empty
// @router / [post] // @router / [post]
func (c *{{ctrlName}}Controller) Post() { func (c *{{ctrlName}}Controller) Post() {
var v models.{{ctrlName}} var v models.{{ctrlName}}
json.Unmarshal(c.Ctx.Input.RequestBody, &v) if err := json.Unmarshal(c.Ctx.Input.RequestBody, &v); err == nil {
if id, err := models.Add{{ctrlName}}(&v); err == nil { if _, err := models.Add{{ctrlName}}(&v); err == nil {
c.Data["json"] = map[string]int64{"id": id} c.Ctx.Output.SetStatus(201)
c.Data["json"] = v
} else {
c.Data["json"] = err.Error()
}
} else { } else {
c.Data["json"] = err.Error() c.Data["json"] = err.Error()
} }
c.ServeJson() c.ServeJSON()
} }
// @Title Get // GetOne ...
// @Title Get One
// @Description get {{ctrlName}} by id // @Description get {{ctrlName}} by id
// @Param id path string true "The key for staticblock" // @Param id path string true "The key for staticblock"
// @Success 200 {object} models.{{ctrlName}} // @Success 200 {object} models.{{ctrlName}}
// @Failure 403 :id is empty // @Failure 403 :id is empty
// @router /:id [get] // @router /:id [get]
func (c *{{ctrlName}}Controller) GetOne() { func (c *{{ctrlName}}Controller) GetOne() {
idStr := c.Ctx.Input.Params[":id"] idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.Atoi(idStr) id, _ := strconv.Atoi(idStr)
v, err := models.Get{{ctrlName}}ById(id) v, err := models.Get{{ctrlName}}ById(id)
if err != nil { if err != nil {
@ -1194,9 +1220,10 @@ func (c *{{ctrlName}}Controller) GetOne() {
} else { } else {
c.Data["json"] = v c.Data["json"] = v
} }
c.ServeJson() c.ServeJSON()
} }
// GetAll ...
// @Title Get All // @Title Get All
// @Description get {{ctrlName}} // @Description get {{ctrlName}}
// @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..." // @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..."
@ -1212,9 +1239,9 @@ func (c *{{ctrlName}}Controller) GetAll() {
var fields []string var fields []string
var sortby []string var sortby []string
var order []string var order []string
var query map[string]string = make(map[string]string) var query = make(map[string]string)
var limit int64 = 10 var limit int64 = 10
var offset int64 = 0 var offset int64
// fields: col1,col2,entity.col3 // fields: col1,col2,entity.col3
if v := c.GetString("fields"); v != "" { if v := c.GetString("fields"); v != "" {
@ -1239,10 +1266,10 @@ func (c *{{ctrlName}}Controller) GetAll() {
// query: k:v,k:v // query: k:v,k:v
if v := c.GetString("query"); v != "" { if v := c.GetString("query"); v != "" {
for _, cond := range strings.Split(v, ",") { for _, cond := range strings.Split(v, ",") {
kv := strings.Split(cond, ":") kv := strings.SplitN(cond, ":", 2)
if len(kv) != 2 { if len(kv) != 2 {
c.Data["json"] = errors.New("Error: invalid query key/value pair") c.Data["json"] = errors.New("Error: invalid query key/value pair")
c.ServeJson() c.ServeJSON()
return return
} }
k, v := kv[0], kv[1] k, v := kv[0], kv[1]
@ -1256,10 +1283,11 @@ func (c *{{ctrlName}}Controller) GetAll() {
} else { } else {
c.Data["json"] = l c.Data["json"] = l
} }
c.ServeJson() c.ServeJSON()
} }
// @Title Update // Put ...
// @Title Put
// @Description update the {{ctrlName}} // @Description update the {{ctrlName}}
// @Param id path string true "The id you want to update" // @Param id path string true "The id you want to update"
// @Param body body models.{{ctrlName}} true "body for {{ctrlName}} content" // @Param body body models.{{ctrlName}} true "body for {{ctrlName}} content"
@ -1267,18 +1295,22 @@ func (c *{{ctrlName}}Controller) GetAll() {
// @Failure 403 :id is not int // @Failure 403 :id is not int
// @router /:id [put] // @router /:id [put]
func (c *{{ctrlName}}Controller) Put() { func (c *{{ctrlName}}Controller) Put() {
idStr := c.Ctx.Input.Params[":id"] idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.Atoi(idStr) id, _ := strconv.Atoi(idStr)
v := models.{{ctrlName}}{Id: id} v := models.{{ctrlName}}{Id: id}
json.Unmarshal(c.Ctx.Input.RequestBody, &v) if err := json.Unmarshal(c.Ctx.Input.RequestBody, &v); err == nil {
if err := models.Update{{ctrlName}}ById(&v); err == nil { if err := models.Update{{ctrlName}}ById(&v); err == nil {
c.Data["json"] = "OK" c.Data["json"] = "OK"
} else {
c.Data["json"] = err.Error()
}
} else { } else {
c.Data["json"] = err.Error() c.Data["json"] = err.Error()
} }
c.ServeJson() c.ServeJSON()
} }
// Delete ...
// @Title Delete // @Title Delete
// @Description delete the {{ctrlName}} // @Description delete the {{ctrlName}}
// @Param id path string true "The id you want to delete" // @Param id path string true "The id you want to delete"
@ -1286,17 +1318,17 @@ func (c *{{ctrlName}}Controller) Put() {
// @Failure 403 id is empty // @Failure 403 id is empty
// @router /:id [delete] // @router /:id [delete]
func (c *{{ctrlName}}Controller) Delete() { func (c *{{ctrlName}}Controller) Delete() {
idStr := c.Ctx.Input.Params[":id"] idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.Atoi(idStr) id, _ := strconv.Atoi(idStr)
if err := models.Delete{{ctrlName}}(id); err == nil { if err := models.Delete{{ctrlName}}(id); err == nil {
c.Data["json"] = "OK" c.Data["json"] = "OK"
} else { } else {
c.Data["json"] = err.Error() c.Data["json"] = err.Error()
} }
c.ServeJson() c.ServeJSON()
} }
` `
ROUTER_TPL = `// @APIVersion 1.0.0 RouterTPL = `// @APIVersion 1.0.0
// @Title beego Test API // @Title beego Test API
// @Description beego has a very cool tools to autogenerate documents for your API // @Description beego has a very cool tools to autogenerate documents for your API
// @Contact astaxie@gmail.com // @Contact astaxie@gmail.com
@ -1318,7 +1350,7 @@ func init() {
beego.AddNamespace(ns) beego.AddNamespace(ns)
} }
` `
NAMESPACE_TPL = ` NamespaceTPL = `
beego.NSNamespace("/{{nameSpace}}", beego.NSNamespace("/{{nameSpace}}",
beego.NSInclude( beego.NSInclude(
&controllers.{{ctrlName}}Controller{}, &controllers.{{ctrlName}}Controller{},

View File

@ -15,6 +15,7 @@
package main package main
import ( import (
"fmt"
"os" "os"
"path" "path"
"strings" "strings"
@ -23,35 +24,53 @@ import (
// article // article
// cms/article // cms/article
// //
func generateController(cname, crupath string) { func generateController(cname, currpath string) {
w := NewColorWriter(os.Stdout)
p, f := path.Split(cname) p, f := path.Split(cname)
controllerName := strings.Title(f) controllerName := strings.Title(f)
packageName := "controllers" packageName := "controllers"
if p != "" { if p != "" {
i := strings.LastIndex(p[:len(p)-1], "/") i := strings.LastIndex(p[:len(p)-1], "/")
packageName = p[i+1 : len(p)-1] packageName = p[i+1 : len(p)-1]
} }
ColorLog("[INFO] Using '%s' as controller name\n", controllerName) ColorLog("[INFO] Using '%s' as controller name\n", controllerName)
ColorLog("[INFO] Using '%s' as package name\n", packageName) ColorLog("[INFO] Using '%s' as package name\n", packageName)
fp := path.Join(crupath, "controllers", p)
fp := path.Join(currpath, "controllers", p)
if _, err := os.Stat(fp); os.IsNotExist(err) { if _, err := os.Stat(fp); os.IsNotExist(err) {
// create controller directory // Create the controller's directory
if err := os.MkdirAll(fp, 0777); err != nil { if err := os.MkdirAll(fp, 0777); err != nil {
ColorLog("[ERRO] Could not create controllers directory: %s\n", err) ColorLog("[ERRO] Could not create controllers directory: %s\n", err)
os.Exit(2) os.Exit(2)
} }
} }
fpath := path.Join(fp, strings.ToLower(controllerName)+".go") fpath := path.Join(fp, strings.ToLower(controllerName)+".go")
if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer f.Close() defer CloseFile(f)
content := strings.Replace(controllerTpl, "{{packageName}}", packageName, -1)
modelPath := path.Join(currpath, "models", strings.ToLower(controllerName)+".go")
var content string
if _, err := os.Stat(modelPath); err == nil {
ColorLog("[INFO] Using matching model '%s'\n", controllerName)
content = strings.Replace(controllerModelTpl, "{{packageName}}", packageName, -1)
pkgPath := getPackagePath(currpath)
content = strings.Replace(content, "{{pkgPath}}", pkgPath, -1)
} else {
content = strings.Replace(controllerTpl, "{{packageName}}", packageName, -1)
}
content = strings.Replace(content, "{{controllerName}}", controllerName, -1) content = strings.Replace(content, "{{controllerName}}", controllerName, -1)
f.WriteString(content) f.WriteString(content)
// gofmt generated source code
// Run 'gofmt' on the generated source code
formatSourceCode(fpath) formatSourceCode(fpath)
ColorLog("[INFO] controller file generated: %s\n", fpath) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
} else { } else {
// error creating file
ColorLog("[ERRO] Could not create controller file: %s\n", err) ColorLog("[ERRO] Could not create controller file: %s\n", err)
os.Exit(2) os.Exit(2)
} }
@ -63,11 +82,12 @@ import (
"github.com/astaxie/beego" "github.com/astaxie/beego"
) )
// operations for {{controllerName}} // {{controllerName}}Controller operations for {{controllerName}}
type {{controllerName}}Controller struct { type {{controllerName}}Controller struct {
beego.Controller beego.Controller
} }
// URLMapping ...
func (c *{{controllerName}}Controller) URLMapping() { func (c *{{controllerName}}Controller) URLMapping() {
c.Mapping("Post", c.Post) c.Mapping("Post", c.Post)
c.Mapping("GetOne", c.GetOne) c.Mapping("GetOne", c.GetOne)
@ -76,17 +96,19 @@ func (c *{{controllerName}}Controller) URLMapping() {
c.Mapping("Delete", c.Delete) c.Mapping("Delete", c.Delete)
} }
// @Title Post // Post ...
// @Title Create
// @Description create {{controllerName}} // @Description create {{controllerName}}
// @Param body body models.{{controllerName}} true "body for {{controllerName}} content" // @Param body body models.{{controllerName}} true "body for {{controllerName}} content"
// @Success 200 {int} models.{{controllerName}}.Id // @Success 201 {object} models.{{controllerName}}
// @Failure 403 body is empty // @Failure 403 body is empty
// @router / [post] // @router / [post]
func (c *{{controllerName}}Controller) Post() { func (c *{{controllerName}}Controller) Post() {
} }
// @Title Get // GetOne ...
// @Title GetOne
// @Description get {{controllerName}} by id // @Description get {{controllerName}} by id
// @Param id path string true "The key for staticblock" // @Param id path string true "The key for staticblock"
// @Success 200 {object} models.{{controllerName}} // @Success 200 {object} models.{{controllerName}}
@ -96,7 +118,8 @@ func (c *{{controllerName}}Controller) GetOne() {
} }
// @Title Get All // GetAll ...
// @Title GetAll
// @Description get {{controllerName}} // @Description get {{controllerName}}
// @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..." // @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..."
// @Param fields query string false "Fields returned. e.g. col1,col2 ..." // @Param fields query string false "Fields returned. e.g. col1,col2 ..."
@ -111,7 +134,8 @@ func (c *{{controllerName}}Controller) GetAll() {
} }
// @Title Update // Put ...
// @Title Put
// @Description update the {{controllerName}} // @Description update the {{controllerName}}
// @Param id path string true "The id you want to update" // @Param id path string true "The id you want to update"
// @Param body body models.{{controllerName}} true "body for {{controllerName}} content" // @Param body body models.{{controllerName}} true "body for {{controllerName}} content"
@ -122,6 +146,7 @@ func (c *{{controllerName}}Controller) Put() {
} }
// Delete ...
// @Title Delete // @Title Delete
// @Description delete the {{controllerName}} // @Description delete the {{controllerName}}
// @Param id path string true "The id you want to delete" // @Param id path string true "The id you want to delete"
@ -132,3 +157,170 @@ func (c *{{controllerName}}Controller) Delete() {
} }
` `
var controllerModelTpl = `package {{packageName}}
import (
"{{pkgPath}}/models"
"encoding/json"
"errors"
"strconv"
"strings"
"github.com/astaxie/beego"
)
// {{controllerName}}Controller oprations for {{controllerName}}
type {{controllerName}}Controller struct {
beego.Controller
}
// URLMapping ...
func (c *{{controllerName}}Controller) URLMapping() {
c.Mapping("Post", c.Post)
c.Mapping("GetOne", c.GetOne)
c.Mapping("GetAll", c.GetAll)
c.Mapping("Put", c.Put)
c.Mapping("Delete", c.Delete)
}
// Post ...
// @Title Post
// @Description create {{controllerName}}
// @Param body body models.{{controllerName}} true "body for {{controllerName}} content"
// @Success 201 {int} models.{{controllerName}}
// @Failure 403 body is empty
// @router / [post]
func (c *{{controllerName}}Controller) Post() {
var v models.{{controllerName}}
json.Unmarshal(c.Ctx.Input.RequestBody, &v)
if _, err := models.Add{{controllerName}}(&v); err == nil {
c.Ctx.Output.SetStatus(201)
c.Data["json"] = v
} else {
c.Data["json"] = err.Error()
}
c.ServeJSON()
}
// GetOne ...
// @Title Get One
// @Description get {{controllerName}} by id
// @Param id path string true "The key for staticblock"
// @Success 200 {object} models.{{controllerName}}
// @Failure 403 :id is empty
// @router /:id [get]
func (c *{{controllerName}}Controller) GetOne() {
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseInt(idStr, 0, 64)
v, err := models.Get{{controllerName}}ById(id)
if err != nil {
c.Data["json"] = err.Error()
} else {
c.Data["json"] = v
}
c.ServeJSON()
}
// GetAll ...
// @Title Get All
// @Description get {{controllerName}}
// @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..."
// @Param fields query string false "Fields returned. e.g. col1,col2 ..."
// @Param sortby query string false "Sorted-by fields. e.g. col1,col2 ..."
// @Param order query string false "Order corresponding to each sortby field, if single value, apply to all sortby fields. e.g. desc,asc ..."
// @Param limit query string false "Limit the size of result set. Must be an integer"
// @Param offset query string false "Start position of result set. Must be an integer"
// @Success 200 {object} models.{{controllerName}}
// @Failure 403
// @router / [get]
func (c *{{controllerName}}Controller) GetAll() {
var fields []string
var sortby []string
var order []string
var query = make(map[string]string)
var limit int64 = 10
var offset int64
// fields: col1,col2,entity.col3
if v := c.GetString("fields"); v != "" {
fields = strings.Split(v, ",")
}
// limit: 10 (default is 10)
if v, err := c.GetInt64("limit"); err == nil {
limit = v
}
// offset: 0 (default is 0)
if v, err := c.GetInt64("offset"); err == nil {
offset = v
}
// sortby: col1,col2
if v := c.GetString("sortby"); v != "" {
sortby = strings.Split(v, ",")
}
// order: desc,asc
if v := c.GetString("order"); v != "" {
order = strings.Split(v, ",")
}
// query: k:v,k:v
if v := c.GetString("query"); v != "" {
for _, cond := range strings.Split(v, ",") {
kv := strings.SplitN(cond, ":", 2)
if len(kv) != 2 {
c.Data["json"] = errors.New("Error: invalid query key/value pair")
c.ServeJSON()
return
}
k, v := kv[0], kv[1]
query[k] = v
}
}
l, err := models.GetAll{{controllerName}}(query, fields, sortby, order, offset, limit)
if err != nil {
c.Data["json"] = err.Error()
} else {
c.Data["json"] = l
}
c.ServeJSON()
}
// Put ...
// @Title Put
// @Description update the {{controllerName}}
// @Param id path string true "The id you want to update"
// @Param body body models.{{controllerName}} true "body for {{controllerName}} content"
// @Success 200 {object} models.{{controllerName}}
// @Failure 403 :id is not int
// @router /:id [put]
func (c *{{controllerName}}Controller) Put() {
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseInt(idStr, 0, 64)
v := models.{{controllerName}}{Id: id}
json.Unmarshal(c.Ctx.Input.RequestBody, &v)
if err := models.Update{{controllerName}}ById(&v); err == nil {
c.Data["json"] = "OK"
} else {
c.Data["json"] = err.Error()
}
c.ServeJSON()
}
// Delete ...
// @Title Delete
// @Description delete the {{controllerName}}
// @Param id path string true "The id you want to delete"
// @Success 200 {string} delete success!
// @Failure 403 id is empty
// @router /:id [delete]
func (c *{{controllerName}}Controller) Delete() {
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseInt(idStr, 0, 64)
if err := models.Delete{{controllerName}}(id); err == nil {
c.Data["json"] = "OK"
} else {
c.Data["json"] = err.Error()
}
c.ServeJSON()
}
`

731
g_docs.go
View File

@ -31,65 +31,12 @@ import (
"strings" "strings"
"unicode" "unicode"
"gopkg.in/yaml.v2"
"github.com/astaxie/beego/swagger" "github.com/astaxie/beego/swagger"
"github.com/astaxie/beego/utils" "github.com/astaxie/beego/utils"
) )
var globalDocsTemplate = `package docs
import (
"encoding/json"
"strings"
"github.com/astaxie/beego"
"github.com/astaxie/beego/swagger"
)
const (
Rootinfo string = {{.rootinfo}}
Subapi string = {{.subapi}}
BasePath string= "{{.version}}"
)
var rootapi swagger.ResourceListing
var apilist map[string]*swagger.ApiDeclaration
func init() {
err := json.Unmarshal([]byte(Rootinfo), &rootapi)
if err != nil {
beego.Error(err)
}
err = json.Unmarshal([]byte(Subapi), &apilist)
if err != nil {
beego.Error(err)
}
beego.GlobalDocApi["Root"] = rootapi
for k, v := range apilist {
for i, a := range v.Apis {
a.Path = urlReplace(k + a.Path)
v.Apis[i] = a
}
v.BasePath = BasePath
beego.GlobalDocApi[strings.Trim(k, "/")] = v
}
}
func urlReplace(src string) string {
pt := strings.Split(src, "/")
for i, p := range pt {
if len(p) > 0 {
if p[0] == ':' {
pt[i] = "{" + p[1:] + "}"
} else if p[0] == '?' && p[1] == ':' {
pt[i] = "{" + p[2:] + "}"
}
}
}
return strings.Join(pt, "/")
}
`
const ( const (
ajson = "application/json" ajson = "application/json"
axml = "application/xml" axml = "application/xml"
@ -97,21 +44,19 @@ const (
ahtml = "text/html" ahtml = "text/html"
) )
var pkgCache map[string]bool //pkg:controller:function:comments comments: key:value var pkgCache map[string]struct{} //pkg:controller:function:comments comments: key:value
var controllerComments map[string]string var controllerComments map[string]string
var importlist map[string]string var importlist map[string]string
var apilist map[string]*swagger.ApiDeclaration var controllerList map[string]map[string]*swagger.Item //controllername Paths items
var controllerList map[string][]swagger.Api var modelsList map[string]map[string]swagger.Schema
var modelsList map[string]map[string]swagger.Model var rootapi swagger.Swagger
var rootapi swagger.ResourceListing
func init() { func init() {
pkgCache = make(map[string]bool) pkgCache = make(map[string]struct{})
controllerComments = make(map[string]string) controllerComments = make(map[string]string)
importlist = make(map[string]string) importlist = make(map[string]string)
apilist = make(map[string]*swagger.ApiDeclaration) controllerList = make(map[string]map[string]*swagger.Item)
controllerList = make(map[string][]swagger.Api) modelsList = make(map[string]map[string]swagger.Schema)
modelsList = make(map[string]map[string]swagger.Model)
} }
func generateDocs(curpath string) { func generateDocs(curpath string) {
@ -124,30 +69,47 @@ func generateDocs(curpath string) {
os.Exit(2) os.Exit(2)
} }
rootapi.Infos = swagger.Infomation{} rootapi.Infos = swagger.Information{}
rootapi.SwaggerVersion = swagger.SwaggerVersion rootapi.SwaggerVersion = "2.0"
//analysis API comments //analysis API comments
if f.Comments != nil { if f.Comments != nil {
for _, c := range f.Comments { for _, c := range f.Comments {
for _, s := range strings.Split(c.Text(), "\n") { for _, s := range strings.Split(c.Text(), "\n") {
if strings.HasPrefix(s, "@APIVersion") { if strings.HasPrefix(s, "@APIVersion") {
rootapi.ApiVersion = strings.TrimSpace(s[len("@APIVersion"):]) rootapi.Infos.Version = strings.TrimSpace(s[len("@APIVersion"):])
} else if strings.HasPrefix(s, "@Title") { } else if strings.HasPrefix(s, "@Title") {
rootapi.Infos.Title = strings.TrimSpace(s[len("@Title"):]) rootapi.Infos.Title = strings.TrimSpace(s[len("@Title"):])
} else if strings.HasPrefix(s, "@Description") { } else if strings.HasPrefix(s, "@Description") {
rootapi.Infos.Description = strings.TrimSpace(s[len("@Description"):]) rootapi.Infos.Description = strings.TrimSpace(s[len("@Description"):])
} else if strings.HasPrefix(s, "@TermsOfServiceUrl") { } else if strings.HasPrefix(s, "@TermsOfServiceUrl") {
rootapi.Infos.TermsOfServiceUrl = strings.TrimSpace(s[len("@TermsOfServiceUrl"):]) rootapi.Infos.TermsOfService = strings.TrimSpace(s[len("@TermsOfServiceUrl"):])
} else if strings.HasPrefix(s, "@Contact") { } else if strings.HasPrefix(s, "@Contact") {
rootapi.Infos.Contact = strings.TrimSpace(s[len("@Contact"):]) rootapi.Infos.Contact.EMail = strings.TrimSpace(s[len("@Contact"):])
} else if strings.HasPrefix(s, "@Name") {
rootapi.Infos.Contact.Name = strings.TrimSpace(s[len("@Name"):])
} else if strings.HasPrefix(s, "@URL") {
rootapi.Infos.Contact.URL = strings.TrimSpace(s[len("@URL"):])
} else if strings.HasPrefix(s, "@License") { } else if strings.HasPrefix(s, "@License") {
rootapi.Infos.License = strings.TrimSpace(s[len("@License"):]) if rootapi.Infos.License == nil {
rootapi.Infos.License = &swagger.License{Name: strings.TrimSpace(s[len("@License"):])}
} else {
rootapi.Infos.License.Name = strings.TrimSpace(s[len("@License"):])
}
} else if strings.HasPrefix(s, "@LicenseUrl") { } else if strings.HasPrefix(s, "@LicenseUrl") {
rootapi.Infos.LicenseUrl = strings.TrimSpace(s[len("@LicenseUrl"):]) if rootapi.Infos.License == nil {
rootapi.Infos.License = &swagger.License{URL: strings.TrimSpace(s[len("@LicenseUrl"):])}
} else {
rootapi.Infos.License.URL = strings.TrimSpace(s[len("@LicenseUrl"):])
}
} else if strings.HasPrefix(s, "@Schemes") {
rootapi.Schemes = strings.Split(strings.TrimSpace(s[len("@Schemes"):]), ",")
} else if strings.HasPrefix(s, "@Host") {
rootapi.Host = strings.TrimSpace(s[len("@Host"):])
} }
} }
} }
} }
// analisys controller package
for _, im := range f.Imports { for _, im := range f.Imports {
localName := "" localName := ""
if im.Name != nil { if im.Name != nil {
@ -159,33 +121,46 @@ func generateDocs(curpath string) {
switch specDecl := d.(type) { switch specDecl := d.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
for _, l := range specDecl.Body.List { for _, l := range specDecl.Body.List {
switch smtp := l.(type) { switch stmt := l.(type) {
case *ast.AssignStmt: case *ast.AssignStmt:
for _, l := range smtp.Rhs { for _, l := range stmt.Rhs {
if v, ok := l.(*ast.CallExpr); ok { if v, ok := l.(*ast.CallExpr); ok {
f, params := analisysNewNamespace(v) // analisys NewNamespace, it will return version and the subfunction
globalDocsTemplate = strings.Replace(globalDocsTemplate, "{{.version}}", f, -1) if selName := v.Fun.(*ast.SelectorExpr).Sel.String(); selName != "NewNamespace" {
continue
}
version, params := analisysNewNamespace(v)
if rootapi.BasePath == "" && version != "" {
rootapi.BasePath = version
}
for _, p := range params { for _, p := range params {
switch pp := p.(type) { switch pp := p.(type) {
case *ast.CallExpr: case *ast.CallExpr:
controllerName := ""
if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" { if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" {
s, params := analisysNewNamespace(pp) s, params := analisysNewNamespace(pp)
subapi := swagger.ApiRef{Path: s}
controllerName := ""
for _, sp := range params { for _, sp := range params {
switch pp := sp.(type) { switch pp := sp.(type) {
case *ast.CallExpr: case *ast.CallExpr:
if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" { if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" {
controllerName = analisysNSInclude(s, pp) controllerName = analisysNSInclude(s, pp)
if v, ok := controllerComments[controllerName]; ok {
rootapi.Tags = append(rootapi.Tags, swagger.Tag{
Name: strings.Trim(s, "/"),
Description: v,
})
}
} }
} }
} }
if v, ok := controllerComments[controllerName]; ok {
subapi.Description = v
}
rootapi.Apis = append(rootapi.Apis, subapi)
} else if selname == "NSInclude" { } else if selname == "NSInclude" {
analisysNSInclude(f, pp) controllerName = analisysNSInclude("", pp)
if v, ok := controllerComments[controllerName]; ok {
rootapi.Tags = append(rootapi.Tags, swagger.Tag{
Name: controllerName, // if the NSInclude has no prefix, we use the controllername as the tag
Description: v,
})
}
} }
} }
} }
@ -196,25 +171,27 @@ func generateDocs(curpath string) {
} }
} }
} }
apiinfo, err := json.Marshal(rootapi) os.Mkdir(path.Join(curpath, "swagger"), 0755)
if err != nil { fd, err := os.Create(path.Join(curpath, "swagger", "swagger.json"))
panic(err) fdyml, err := os.Create(path.Join(curpath, "swagger", "swagger.yml"))
}
subapi, err := json.Marshal(apilist)
if err != nil {
panic(err)
}
os.Mkdir(path.Join(curpath, "docs"), 0755)
fd, err := os.Create(path.Join(curpath, "docs", "docs.go"))
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer fdyml.Close()
defer fd.Close() defer fd.Close()
a := strings.Replace(globalDocsTemplate, "{{.rootinfo}}", "`"+string(apiinfo)+"`", -1) dt, err := json.MarshalIndent(rootapi, "", " ")
a = strings.Replace(a, "{{.subapi}}", "`"+string(subapi)+"`", -1) dtyml, erryml := yaml.Marshal(rootapi)
fd.WriteString(a) if err != nil || erryml != nil {
panic(err)
}
_, err = fd.Write(dt)
_, erryml = fdyml.Write(dtyml)
if err != nil || erryml != nil {
panic(err)
}
} }
// return version and the others params
func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) { func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) {
for i, p := range ce.Args { for i, p := range ce.Args {
if i == 0 { if i == 0 {
@ -231,32 +208,49 @@ func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) {
func analisysNSInclude(baseurl string, ce *ast.CallExpr) string { func analisysNSInclude(baseurl string, ce *ast.CallExpr) string {
cname := "" cname := ""
a := &swagger.ApiDeclaration{}
a.ApiVersion = rootapi.ApiVersion
a.SwaggerVersion = swagger.SwaggerVersion
a.ResourcePath = baseurl
a.Produces = []string{"application/json", "application/xml", "text/plain", "text/html"}
a.Apis = make([]swagger.Api, 0)
a.Models = make(map[string]swagger.Model)
for _, p := range ce.Args { for _, p := range ce.Args {
x := p.(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.SelectorExpr) x := p.(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.SelectorExpr)
if v, ok := importlist[fmt.Sprint(x.X)]; ok { if v, ok := importlist[fmt.Sprint(x.X)]; ok {
cname = v + x.Sel.Name cname = v + x.Sel.Name
} }
if apis, ok := controllerList[cname]; ok { if apis, ok := controllerList[cname]; ok {
if len(a.Apis) > 0 { for rt, item := range apis {
a.Apis = append(a.Apis, apis...) tag := ""
} else { if baseurl != "" {
a.Apis = apis rt = baseurl + rt
} tag = strings.Trim(baseurl, "/")
} } else {
if models, ok := modelsList[cname]; ok { tag = cname
for _, m := range models { }
a.Models[m.Id] = m if item.Get != nil {
item.Get.Tags = []string{tag}
}
if item.Post != nil {
item.Post.Tags = []string{tag}
}
if item.Put != nil {
item.Put.Tags = []string{tag}
}
if item.Patch != nil {
item.Patch.Tags = []string{tag}
}
if item.Head != nil {
item.Head.Tags = []string{tag}
}
if item.Delete != nil {
item.Delete.Tags = []string{tag}
}
if item.Options != nil {
item.Options.Tags = []string{tag}
}
if len(rootapi.Paths) == 0 {
rootapi.Paths = make(map[string]*swagger.Item)
}
rt = urlReplace(rt)
rootapi.Paths[rt] = item
} }
} }
} }
apilist[baseurl] = a
return cname return cname
} }
@ -265,15 +259,15 @@ func analisyscontrollerPkg(localName, pkgpath string) {
if isSystemPackage(pkgpath) { if isSystemPackage(pkgpath) {
return return
} }
if pkgpath == "github.com/astaxie/beego" {
return
}
if localName != "" { if localName != "" {
importlist[localName] = pkgpath importlist[localName] = pkgpath
} else { } else {
pps := strings.Split(pkgpath, "/") pps := strings.Split(pkgpath, "/")
importlist[pps[len(pps)-1]] = pkgpath importlist[pps[len(pps)-1]] = pkgpath
} }
if pkgpath == "github.com/astaxie/beego" {
return
}
gopath := os.Getenv("GOPATH") gopath := os.Getenv("GOPATH")
if gopath == "" { if gopath == "" {
panic("please set gopath") panic("please set gopath")
@ -292,6 +286,7 @@ func analisyscontrollerPkg(localName, pkgpath string) {
if _, ok := pkgCache[pkgpath]; ok { if _, ok := pkgCache[pkgpath]; ok {
return return
} }
pkgCache[pkgpath] = struct{}{}
} else { } else {
ColorLog("[ERRO] the %s pkg not exist in gopath\n", pkgpath) ColorLog("[ERRO] the %s pkg not exist in gopath\n", pkgpath)
os.Exit(1) os.Exit(1)
@ -313,16 +308,20 @@ func analisyscontrollerPkg(localName, pkgpath string) {
case *ast.FuncDecl: case *ast.FuncDecl:
if specDecl.Recv != nil && len(specDecl.Recv.List) > 0 { if specDecl.Recv != nil && len(specDecl.Recv.List) > 0 {
if t, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr); ok { if t, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr); ok {
// parse controller method
parserComments(specDecl.Doc, specDecl.Name.String(), fmt.Sprint(t.X), pkgpath) parserComments(specDecl.Doc, specDecl.Name.String(), fmt.Sprint(t.X), pkgpath)
} }
} }
case *ast.GenDecl: case *ast.GenDecl:
if specDecl.Tok.String() == "type" { if specDecl.Tok == token.TYPE {
for _, s := range specDecl.Specs { for _, s := range specDecl.Specs {
switch tp := s.(*ast.TypeSpec).Type.(type) { switch tp := s.(*ast.TypeSpec).Type.(type) {
case *ast.StructType: case *ast.StructType:
_ = tp.Struct _ = tp.Struct
controllerComments[pkgpath+s.(*ast.TypeSpec).Name.String()] = specDecl.Doc.Text() //parse controller definition comments
if strings.TrimSpace(specDecl.Doc.Text()) != "" {
controllerComments[pkgpath+s.(*ast.TypeSpec).Name.String()] = specDecl.Doc.Text()
}
} }
} }
} }
@ -351,10 +350,24 @@ func isSystemPackage(pkgpath string) bool {
return false return false
} }
func peekNextSplitString(ss string) (s string, spacePos int) {
spacePos = strings.IndexFunc(ss, unicode.IsSpace)
if spacePos < 0 {
s = ss
spacePos = len(ss)
} else {
s = strings.TrimSpace(ss[:spacePos])
}
return
}
// parse the func comments // parse the func comments
func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error { func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error {
innerapi := swagger.Api{} var routerPath string
opts := swagger.Operation{} var HTTPMethod string
opts := swagger.Operation{
Responses: make(map[string]swagger.Response),
}
if comments != nil && comments.List != nil { if comments != nil && comments.List != nil {
for _, c := range comments.List { for _, c := range comments.List {
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//")) t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
@ -364,66 +377,64 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
if len(e1) < 1 { if len(e1) < 1 {
return errors.New("you should has router infomation") return errors.New("you should has router infomation")
} }
innerapi.Path = e1[0] routerPath = e1[0]
if len(e1) == 2 && e1[1] != "" { if len(e1) == 2 && e1[1] != "" {
e1 = strings.SplitN(e1[1], " ", 2) e1 = strings.SplitN(e1[1], " ", 2)
opts.HttpMethod = strings.ToUpper(strings.Trim(e1[0], "[]")) HTTPMethod = strings.ToUpper(strings.Trim(e1[0], "[]"))
} else { } else {
opts.HttpMethod = "GET" HTTPMethod = "GET"
} }
} else if strings.HasPrefix(t, "@Title") { } else if strings.HasPrefix(t, "@Title") {
opts.Nickname = strings.TrimSpace(t[len("@Title"):]) opts.OperationID = controllerName + "." + strings.TrimSpace(t[len("@Title"):])
} else if strings.HasPrefix(t, "@Description") { } else if strings.HasPrefix(t, "@Description") {
opts.Summary = strings.TrimSpace(t[len("@Description"):]) opts.Description = strings.TrimSpace(t[len("@Description"):])
} else if strings.HasPrefix(t, "@Summary") {
opts.Summary = strings.TrimSpace(t[len("@Summary"):])
} else if strings.HasPrefix(t, "@Success") { } else if strings.HasPrefix(t, "@Success") {
ss := strings.TrimSpace(t[len("@Success"):]) ss := strings.TrimSpace(t[len("@Success"):])
rs := swagger.ResponseMessage{} rs := swagger.Response{}
st := make([]string, 3) respCode, pos := peekNextSplitString(ss)
j := 0 ss = strings.TrimSpace(ss[pos:])
var tmp []rune respType, pos := peekNextSplitString(ss)
start := false if respType == "{object}" || respType == "{array}" {
isArray := respType == "{array}"
for i, c := range ss { ss = strings.TrimSpace(ss[pos:])
if unicode.IsSpace(c) { schemaName, pos := peekNextSplitString(ss)
if !start && j < 2 { if schemaName == "" {
continue ColorLog("[ERRO][%s.%s] Schema must follow {object} or {array}\n", controllerName, funcName)
os.Exit(-1)
}
if strings.HasPrefix(schemaName, "[]") {
schemaName = schemaName[2:]
isArray = true
}
schema := swagger.Schema{}
if sType, ok := basicTypes[schemaName]; ok {
typeFormat := strings.Split(sType, ":")
schema.Type = typeFormat[0]
schema.Format = typeFormat[1]
} else {
cmpath, m, mod, realTypes := getModel(schemaName)
schema.Ref = "#/definitions/" + m
if _, ok := modelsList[pkgpath+controllerName]; !ok {
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0)
} }
if j == 0 || j == 1 { modelsList[pkgpath+controllerName][schemaName] = mod
st[j] = string(tmp) appendModels(cmpath, pkgpath, controllerName, realTypes)
tmp = make([]rune, 0) }
j += 1 if isArray {
start = false rs.Schema = &swagger.Schema{
continue Type: "array",
} else { Items: &schema,
st[j] = strings.TrimSpace(ss[i+1:])
break
} }
} else { } else {
start = true rs.Schema = &schema
tmp = append(tmp, c)
} }
rs.Description = strings.TrimSpace(ss[pos:])
} else {
rs.Description = strings.TrimSpace(ss)
} }
if len(tmp) > 0 && st[2] == "" { opts.Responses[respCode] = rs
st[2] = strings.TrimSpace(string(tmp))
}
rs.Message = st[2]
if st[1] == "{object}" {
if st[2] == "" {
panic(controllerName + " " + funcName + " has no object")
}
cmpath, m, mod, realTypes := getModel(st[2])
//ll := strings.Split(st[2], ".")
//opts.Type = ll[len(ll)-1]
rs.ResponseModel = m
if _, ok := modelsList[pkgpath+controllerName]; !ok {
modelsList[pkgpath+controllerName] = make(map[string]swagger.Model, 0)
}
modelsList[pkgpath+controllerName][st[2]] = mod
appendModels(cmpath, pkgpath, controllerName, realTypes)
}
rs.Code, _ = strconv.Atoi(st[0])
opts.ResponseMessages = append(opts.ResponseMessages, rs)
} else if strings.HasPrefix(t, "@Param") { } else if strings.HasPrefix(t, "@Param") {
para := swagger.Parameter{} para := swagger.Parameter{}
p := getparams(strings.TrimSpace(t[len("@Param "):])) p := getparams(strings.TrimSpace(t[len("@Param "):]))
@ -431,25 +442,78 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
panic(controllerName + "_" + funcName + "'s comments @Param at least should has 4 params") panic(controllerName + "_" + funcName + "'s comments @Param at least should has 4 params")
} }
para.Name = p[0] para.Name = p[0]
para.ParamType = p[1] switch p[1] {
case "query":
fallthrough
case "header":
fallthrough
case "path":
fallthrough
case "formData":
fallthrough
case "body":
break
default:
ColorLog("[WARN][%s.%s] Unknow param location: %s, Possible values are `query`, `header`, `path`, `formData` or `body`.\n", controllerName, funcName, p[1])
}
para.In = p[1]
pp := strings.Split(p[2], ".") pp := strings.Split(p[2], ".")
para.DataType = pp[len(pp)-1] typ := pp[len(pp)-1]
if len(pp) >= 2 {
cmpath, m, mod, realTypes := getModel(p[2])
para.Schema = &swagger.Schema{
Ref: "#/definitions/" + m,
}
if _, ok := modelsList[pkgpath+controllerName]; !ok {
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0)
}
modelsList[pkgpath+controllerName][typ] = mod
appendModels(cmpath, pkgpath, controllerName, realTypes)
} else {
isArray := false
paraType := ""
paraFormat := ""
if strings.HasPrefix(typ, "[]") {
typ = typ[2:]
isArray = true
}
if typ == "string" || typ == "number" || typ == "integer" || typ == "boolean" ||
typ == "array" || typ == "file" {
paraType = typ
} else if sType, ok := basicTypes[typ]; ok {
typeFormat := strings.Split(sType, ":")
paraType = typeFormat[0]
paraFormat = typeFormat[1]
} else {
ColorLog("[WARN][%s.%s] Unknow param type: %s\n", controllerName, funcName, typ)
}
if isArray {
para.Type = "array"
para.Items = &swagger.ParameterItems{
Type: paraType,
Format: paraFormat,
}
} else {
para.Type = paraType
para.Format = paraFormat
}
}
if len(p) > 4 { if len(p) > 4 {
para.Required, _ = strconv.ParseBool(p[3]) para.Required, _ = strconv.ParseBool(p[3])
para.Description = p[4] para.Description = strings.Trim(p[4], `" `)
} else { } else {
para.Description = p[3] para.Description = strings.Trim(p[3], `" `)
} }
opts.Parameters = append(opts.Parameters, para) opts.Parameters = append(opts.Parameters, para)
} else if strings.HasPrefix(t, "@Failure") { } else if strings.HasPrefix(t, "@Failure") {
rs := swagger.ResponseMessage{} rs := swagger.Response{}
st := strings.TrimSpace(t[len("@Failure"):]) st := strings.TrimSpace(t[len("@Failure"):])
var cd []rune var cd []rune
var start bool var start bool
for i, s := range st { for i, s := range st {
if unicode.IsSpace(s) { if unicode.IsSpace(s) {
if start { if start {
rs.Message = strings.TrimSpace(st[i+1:]) rs.Description = strings.TrimSpace(st[i+1:])
break break
} else { } else {
continue continue
@ -458,10 +522,9 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
start = true start = true
cd = append(cd, s) cd = append(cd, s)
} }
rs.Code, _ = strconv.Atoi(string(cd)) opts.Responses[string(cd)] = rs
opts.ResponseMessages = append(opts.ResponseMessages, rs) } else if strings.HasPrefix(t, "@Deprecated") {
} else if strings.HasPrefix(t, "@Type") { opts.Deprecated, _ = strconv.ParseBool(strings.TrimSpace(t[len("@Deprecated"):]))
opts.Type = strings.TrimSpace(t[len("@Type"):])
} else if strings.HasPrefix(t, "@Accept") { } else if strings.HasPrefix(t, "@Accept") {
accepts := strings.Split(strings.TrimSpace(strings.TrimSpace(t[len("@Accept"):])), ",") accepts := strings.Split(strings.TrimSpace(strings.TrimSpace(t[len("@Accept"):])), ",")
for _, a := range accepts { for _, a := range accepts {
@ -483,14 +546,35 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
} }
} }
} }
innerapi.Operations = append(innerapi.Operations, opts) if routerPath != "" {
if innerapi.Path != "" { var item *swagger.Item
if _, ok := controllerList[pkgpath+controllerName]; ok { if itemList, ok := controllerList[pkgpath+controllerName]; ok {
controllerList[pkgpath+controllerName] = append(controllerList[pkgpath+controllerName], innerapi) if it, ok := itemList[routerPath]; !ok {
item = &swagger.Item{}
} else {
item = it
}
} else { } else {
controllerList[pkgpath+controllerName] = make([]swagger.Api, 1) controllerList[pkgpath+controllerName] = make(map[string]*swagger.Item)
controllerList[pkgpath+controllerName][0] = innerapi item = &swagger.Item{}
} }
switch HTTPMethod {
case "GET":
item.Get = &opts
case "POST":
item.Post = &opts
case "PUT":
item.Put = &opts
case "PATCH":
item.Patch = &opts
case "DELETE":
item.Delete = &opts
case "HEAD":
item.Head = &opts
case "OPTIONS":
item.Options = &opts
}
controllerList[pkgpath+controllerName][routerPath] = item
} }
return nil return nil
} }
@ -526,7 +610,7 @@ func getparams(str string) []string {
return r return r
} }
func getModel(str string) (pkgpath, objectname string, m swagger.Model, realTypes []string) { func getModel(str string) (pkgpath, objectname string, m swagger.Schema, realTypes []string) {
strs := strings.Split(str, ".") strs := strings.Split(str, ".")
objectname = strs[len(strs)-1] objectname = strs[len(strs)-1]
pkgpath = strings.Join(strs[:len(strs)-1], "/") pkgpath = strings.Join(strs[:len(strs)-1], "/")
@ -542,7 +626,7 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Model, realType
ColorLog("[ERRO] the model %s parser.ParseDir error\n", str) ColorLog("[ERRO] the model %s parser.ParseDir error\n", str)
os.Exit(1) os.Exit(1)
} }
m.Type = "object"
for _, pkg := range astPkgs { for _, pkg := range astPkgs {
for _, fl := range pkg.Files { for _, fl := range pkg.Files {
for k, d := range fl.Scope.Objects { for k, d := range fl.Scope.Objects {
@ -550,140 +634,179 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Model, realType
if k != objectname { if k != objectname {
continue continue
} }
ts, ok := d.Decl.(*ast.TypeSpec) parseObject(d, k, &m, &realTypes, astPkgs)
if !ok {
ColorLog("Unknown type without TypeSec: %v", d)
os.Exit(1)
}
st, ok := ts.Type.(*ast.StructType)
if !ok {
continue
}
m.Id = k
if st.Fields.List != nil {
m.Properties = make(map[string]swagger.ModelProperty)
for _, field := range st.Fields.List {
isSlice, realType := typeAnalyser(field)
realTypes = append(realTypes, realType)
mp := swagger.ModelProperty{}
// add type slice
if isSlice {
if isBasicType(realType) {
mp.Type = "[]" + realType
} else {
mp.Type = "array"
mp.Items = make(map[string]string)
mp.Items["$ref"] = realType
}
} else {
mp.Type = realType
}
// dont add property if anonymous field
if field.Names != nil {
// set property name as field name
var name = field.Names[0].Name
// if no tag skip tag processing
if field.Tag == nil {
m.Properties[name] = mp
continue
}
var tagValues []string
stag := reflect.StructTag(strings.Trim(field.Tag.Value, "`"))
tag := stag.Get("json")
if tag != "" {
tagValues = strings.Split(tag, ",")
}
// dont add property if json tag first value is "-"
if len(tagValues) == 0 || tagValues[0] != "-" {
// set property name to the left most json tag value only if is not omitempty
if len(tagValues) > 0 && tagValues[0] != "omitempty" {
name = tagValues[0]
}
if thrifttag := stag.Get("thrift"); thrifttag != "" {
ts := strings.Split(thrifttag, ",")
if ts[0] != "" {
name = ts[0]
}
}
if required := stag.Get("required"); required != "" {
m.Required = append(m.Required, name)
}
if desc := stag.Get("description"); desc != "" {
mp.Description = desc
}
m.Properties[name] = mp
}
if ignore := stag.Get("ignore"); ignore != "" {
continue
}
}
}
}
return
} }
} }
} }
} }
if m.Id == "" { if m.Title == "" {
ColorLog("can't find the object: %v", str) ColorLog("[WARN]can't find the object: %s\n", str)
os.Exit(1) // TODO remove when all type have been supported
//os.Exit(1)
} }
if len(rootapi.Definitions) == 0 {
rootapi.Definitions = make(map[string]swagger.Schema)
}
rootapi.Definitions[objectname] = m
return return
} }
func typeAnalyser(f *ast.Field) (isSlice bool, realType string) { func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string, astPkgs map[string]*ast.Package) {
if arr, ok := f.Type.(*ast.ArrayType); ok { ts, ok := d.Decl.(*ast.TypeSpec)
if isBasicType(fmt.Sprint(arr.Elt)) { if !ok {
return false, fmt.Sprintf("[]%v", arr.Elt) ColorLog("Unknown type without TypeSec: %v\n", d)
os.Exit(1)
}
// TODO support other types, such as `ArrayType`, `MapType`, `InterfaceType` etc...
st, ok := ts.Type.(*ast.StructType)
if !ok {
return
}
m.Title = k
if st.Fields.List != nil {
m.Properties = make(map[string]swagger.Propertie)
for _, field := range st.Fields.List {
isSlice, realType, sType := typeAnalyser(field)
*realTypes = append(*realTypes, realType)
mp := swagger.Propertie{}
if isSlice {
mp.Type = "array"
if isBasicType(realType) {
typeFormat := strings.Split(sType, ":")
mp.Items = &swagger.Propertie{
Type: typeFormat[0],
Format: typeFormat[1],
}
} else {
mp.Items = &swagger.Propertie{
Ref: "#/definitions/" + realType,
}
}
} else {
if sType == "object" {
mp.Ref = "#/definitions/" + realType
} else if isBasicType(realType) {
typeFormat := strings.Split(sType, ":")
mp.Type = typeFormat[0]
mp.Format = typeFormat[1]
} else if realType == "map" {
typeFormat := strings.Split(sType, ":")
mp.AdditionalProperties = &swagger.Propertie{
Type: typeFormat[0],
Format: typeFormat[1],
}
}
}
if field.Names != nil {
// set property name as field name
var name = field.Names[0].Name
// if no tag skip tag processing
if field.Tag == nil {
m.Properties[name] = mp
continue
}
var tagValues []string
stag := reflect.StructTag(strings.Trim(field.Tag.Value, "`"))
tag := stag.Get("json")
if tag != "" {
tagValues = strings.Split(tag, ",")
}
// dont add property if json tag first value is "-"
if len(tagValues) == 0 || tagValues[0] != "-" {
// set property name to the left most json tag value only if is not omitempty
if len(tagValues) > 0 && tagValues[0] != "omitempty" {
name = tagValues[0]
}
if thrifttag := stag.Get("thrift"); thrifttag != "" {
ts := strings.Split(thrifttag, ",")
if ts[0] != "" {
name = ts[0]
}
}
if required := stag.Get("required"); required != "" {
m.Required = append(m.Required, name)
}
if desc := stag.Get("description"); desc != "" {
mp.Description = desc
}
m.Properties[name] = mp
}
if ignore := stag.Get("ignore"); ignore != "" {
continue
}
} else {
for _, pkg := range astPkgs {
for _, fl := range pkg.Files {
for nameOfObj, obj := range fl.Scope.Objects {
if obj.Name == fmt.Sprint(field.Type) {
parseObject(obj, nameOfObj, m, realTypes, astPkgs)
}
}
}
}
}
} }
if mp, ok := arr.Elt.(*ast.MapType); ok {
return false, fmt.Sprintf("map[%v][%v]", mp.Key, mp.Value)
}
if star, ok := arr.Elt.(*ast.StarExpr); ok {
return true, fmt.Sprint(star.X)
} else {
return true, fmt.Sprint(arr.Elt)
}
} else {
switch t := f.Type.(type) {
case *ast.StarExpr:
return false, fmt.Sprint(t.X)
}
return false, fmt.Sprint(f.Type)
} }
} }
func isBasicType(Type string) bool { func typeAnalyser(f *ast.Field) (isSlice bool, realType, swaggerType string) {
for _, v := range basicTypes { if arr, ok := f.Type.(*ast.ArrayType); ok {
if v == Type { if isBasicType(fmt.Sprint(arr.Elt)) {
return true return false, fmt.Sprintf("[]%v", arr.Elt), basicTypes[fmt.Sprint(arr.Elt)]
} }
if mp, ok := arr.Elt.(*ast.MapType); ok {
return false, fmt.Sprintf("map[%v][%v]", mp.Key, mp.Value), "object"
}
if star, ok := arr.Elt.(*ast.StarExpr); ok {
return true, fmt.Sprint(star.X), "object"
}
return true, fmt.Sprint(arr.Elt), "object"
}
switch t := f.Type.(type) {
case *ast.StarExpr:
return false, fmt.Sprint(t.X), "object"
case *ast.MapType:
val := fmt.Sprintf("%v", t.Value)
if isBasicType(val) {
return false, "map", basicTypes[val]
}
return false, val, "object"
}
if k, ok := basicTypes[fmt.Sprint(f.Type)]; ok {
return false, fmt.Sprint(f.Type), k
}
return false, fmt.Sprint(f.Type), "object"
}
func isBasicType(Type string) bool {
if _, ok := basicTypes[Type]; ok {
return true
} }
return false return false
} }
// refer to builtin.go // refer to builtin.go
var basicTypes = []string{ var basicTypes = map[string]string{
"bool", "bool": "boolean:",
"uint", "uint8", "uint16", "uint32", "uint64", "uint": "integer:int32", "uint8": "integer:int32", "uint16": "integer:int32", "uint32": "integer:int32", "uint64": "integer:int64",
"int", "int8", "int16", "int32", "int64", "int": "integer:int64", "int8": "integer:int32", "int16:int32": "integer:int32", "int32": "integer:int32", "int64": "integer:int64",
"float32", "float64", "uintptr": "integer:int64",
"string", "float32": "number:float", "float64": "number:double",
"complex64", "complex128", "string": "string:",
"byte", "rune", "uintptr", "complex64": "number:float", "complex128": "number:double",
"byte": "string:byte", "rune": "string:byte",
} }
// regexp get json tag // regexp get json tag
func grepJsonTag(tag string) string { func grepJSONTag(tag string) string {
r, _ := regexp.Compile(`json:"([^"]*)"`) r, _ := regexp.Compile(`json:"([^"]*)"`)
matches := r.FindAllStringSubmatch(tag, -1) matches := r.FindAllStringSubmatch(tag, -1)
if len(matches) > 0 { if len(matches) > 0 {
@ -713,3 +836,17 @@ func appendModels(cmpath, pkgpath, controllerName string, realTypes []string) {
} }
} }
} }
func urlReplace(src string) string {
pt := strings.Split(src, "/")
for i, p := range pt {
if len(p) > 0 {
if p[0] == ':' {
pt[i] = "{" + p[1:] + "}"
} else if p[0] == '?' && p[1] == ':' {
pt[i] = "{" + p[2:] + "}"
}
}
}
return strings.Join(pt, "/")
}

View File

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

View File

@ -23,15 +23,170 @@ import (
) )
const ( const (
M_PATH = "migrations" MPath = "migrations"
M_DATE_FORMAT = "20060102_150405" MDateFormat = "20060102_150405"
DBPath = "database"
) )
type DBDriver interface {
generateCreateUp(tableName string) string
generateCreateDown(tableName string) string
}
type mysqlDriver struct{}
func (m mysqlDriver) generateCreateUp(tableName string) string {
upsql := `m.SQL("CREATE TABLE ` + tableName + "(" + m.generateSQLFromFields(fields.String()) + `)");`
return upsql
}
func (m mysqlDriver) generateCreateDown(tableName string) string {
downsql := `m.SQL("DROP TABLE ` + "`" + tableName + "`" + `")`
return downsql
}
func (m mysqlDriver) generateSQLFromFields(fields string) string {
sql, tags := "", ""
fds := strings.Split(fields, ",")
for i, v := range fds {
kv := strings.SplitN(v, ":", 2)
if len(kv) != 2 {
ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n")
return ""
}
typ, tag := m.getSQLType(kv[1])
if typ == "" {
ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n")
return ""
}
if i == 0 && strings.ToLower(kv[0]) != "id" {
sql += "`id` int(11) NOT NULL AUTO_INCREMENT,"
tags = tags + "PRIMARY KEY (`id`),"
}
sql += "`" + snakeString(kv[0]) + "` " + typ + ","
if tag != "" {
tags = tags + fmt.Sprintf(tag, "`"+snakeString(kv[0])+"`") + ","
}
}
sql = strings.TrimRight(sql+tags, ",")
return sql
}
func (m mysqlDriver) getSQLType(ktype string) (tp, tag string) {
kv := strings.SplitN(ktype, ":", 2)
switch kv[0] {
case "string":
if len(kv) == 2 {
return "varchar(" + kv[1] + ") NOT NULL", ""
}
return "varchar(128) NOT NULL", ""
case "text":
return "longtext NOT NULL", ""
case "auto":
return "int(11) NOT NULL AUTO_INCREMENT", ""
case "pk":
return "int(11) NOT NULL", "PRIMARY KEY (%s)"
case "datetime":
return "datetime NOT NULL", ""
case "int", "int8", "int16", "int32", "int64":
fallthrough
case "uint", "uint8", "uint16", "uint32", "uint64":
return "int(11) DEFAULT NULL", ""
case "bool":
return "tinyint(1) NOT NULL", ""
case "float32", "float64":
return "float NOT NULL", ""
case "float":
return "float NOT NULL", ""
}
return "", ""
}
type postgresqlDriver struct{}
func (m postgresqlDriver) generateCreateUp(tableName string) string {
upsql := `m.SQL("CREATE TABLE ` + tableName + "(" + m.generateSQLFromFields(fields.String()) + `)");`
return upsql
}
func (m postgresqlDriver) generateCreateDown(tableName string) string {
downsql := `m.SQL("DROP TABLE ` + tableName + `")`
return downsql
}
func (m postgresqlDriver) generateSQLFromFields(fields string) string {
sql, tags := "", ""
fds := strings.Split(fields, ",")
for i, v := range fds {
kv := strings.SplitN(v, ":", 2)
if len(kv) != 2 {
ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n")
return ""
}
typ, tag := m.getSQLType(kv[1])
if typ == "" {
ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n")
return ""
}
if i == 0 && strings.ToLower(kv[0]) != "id" {
sql += "id serial primary key,"
}
sql += snakeString(kv[0]) + " " + typ + ","
if tag != "" {
tags = tags + fmt.Sprintf(tag, snakeString(kv[0])) + ","
}
}
if tags != "" {
sql = strings.TrimRight(sql+" "+tags, ",")
} else {
sql = strings.TrimRight(sql, ",")
}
return sql
}
func (m postgresqlDriver) getSQLType(ktype string) (tp, tag string) {
kv := strings.SplitN(ktype, ":", 2)
switch kv[0] {
case "string":
if len(kv) == 2 {
return "char(" + kv[1] + ") NOT NULL", ""
}
return "TEXT NOT NULL", ""
case "text":
return "TEXT NOT NULL", ""
case "auto", "pk":
return "serial primary key", ""
case "datetime":
return "TIMESTAMP WITHOUT TIME ZONE NOT NULL", ""
case "int", "int8", "int16", "int32", "int64":
fallthrough
case "uint", "uint8", "uint16", "uint32", "uint64":
return "integer DEFAULT NULL", ""
case "bool":
return "boolean NOT NULL", ""
case "float32", "float64", "float":
return "numeric NOT NULL", ""
}
return "", ""
}
func newDBDriver() DBDriver {
switch driver {
case "mysql":
return mysqlDriver{}
case "postgres":
return postgresqlDriver{}
default:
panic("driver not supported")
}
}
// generateMigration generates migration file template for database schema update. // generateMigration generates migration file template for database schema update.
// The generated file template consists of an up() method for updating schema and // The generated file template consists of an up() method for updating schema and
// a down() method for reverting the update. // a down() method for reverting the update.
func generateMigration(mname, upsql, downsql, curpath string) { func generateMigration(mname, upsql, downsql, curpath string) {
migrationFilePath := path.Join(curpath, "database", M_PATH) w := NewColorWriter(os.Stdout)
migrationFilePath := path.Join(curpath, DBPath, MPath)
if _, err := os.Stat(migrationFilePath); os.IsNotExist(err) { if _, err := os.Stat(migrationFilePath); os.IsNotExist(err) {
// create migrations directory // create migrations directory
if err := os.MkdirAll(migrationFilePath, 0777); err != nil { if err := os.MkdirAll(migrationFilePath, 0777); err != nil {
@ -40,26 +195,25 @@ func generateMigration(mname, upsql, downsql, curpath string) {
} }
} }
// create file // create file
today := time.Now().Format(M_DATE_FORMAT) today := time.Now().Format(MDateFormat)
fpath := path.Join(migrationFilePath, fmt.Sprintf("%s_%s.go", today, mname)) fpath := path.Join(migrationFilePath, fmt.Sprintf("%s_%s.go", today, mname))
if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer f.Close() defer CloseFile(f)
content := strings.Replace(MIGRATION_TPL, "{{StructName}}", camelCase(mname)+"_"+today, -1) content := strings.Replace(MigrationTPL, "{{StructName}}", camelCase(mname)+"_"+today, -1)
content = strings.Replace(content, "{{CurrTime}}", today, -1) content = strings.Replace(content, "{{CurrTime}}", today, -1)
content = strings.Replace(content, "{{UpSQL}}", upsql, -1) content = strings.Replace(content, "{{UpSQL}}", upsql, -1)
content = strings.Replace(content, "{{DownSQL}}", downsql, -1) content = strings.Replace(content, "{{DownSQL}}", downsql, -1)
f.WriteString(content) f.WriteString(content)
// gofmt generated source code // Run 'gofmt' on the generated source code
formatSourceCode(fpath) formatSourceCode(fpath)
ColorLog("[INFO] Migration file generated: %s\n", fpath) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
} else { } else {
// error creating file
ColorLog("[ERRO] Could not create migration file: %s\n", err) ColorLog("[ERRO] Could not create migration file: %s\n", err)
os.Exit(2) os.Exit(2)
} }
} }
const MIGRATION_TPL = `package main const MigrationTPL = `package main
import ( import (
"github.com/astaxie/beego/migration" "github.com/astaxie/beego/migration"
@ -79,13 +233,13 @@ func init() {
// Run the migrations // Run the migrations
func (m *{{StructName}}) Up() { func (m *{{StructName}}) Up() {
// use m.Sql("CREATE TABLE ...") to make schema update // use m.SQL("CREATE TABLE ...") to make schema update
{{UpSQL}} {{UpSQL}}
} }
// Reverse the migrations // Reverse the migrations
func (m *{{StructName}}) Down() { func (m *{{StructName}}) Down() {
// use m.Sql("DROP TABLE ...") to reverse schema update // use m.SQL("DROP TABLE ...") to reverse schema update
{{DownSQL}} {{DownSQL}}
} }
` `

View File

@ -1,13 +1,30 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main package main
import ( import (
"errors" "errors"
"fmt"
"os" "os"
"path" "path"
"strings" "strings"
) )
func generateModel(mname, fields, crupath string) { func generateModel(mname, fields, currpath string) {
w := NewColorWriter(os.Stdout)
p, f := path.Split(mname) p, f := path.Split(mname)
modelName := strings.Title(f) modelName := strings.Title(f)
packageName := "models" packageName := "models"
@ -15,24 +32,28 @@ func generateModel(mname, fields, crupath string) {
i := strings.LastIndex(p[:len(p)-1], "/") i := strings.LastIndex(p[:len(p)-1], "/")
packageName = p[i+1 : len(p)-1] packageName = p[i+1 : len(p)-1]
} }
modelStruct, err, hastime := getStruct(modelName, fields)
modelStruct, hastime, err := getStruct(modelName, fields)
if err != nil { if err != nil {
ColorLog("[ERRO] Could not genrate models struct: %s\n", err) ColorLog("[ERRO] Could not generate the model struct: %s\n", err)
os.Exit(2) os.Exit(2)
} }
ColorLog("[INFO] Using '%s' as model name\n", modelName) ColorLog("[INFO] Using '%s' as model name\n", modelName)
ColorLog("[INFO] Using '%s' as package name\n", packageName) ColorLog("[INFO] Using '%s' as package name\n", packageName)
fp := path.Join(crupath, "models", p)
fp := path.Join(currpath, "models", p)
if _, err := os.Stat(fp); os.IsNotExist(err) { if _, err := os.Stat(fp); os.IsNotExist(err) {
// create controller directory // Create the model's directory
if err := os.MkdirAll(fp, 0777); err != nil { if err := os.MkdirAll(fp, 0777); err != nil {
ColorLog("[ERRO] Could not create models directory: %s\n", err) ColorLog("[ERRO] Could not create the model directory: %s\n", err)
os.Exit(2) os.Exit(2)
} }
} }
fpath := path.Join(fp, strings.ToLower(modelName)+".go") fpath := path.Join(fp, strings.ToLower(modelName)+".go")
if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer f.Close() defer CloseFile(f)
content := strings.Replace(modelTpl, "{{packageName}}", packageName, -1) content := strings.Replace(modelTpl, "{{packageName}}", packageName, -1)
content = strings.Replace(content, "{{modelName}}", modelName, -1) content = strings.Replace(content, "{{modelName}}", modelName, -1)
content = strings.Replace(content, "{{modelStruct}}", modelStruct, -1) content = strings.Replace(content, "{{modelStruct}}", modelStruct, -1)
@ -42,42 +63,45 @@ func generateModel(mname, fields, crupath string) {
content = strings.Replace(content, "{{timePkg}}", "", -1) content = strings.Replace(content, "{{timePkg}}", "", -1)
} }
f.WriteString(content) f.WriteString(content)
// gofmt generated source code // Run 'gofmt' on the generated source code
formatSourceCode(fpath) formatSourceCode(fpath)
ColorLog("[INFO] model file generated: %s\n", fpath) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
} else { } else {
// error creating file
ColorLog("[ERRO] Could not create model file: %s\n", err) ColorLog("[ERRO] Could not create model file: %s\n", err)
os.Exit(2) os.Exit(2)
} }
} }
func getStruct(structname, fields string) (string, error, bool) { func getStruct(structname, fields string) (string, bool, error) {
if fields == "" { if fields == "" {
return "", errors.New("fields can't empty"), false return "", false, errors.New("fields cannot be empty")
} }
hastime := false hastime := false
structStr := "type " + structname + " struct{\n" structStr := "type " + structname + " struct{\n"
fds := strings.Split(fields, ",") fds := strings.Split(fields, ",")
for i, v := range fds { for i, v := range fds {
kv := strings.SplitN(v, ":", 2) kv := strings.SplitN(v, ":", 2)
if len(kv) != 2 { if len(kv) != 2 {
return "", errors.New("the filds format is wrong. should key:type,key:type " + v), false return "", false, errors.New("the fields format is wrong. Should be key:type,key:type " + v)
} }
typ, tag, hastimeinner := getType(kv[1]) typ, tag, hastimeinner := getType(kv[1])
if typ == "" { if typ == "" {
return "", errors.New("the filds format is wrong. should key:type,key:type " + v), false return "", false, errors.New("the fields format is wrong. Should be key:type,key:type " + v)
} }
if i == 0 && strings.ToLower(kv[0]) != "id" { if i == 0 && strings.ToLower(kv[0]) != "id" {
structStr = structStr + "Id int64 `orm:\"auto\"`\n" structStr = structStr + "Id int64 `orm:\"auto\"`\n"
} }
if hastimeinner { if hastimeinner {
hastime = true hastime = true
} }
structStr = structStr + camelString(kv[0]) + " " + typ + " " + tag + "\n" structStr = structStr + camelString(kv[0]) + " " + typ + " " + tag + "\n"
} }
structStr += "}\n" structStr += "}\n"
return structStr, nil, hastime return structStr, hastime, nil
} }
// fields support type // fields support type
@ -88,9 +112,8 @@ func getType(ktype string) (kt, tag string, hasTime bool) {
case "string": case "string":
if len(kv) == 2 { if len(kv) == 2 {
return "string", "`orm:\"size(" + kv[1] + ")\"`", false return "string", "`orm:\"size(" + kv[1] + ")\"`", false
} else {
return "string", "`orm:\"size(128)\"`", false
} }
return "string", "`orm:\"size(128)\"`", false
case "text": case "text":
return "string", "`orm:\"type(longtext)\"`", false return "string", "`orm:\"type(longtext)\"`", false
case "auto": case "auto":
@ -140,7 +163,7 @@ func Add{{modelName}}(m *{{modelName}}) (id int64, err error) {
// Get{{modelName}}ById retrieves {{modelName}} by Id. Returns error if // Get{{modelName}}ById retrieves {{modelName}} by Id. Returns error if
// Id doesn't exist // Id doesn't exist
func Get{{modelName}}ById(id int) (v *{{modelName}}, err error) { func Get{{modelName}}ById(id int64) (v *{{modelName}}, err error) {
o := orm.NewOrm() o := orm.NewOrm()
v = &{{modelName}}{Id: id} v = &{{modelName}}{Id: id}
if err = o.Read(v); err == nil { if err = o.Read(v); err == nil {
@ -202,7 +225,7 @@ func GetAll{{modelName}}(query map[string]string, fields []string, sortby []stri
var l []{{modelName}} var l []{{modelName}}
qs = qs.OrderBy(sortFields...) qs = qs.OrderBy(sortFields...)
if _, err := qs.Limit(limit, offset).All(&l, fields...); err == nil { if _, err = qs.Limit(limit, offset).All(&l, fields...); err == nil {
if len(fields) == 0 { if len(fields) == 0 {
for _, v := range l { for _, v := range l {
ml = append(ml, v) ml = append(ml, v)
@ -240,7 +263,7 @@ func Update{{modelName}}ById(m *{{modelName}}) (err error) {
// Delete{{modelName}} deletes {{modelName}} by Id and returns error if // Delete{{modelName}} deletes {{modelName}} by Id and returns error if
// the record to be deleted doesn't exist // the record to be deleted doesn't exist
func Delete{{modelName}}(id int) (err error) { func Delete{{modelName}}(id int64) (err error) {
o := orm.NewOrm() o := orm.NewOrm()
v := {{modelName}}{Id: id} v := {{modelName}}{Id: id}
// ascertain id exists in the database // ascertain id exists in the database

View File

@ -1,101 +1,48 @@
package main package main
import ( import "strings"
"fmt"
"strings"
)
func generateScaffold(sname, fields, crupath, driver, conn string) { func generateScaffold(sname, fields, currpath, driver, conn string) {
// generate model ColorLog("[INFO] Do you want to create a '%v' model? [Yes|No] ", sname)
ColorLog("[INFO] Do you want me to create a %v model? [yes|no]] ", sname)
// Generate the model
if askForConfirmation() { if askForConfirmation() {
generateModel(sname, fields, crupath) generateModel(sname, fields, currpath)
} }
// generate controller // Generate the controller
ColorLog("[INFO] Do you want me to create a %v controller? [yes|no]] ", sname) ColorLog("[INFO] Do you want to create a '%v' controller? [Yes|No] ", sname)
if askForConfirmation() { if askForConfirmation() {
generateController(sname, crupath) generateController(sname, currpath)
} }
// generate view
ColorLog("[INFO] Do you want me to create views for this %v resource? [yes|no]] ", sname) // Generate the views
ColorLog("[INFO] Do you want to create views for this '%v' resource? [Yes|No] ", sname)
if askForConfirmation() { if askForConfirmation() {
generateView(sname, crupath) generateView(sname, currpath)
} }
// generate migration
ColorLog("[INFO] Do you want me to create a %v migration and schema for this resource? [yes|no]] ", sname) // Generate a migration
ColorLog("[INFO] Do you want to create a '%v' migration and schema for this resource? [Yes|No] ", sname)
if askForConfirmation() { if askForConfirmation() {
upsql := "" upsql := ""
downsql := "" downsql := ""
if fields != "" { if fields != "" {
upsql = `m.Sql("CREATE TABLE ` + sname + "(" + generateSQLFromFields(fields) + `)");` dbMigrator := newDBDriver()
downsql = `m.Sql("DROP TABLE ` + "`" + sname + "`" + `")` upsql = dbMigrator.generateCreateUp(sname)
downsql = dbMigrator.generateCreateDown(sname)
//todo remove
//if driver == "" {
// downsql = strings.Replace(downsql, "`", "", -1)
//}
} }
generateMigration(sname, upsql, downsql, crupath) generateMigration(sname, upsql, downsql, currpath)
} }
// run migration
ColorLog("[INFO] Do you want to go ahead and migrate the database? [yes|no]] ") // Run the migration
ColorLog("[INFO] Do you want to migrate the database? [Yes|No] ")
if askForConfirmation() { if askForConfirmation() {
migrateUpdate(crupath, driver, conn) migrateUpdate(currpath, driver, conn)
} }
ColorLog("[INFO] All done! Don't forget to add beego.Router(\"/%v\" ,&controllers.%vController{}) to routers/route.go\n", sname, strings.Title(sname)) ColorLog("[INFO] All done! Don't forget to add beego.Router(\"/%v\" ,&controllers.%vController{}) to routers/route.go\n", sname, strings.Title(sname))
} }
func generateSQLFromFields(fields string) string {
sql := ""
tags := ""
fds := strings.Split(fields, ",")
for i, v := range fds {
kv := strings.SplitN(v, ":", 2)
if len(kv) != 2 {
ColorLog("[ERRO] the filds format is wrong. should key:type,key:type " + v)
return ""
}
typ, tag := getSqlType(kv[1])
if typ == "" {
ColorLog("[ERRO] the filds format is wrong. should key:type,key:type " + v)
return ""
}
if i == 0 && strings.ToLower(kv[0]) != "id" {
sql = sql + "`id` int(11) NOT NULL AUTO_INCREMENT,"
tags = tags + "PRIMARY KEY (`id`),"
}
sql = sql + "`" + snakeString(kv[0]) + "` " + typ + ","
if tag != "" {
tags = tags + fmt.Sprintf(tag, "`"+snakeString(kv[0])+"`") + ","
}
}
sql = strings.TrimRight(sql+tags, ",")
return sql
}
func getSqlType(ktype string) (tp, tag string) {
kv := strings.SplitN(ktype, ":", 2)
switch kv[0] {
case "string":
if len(kv) == 2 {
return "varchar(" + kv[1] + ") NOT NULL", ""
} else {
return "varchar(128) NOT NULL", ""
}
case "text":
return "longtext NOT NULL", ""
case "auto":
return "int(11) NOT NULL AUTO_INCREMENT", ""
case "pk":
return "int(11) NOT NULL", "PRIMARY KEY (%s)"
case "datetime":
return "datetime NOT NULL", ""
case "int", "int8", "int16", "int32", "int64":
fallthrough
case "uint", "uint8", "uint16", "uint32", "uint64":
return "int(11) DEFAULT NULL", ""
case "bool":
return "tinyint(1) NOT NULL", ""
case "float32", "float64":
return "float NOT NULL", ""
case "float":
return "float NOT NULL", ""
}
return "", ""
}

View File

@ -1,47 +1,74 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main package main
import ( import (
"fmt"
"os" "os"
"path" "path"
) )
// recipe // recipe
// admin/recipe // admin/recipe
func generateView(vpath, crupath string) { func generateView(viewpath, currpath string) {
absvpath := path.Join(crupath, "views", vpath) w := NewColorWriter(os.Stdout)
os.MkdirAll(absvpath, os.ModePerm)
cfile := path.Join(absvpath, "index.tpl") ColorLog("[INFO] Generating view...\n")
absViewPath := path.Join(currpath, "views", viewpath)
err := os.MkdirAll(absViewPath, os.ModePerm)
if err != nil {
ColorLog("[ERRO] Could not create '%s' view: %s\n", viewpath, err)
os.Exit(2)
}
cfile := path.Join(absViewPath, "index.tpl")
if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer f.Close() defer CloseFile(f)
f.WriteString(cfile) f.WriteString(cfile)
ColorLog("[INFO] Created: %v\n", cfile) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m")
} else { } else {
ColorLog("[ERRO] Could not create view file: %s\n", err) ColorLog("[ERRO] Could not create view file: %s\n", err)
os.Exit(2) os.Exit(2)
} }
cfile = path.Join(absvpath, "show.tpl")
cfile = path.Join(absViewPath, "show.tpl")
if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer f.Close() defer CloseFile(f)
f.WriteString(cfile) f.WriteString(cfile)
ColorLog("[INFO] Created: %v\n", cfile) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m")
} else { } else {
ColorLog("[ERRO] Could not create view file: %s\n", err) ColorLog("[ERRO] Could not create view file: %s\n", err)
os.Exit(2) os.Exit(2)
} }
cfile = path.Join(absvpath, "create.tpl")
cfile = path.Join(absViewPath, "create.tpl")
if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer f.Close() defer CloseFile(f)
f.WriteString(cfile) f.WriteString(cfile)
ColorLog("[INFO] Created: %v\n", cfile) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m")
} else { } else {
ColorLog("[ERRO] Could not create view file: %s\n", err) ColorLog("[ERRO] Could not create view file: %s\n", err)
os.Exit(2) os.Exit(2)
} }
cfile = path.Join(absvpath, "edit.tpl")
cfile = path.Join(absViewPath, "edit.tpl")
if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil { if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer f.Close() defer CloseFile(f)
f.WriteString(cfile) f.WriteString(cfile)
ColorLog("[INFO] Created: %v\n", cfile) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m")
} else { } else {
ColorLog("[ERRO] Could not create view file: %s\n", err) ColorLog("[ERRO] Could not create view file: %s\n", err)
os.Exit(2) os.Exit(2)

View File

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

View File

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

100
new.go
View File

@ -55,100 +55,67 @@ func init() {
} }
func createApp(cmd *Command, args []string) int { func createApp(cmd *Command, args []string) int {
curpath, _ := os.Getwd() ShowShortVersionBanner()
w := NewColorWriter(os.Stdout)
if len(args) != 1 { if len(args) != 1 {
ColorLog("[ERRO] Argument [appname] is missing\n") ColorLog("[ERRO] Argument [appname] is missing\n")
os.Exit(2) os.Exit(2)
} }
apppath, packpath, err := checkEnv(args[0])
gopath := os.Getenv("GOPATH") if err != nil {
Debugf("gopath:%s", gopath) fmt.Println(err)
if gopath == "" {
ColorLog("[ERRO] $GOPATH not found\n")
ColorLog("[HINT] Set $GOPATH in your environment vairables\n")
os.Exit(2) os.Exit(2)
} }
haspath := false
appsrcpath := ""
wgopath := path.SplitList(gopath)
for _, wg := range wgopath {
wg = path.Join(wg, "src")
if strings.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) {
haspath = true
appsrcpath = wg
break
}
wg, _ = path.EvalSymlinks(wg)
if strings.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) {
haspath = true
appsrcpath = wg
break
}
}
if !haspath {
ColorLog("[ERRO] Unable to create an application outside of $GOPATH%ssrc(%s%ssrc)\n", string(path.Separator), gopath, string(path.Separator))
ColorLog("[HINT] Change your work directory by `cd ($GOPATH%ssrc)`\n", string(path.Separator))
os.Exit(2)
}
apppath := path.Join(curpath, args[0])
if isExist(apppath) { if isExist(apppath) {
ColorLog("[ERRO] Path (%s) already exists\n", apppath) ColorLog("[ERRO] Path (%s) already exists\n", apppath)
ColorLog("[WARN] Do you want to overwrite it? [yes|no]]") ColorLog("[WARN] Do you want to overwrite it? [Yes|No] ")
if !askForConfirmation() { if !askForConfirmation() {
os.Exit(2) os.Exit(2)
} }
} }
fmt.Println("[INFO] Creating application...") ColorLog("[INFO] Creating application...\n")
os.MkdirAll(apppath, 0755) os.MkdirAll(apppath, 0755)
fmt.Println(apppath + string(path.Separator)) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "conf"), 0755) os.Mkdir(path.Join(apppath, "conf"), 0755)
fmt.Println(path.Join(apppath, "conf") + string(path.Separator)) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "controllers"), 0755) os.Mkdir(path.Join(apppath, "controllers"), 0755)
fmt.Println(path.Join(apppath, "controllers") + string(path.Separator)) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "models"), 0755) os.Mkdir(path.Join(apppath, "models"), 0755)
fmt.Println(path.Join(apppath, "models") + string(path.Separator)) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "routers"), 0755) os.Mkdir(path.Join(apppath, "routers"), 0755)
fmt.Println(path.Join(apppath, "routers") + string(path.Separator)) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "tests"), 0755) os.Mkdir(path.Join(apppath, "tests"), 0755)
fmt.Println(path.Join(apppath, "tests") + string(path.Separator)) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "static"), 0755) os.Mkdir(path.Join(apppath, "static"), 0755)
fmt.Println(path.Join(apppath, "static") + string(path.Separator)) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "static", "js"), 0755) os.Mkdir(path.Join(apppath, "static", "js"), 0755)
fmt.Println(path.Join(apppath, "static", "js") + string(path.Separator)) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "js")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "static", "css"), 0755) os.Mkdir(path.Join(apppath, "static", "css"), 0755)
fmt.Println(path.Join(apppath, "static", "css") + string(path.Separator)) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "css")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "static", "img"), 0755) os.Mkdir(path.Join(apppath, "static", "img"), 0755)
fmt.Println(path.Join(apppath, "static", "img") + string(path.Separator)) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "img")+string(path.Separator), "\x1b[0m")
fmt.Println(path.Join(apppath, "views") + string(path.Separator)) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "views")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "views"), 0755) os.Mkdir(path.Join(apppath, "views"), 0755)
fmt.Println(path.Join(apppath, "conf", "app.conf")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
writetofile(path.Join(apppath, "conf", "app.conf"), strings.Replace(appconf, "{{.Appname}}", args[0], -1)) WriteToFile(path.Join(apppath, "conf", "app.conf"), strings.Replace(appconf, "{{.Appname}}", path.Base(args[0]), -1))
fmt.Println(path.Join(apppath, "controllers", "default.go")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "default.go"), "\x1b[0m")
writetofile(path.Join(apppath, "controllers", "default.go"), controllers) WriteToFile(path.Join(apppath, "controllers", "default.go"), controllers)
fmt.Println(path.Join(apppath, "views", "index.tpl")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "views", "index.tpl"), "\x1b[0m")
writetofile(path.Join(apppath, "views", "index.tpl"), indextpl) WriteToFile(path.Join(apppath, "views", "index.tpl"), indextpl)
fmt.Println(path.Join(apppath, "routers", "router.go")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers", "router.go"), "\x1b[0m")
writetofile(path.Join(apppath, "routers", "router.go"), strings.Replace(router, "{{.Appname}}", strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/"), -1)) WriteToFile(path.Join(apppath, "routers", "router.go"), strings.Replace(router, "{{.Appname}}", packpath, -1))
fmt.Println(path.Join(apppath, "tests", "default_test.go")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests", "default_test.go"), "\x1b[0m")
writetofile(path.Join(apppath, "tests", "default_test.go"), strings.Replace(test, "{{.Appname}}", strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/"), -1)) WriteToFile(path.Join(apppath, "tests", "default_test.go"), strings.Replace(test, "{{.Appname}}", packpath, -1))
fmt.Println(path.Join(apppath, "main.go")) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
writetofile(path.Join(apppath, "main.go"), strings.Replace(maingo, "{{.Appname}}", strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/"), -1)) WriteToFile(path.Join(apppath, "main.go"), strings.Replace(maingo, "{{.Appname}}", packpath, -1))
ColorLog("[SUCC] New application successfully created!\n") ColorLog("[SUCC] New application successfully created!\n")
return 0 return 0
@ -237,7 +204,7 @@ type MainController struct {
func (c *MainController) Get() { func (c *MainController) Get() {
c.Data["Website"] = "beego.me" c.Data["Website"] = "beego.me"
c.Data["Email"] = "astaxie@gmail.com" c.Data["Email"] = "astaxie@gmail.com"
c.TplNames = "index.tpl" c.TplName = "index.tpl"
} }
` `
@ -336,11 +303,12 @@ var indextpl = `<!DOCTYPE html>
</html> </html>
` `
func writetofile(filename, content string) { // WriteToFile creates a file and writes content to it
func WriteToFile(filename, content string) {
f, err := os.Create(filename) f, err := os.Create(filename)
defer CloseFile(f)
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer f.Close()
f.WriteString(content) f.WriteString(content)
} }

45
pack.go
View File

@ -48,8 +48,8 @@ This eases the deployment by extracting the zip file to a server.
-be=[] additional ENV Variables of go build. eg: GOARCH=arm -be=[] additional ENV Variables of go build. eg: GOARCH=arm
-o compressed file output dir. default use current path -o compressed file output dir. default use current path
-f="" format: tar.gz, zip (default: tar.gz) -f="" format: tar.gz, zip (default: tar.gz)
-exp="" relpath exclude prefix (default: .). -exp="" relpath exclude prefix (default: .). use : as separator
-exs="" relpath exclude suffix (default: .go:.DS_Store:.tmp). -exs="" relpath exclude suffix (default: .go:.DS_Store:.tmp). use : as separator
all path use : as separator all path use : as separator
-exr=[] file/directory name exclude by Regexp (default: ^). -exr=[] file/directory name exclude by Regexp (default: ^).
-fs=false follow symlink (default: false). -fs=false follow symlink (default: false).
@ -72,6 +72,7 @@ var (
buildEnvs ListOpts buildEnvs ListOpts
verbose bool verbose bool
format string format string
w io.Writer
) )
type ListOpts []string type ListOpts []string
@ -101,6 +102,7 @@ func init() {
fs.BoolVar(&verbose, "v", false, "verbose") fs.BoolVar(&verbose, "v", false, "verbose")
cmdPack.Flag = *fs cmdPack.Flag = *fs
cmdPack.Run = packApp cmdPack.Run = packApp
w = NewColorWriter(os.Stdout)
} }
func exitPrint(con string) { func exitPrint(con string) {
@ -199,6 +201,7 @@ func (wft *walkFileTree) virPath(fpath string) string {
return "" return ""
} }
name = name[1:] name = name[1:]
name = path.ToSlash(name)
return name return name
} }
@ -241,13 +244,12 @@ func (wft *walkFileTree) walkLeaf(fpath string, fi os.FileInfo, err error) error
if added, err := wft.wak.compress(name, fpath, fi); added { if added, err := wft.wak.compress(name, fpath, fi); added {
if verbose { if verbose {
fmt.Printf("Compressed: %s\n", name) fmt.Fprintf(w, "\t%s%scompressed%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", name, "\x1b[0m")
} }
wft.allfiles[name] = true wft.allfiles[name] = true
return err return err
} else {
return err
} }
return err
} }
func (wft *walkFileTree) iterDirectory(fpath string, fi os.FileInfo) error { func (wft *walkFileTree) iterDirectory(fpath string, fi os.FileInfo) error {
@ -338,7 +340,7 @@ func (wft *tarWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
defer fr.Close() defer CloseFile(fr)
_, err = io.Copy(tw, fr) _, err = io.Copy(tw, fr)
if err != nil { if err != nil {
return false, err return false, err
@ -374,7 +376,7 @@ func (wft *zipWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
defer fr.Close() defer CloseFile(fr)
_, err = io.Copy(w, fr) _, err = io.Copy(w, fr)
if err != nil { if err != nil {
return false, err return false, err
@ -396,10 +398,10 @@ func (wft *zipWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
func packDirectory(excludePrefix []string, excludeSuffix []string, func packDirectory(excludePrefix []string, excludeSuffix []string,
excludeRegexp []*regexp.Regexp, includePath ...string) (err error) { excludeRegexp []*regexp.Regexp, includePath ...string) (err error) {
fmt.Printf("exclude relpath prefix: %s\n", strings.Join(excludePrefix, ":")) ColorLog("Excluding relpath prefix: %s\n", strings.Join(excludePrefix, ":"))
fmt.Printf("exclude relpath suffix: %s\n", strings.Join(excludeSuffix, ":")) ColorLog("Excluding relpath suffix: %s\n", strings.Join(excludeSuffix, ":"))
if len(excludeRegexp) > 0 { if len(excludeRegexp) > 0 {
fmt.Printf("exclude filename regex: `%s`\n", strings.Join(excludeR, "`, `")) ColorLog("Excluding filename regex: `%s`\n", strings.Join(excludeR, "`, `"))
} }
w, err := os.OpenFile(outputP, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) w, err := os.OpenFile(outputP, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
@ -455,7 +457,7 @@ func packDirectory(excludePrefix []string, excludeSuffix []string,
func isBeegoProject(thePath string) bool { func isBeegoProject(thePath string) bool {
fh, _ := os.Open(thePath) fh, _ := os.Open(thePath)
fis, _ := fh.Readdir(-1) fis, _ := fh.Readdir(-1)
regex := regexp.MustCompile(`(?s)package main.*?import.*?\(.*?"github.com/astaxie/beego".*?\).*func main()`) regex := regexp.MustCompile(`(?s)package main.*?import.*?\(.*?github.com/astaxie/beego".*?\).*func main()`)
for _, fi := range fis { for _, fi := range fis {
if fi.IsDir() == false && strings.HasSuffix(fi.Name(), ".go") { if fi.IsDir() == false && strings.HasSuffix(fi.Name(), ".go") {
data, err := ioutil.ReadFile(path.Join(thePath, fi.Name())) data, err := ioutil.ReadFile(path.Join(thePath, fi.Name()))
@ -471,6 +473,8 @@ func isBeegoProject(thePath string) bool {
} }
func packApp(cmd *Command, args []string) int { func packApp(cmd *Command, args []string) int {
ShowShortVersionBanner()
curPath, _ := os.Getwd() curPath, _ := os.Getwd()
thePath := "" thePath := ""
@ -492,17 +496,17 @@ func packApp(cmd *Command, args []string) int {
thePath, err := path.Abs(appPath) thePath, err := path.Abs(appPath)
if err != nil { if err != nil {
exitPrint(fmt.Sprintf("wrong app path: %s", thePath)) exitPrint(fmt.Sprintf("Wrong app path: %s", thePath))
} }
if stat, err := os.Stat(thePath); os.IsNotExist(err) || stat.IsDir() == false { if stat, err := os.Stat(thePath); os.IsNotExist(err) || stat.IsDir() == false {
exitPrint(fmt.Sprintf("not exist app path: %s", thePath)) exitPrint(fmt.Sprintf("App path does not exist: %s", thePath))
} }
if isBeegoProject(thePath) == false { if isBeegoProject(thePath) == false {
exitPrint(fmt.Sprintf("not support non beego project")) exitPrint(fmt.Sprintf("Bee does not support non Beego project"))
} }
fmt.Printf("app path: %s\n", thePath) ColorLog("Packaging application: %s\n", thePath)
appName := path.Base(thePath) appName := path.Base(thePath)
@ -522,8 +526,7 @@ func packApp(cmd *Command, args []string) int {
os.Mkdir(tmpdir, 0700) os.Mkdir(tmpdir, 0700)
if build { if build {
fmt.Println("build", appName) ColorLog("Building application...\n")
var envs []string var envs []string
for _, env := range buildEnvs { for _, env := range buildEnvs {
parts := strings.SplitN(env, "=", 2) parts := strings.SplitN(env, "=", 2)
@ -545,7 +548,7 @@ func packApp(cmd *Command, args []string) int {
os.Setenv("GOOS", goos) os.Setenv("GOOS", goos)
os.Setenv("GOARCH", goarch) os.Setenv("GOARCH", goarch)
fmt.Println("GOOS", goos, "GOARCH", goarch) ColorLog("Env: GOOS=%s GOARCH=%s\n", goos, goarch)
binPath := path.Join(tmpdir, appName) binPath := path.Join(tmpdir, appName)
if goos == "windows" { if goos == "windows" {
@ -558,7 +561,7 @@ func packApp(cmd *Command, args []string) int {
} }
if verbose { if verbose {
fmt.Println("go ", strings.Join(args, " ")) fmt.Fprintf(w, "\t%s%s+ go %s%s%s\n", "\x1b[32m", "\x1b[1m", strings.Join(args, " "), "\x1b[21m", "\x1b[0m")
} }
execmd := exec.Command("go", args...) execmd := exec.Command("go", args...)
@ -571,7 +574,7 @@ func packApp(cmd *Command, args []string) int {
exitPrint(err.Error()) exitPrint(err.Error())
} }
fmt.Println("build success") ColorLog("Build successful\n")
} }
switch format { switch format {
@ -623,6 +626,6 @@ func packApp(cmd *Command, args []string) int {
exitPrint(err.Error()) exitPrint(err.Error())
} }
fmt.Printf("file write to `%s`\n", outputP) ColorLog("Writing to output: `%s`\n", outputP)
return 0 return 0
} }

143
run.go
View File

@ -15,6 +15,7 @@
package main package main
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
path "path/filepath" path "path/filepath"
@ -23,7 +24,7 @@ import (
) )
var cmdRun = &Command{ var cmdRun = &Command{
UsageLine: "run [appname] [watchall] [-main=*.go] [-downdoc=true] [-gendoc=true]", UsageLine: "run [appname] [watchall] [-main=*.go] [-downdoc=true] [-gendoc=true] [-vendor=true] [-e=folderToExclude] [-tags=goBuildTags] [-runmode=BEEGO_RUNMODE]",
Short: "run the app and start a Web server for development", Short: "run the app and start a Web server for development",
Long: ` Long: `
Run command will supervise the file system of the beego project using inotify, Run command will supervise the file system of the beego project using inotify,
@ -32,39 +33,85 @@ it will recompile and restart the app after any modifications.
`, `,
} }
var mainFiles ListOpts var (
mainFiles ListOpts
var downdoc docValue downdoc docValue
var gendoc docValue gendoc docValue
// The flags list of the paths excluded from watching
excludedPaths strFlags
// Pass through to -tags arg of "go build"
buildTags string
// Application path
currpath string
// Application name
appname string
// Channel to signal an Exit
exit chan bool
// Flag to watch the vendor folder
vendorWatch bool
// Current user workspace
currentGoPath string
// Current runmode
runmode string
)
func init() { func init() {
cmdRun.Run = runApp cmdRun.Run = runApp
cmdRun.Flag.Var(&mainFiles, "main", "specify main go files") cmdRun.Flag.Var(&mainFiles, "main", "specify main go files")
cmdRun.Flag.Var(&gendoc, "gendoc", "auto generate the docs") cmdRun.Flag.Var(&gendoc, "gendoc", "auto generate the docs")
cmdRun.Flag.Var(&downdoc, "downdoc", "auto download swagger file when not exist") cmdRun.Flag.Var(&downdoc, "downdoc", "auto download swagger file when not exist")
cmdRun.Flag.Var(&excludedPaths, "e", "Excluded paths[].")
cmdRun.Flag.BoolVar(&vendorWatch, "vendor", false, "Watch vendor folder")
cmdRun.Flag.StringVar(&buildTags, "tags", "", "Build tags (https://golang.org/pkg/go/build/)")
cmdRun.Flag.StringVar(&runmode, "runmode", "", "Set BEEGO_RUNMODE env variable.")
exit = make(chan bool)
} }
var appname string
func runApp(cmd *Command, args []string) int { func runApp(cmd *Command, args []string) int {
exit := make(chan bool) ShowShortVersionBanner()
crupath, _ := os.Getwd()
if len(args) == 0 || args[0] == "watchall" { if len(args) == 0 || args[0] == "watchall" {
appname = path.Base(crupath) currpath, _ = os.Getwd()
ColorLog("[INFO] Uses '%s' as 'appname'\n", appname)
if found, _gopath, _ := SearchGOPATHs(currpath); found {
appname = path.Base(currpath)
currentGoPath = _gopath
} else {
exitPrint(fmt.Sprintf("Bee does not support non Beego project: %s", currpath))
}
ColorLog("[INFO] Using '%s' as 'appname'\n", appname)
} else { } else {
appname = args[0] // Check if passed Bee application path/name exists in the GOPATH(s)
ColorLog("[INFO] Uses '%s' as 'appname'\n", appname) if found, _gopath, _path := SearchGOPATHs(args[0]); found {
if strings.HasSuffix(appname, ".go") && isExist(path.Join(crupath, appname)) { currpath = _path
ColorLog("[WARN] The appname has conflic with crupath's file, do you want to build appname as %s\n", appname) currentGoPath = _gopath
appname = path.Base(currpath)
} else {
panic(fmt.Sprintf("No Beego application '%s' found in your GOPATH", args[0]))
}
ColorLog("[INFO] Using '%s' as 'appname'\n", appname)
if strings.HasSuffix(appname, ".go") && isExist(currpath) {
ColorLog("[WARN] The appname is in conflict with currpath's file, do you want to build appname as %s\n", appname)
ColorLog("[INFO] Do you want to overwrite it? [yes|no]] ") ColorLog("[INFO] Do you want to overwrite it? [yes|no]] ")
if !askForConfirmation() { if !askForConfirmation() {
return 0 return 0
} }
} }
} }
Debugf("current path:%s\n", crupath)
Debugf("current path:%s\n", currpath)
if runmode == "prod" || runmode == "dev"{
os.Setenv("BEEGO_RUNMODE", runmode)
ColorLog("[INFO] Using '%s' as 'runmode'\n", os.Getenv("BEEGO_RUNMODE"))
}else if runmode != ""{
os.Setenv("BEEGO_RUNMODE", runmode)
ColorLog("[WARN] Using '%s' as 'runmode'\n", os.Getenv("BEEGO_RUNMODE"))
}else if os.Getenv("BEEGO_RUNMODE") != ""{
ColorLog("[WARN] Using '%s' as 'runmode'\n", os.Getenv("BEEGO_RUNMODE"))
}
err := loadConfig() err := loadConfig()
if err != nil { if err != nil {
@ -72,19 +119,12 @@ func runApp(cmd *Command, args []string) int {
} }
var paths []string var paths []string
readAppDirectories(currpath, &paths)
readAppDirectories(crupath, &paths)
// Because monitor files has some issues, we watch current directory // Because monitor files has some issues, we watch current directory
// and ignore non-go files. // and ignore non-go files.
gps := GetGOPATHs()
if len(gps) == 0 {
ColorLog("[ERRO] Fail to start[ %s ]\n", "$GOPATH is not set or empty")
os.Exit(2)
}
gopath := gps[0]
for _, p := range conf.DirStruct.Others { for _, p := range conf.DirStruct.Others {
paths = append(paths, strings.Replace(p, "$GOPATH", gopath, -1)) paths = append(paths, strings.Replace(p, "$GOPATH", currentGoPath, -1))
} }
files := []string{} files := []string{}
@ -93,7 +133,14 @@ func runApp(cmd *Command, args []string) int {
files = append(files, arg) files = append(files, arg)
} }
} }
if downdoc == "true" {
if _, err := os.Stat(path.Join(currpath, "swagger", "index.html")); err != nil {
if os.IsNotExist(err) {
downloadFromURL(swaggerlink, "swagger.zip")
unzipAndDelete("swagger.zip")
}
}
}
if gendoc == "true" { if gendoc == "true" {
NewWatcher(paths, files, true) NewWatcher(paths, files, true)
Autobuild(files, true) Autobuild(files, true)
@ -101,21 +148,13 @@ func runApp(cmd *Command, args []string) int {
NewWatcher(paths, files, false) NewWatcher(paths, files, false)
Autobuild(files, false) Autobuild(files, false)
} }
if downdoc == "true" {
if _, err := os.Stat(path.Join(crupath, "swagger")); err != nil {
if os.IsNotExist(err) {
downloadFromUrl(swaggerlink, "swagger.zip")
unzipAndDelete("swagger.zip", "swagger")
}
}
}
for { for {
select { select {
case <-exit: case <-exit:
runtime.Goexit() runtime.Goexit()
} }
} }
return 0
} }
func readAppDirectories(directory string, paths *[]string) { func readAppDirectories(directory string, paths *[]string) {
@ -129,6 +168,18 @@ func readAppDirectories(directory string, paths *[]string) {
if strings.HasSuffix(fileInfo.Name(), "docs") { if strings.HasSuffix(fileInfo.Name(), "docs") {
continue continue
} }
if strings.HasSuffix(fileInfo.Name(), "swagger") {
continue
}
if !vendorWatch && strings.HasSuffix(fileInfo.Name(), "vendor") {
continue
}
if isExcluded(path.Join(directory, fileInfo.Name())) {
continue
}
if fileInfo.IsDir() == true && fileInfo.Name()[0] != '.' { if fileInfo.IsDir() == true && fileInfo.Name()[0] != '.' {
readAppDirectories(directory+"/"+fileInfo.Name(), paths) readAppDirectories(directory+"/"+fileInfo.Name(), paths)
continue continue
@ -143,6 +194,26 @@ func readAppDirectories(directory string, paths *[]string) {
useDirectory = true useDirectory = true
} }
} }
return return
} }
// If a file is excluded
func isExcluded(filePath string) bool {
for _, p := range excludedPaths {
absP, err := path.Abs(p)
if err != nil {
ColorLog("[ERROR] Can not get absolute path of [ %s ]\n", p)
continue
}
absFilePath, err := path.Abs(filePath)
if err != nil {
ColorLog("[ERROR] Can not get absolute path of [ %s ]\n", filePath)
break
}
if strings.HasPrefix(absFilePath, absP) {
ColorLog("[INFO] Excluding from watching [ %s ]\n", filePath)
return true
}
}
return false
}

View File

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

View File

@ -73,7 +73,6 @@ func testApp(cmd *Command, args []string) int {
runTest() runTest()
} }
} }
return 0
} }
func runTest() { func runTest() {

52
util.go
View File

@ -15,13 +15,14 @@
package main package main
import ( import (
"fmt"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"time" "time"
"path"
"fmt"
) )
// Go is a basic promise implementation: it wraps calls a function in a goroutine // Go is a basic promise implementation: it wraps calls a function in a goroutine
@ -152,7 +153,6 @@ func getColorLevel(level string) string {
default: default:
return level return level
} }
return level
} }
// IsExist returns whether a file or directory exists. // IsExist returns whether a file or directory exists.
@ -174,6 +174,34 @@ func GetGOPATHs() []string {
return paths return paths
} }
func SearchGOPATHs(app string) (bool, string, string) {
gps := GetGOPATHs()
if len(gps) == 0 {
ColorLog("[ERRO] Fail to start [ %s ]\n", "GOPATH environment variable is not set or empty")
os.Exit(2)
}
// Lookup the application inside the user workspace(s)
for _, gopath := range gps {
var currentPath string
if !strings.Contains(app, "src") {
gopathsrc := path.Join(gopath, "src")
currentPath = path.Join(gopathsrc, app)
} else {
currentPath = app
}
if isExist(currentPath) {
if !isBeegoProject(currentPath) {
continue
}
return true, gopath, currentPath
}
}
return false, "", ""
}
// askForConfirmation uses Scanln to parse user input. A user must type in "yes" or "no" and // askForConfirmation uses Scanln to parse user input. A user must type in "yes" or "no" and
// then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as // then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as
// confirmations. If the input is not recognized, it will ask again. The function does not return // confirmations. If the input is not recognized, it will ask again. The function does not return
@ -247,3 +275,23 @@ func camelString(s string) string {
} }
return string(data[:len(data)]) return string(data[:len(data)])
} }
// The string flag list, implemented flag.Value interface
type strFlags []string
func (s *strFlags) String() string {
return fmt.Sprintf("%s", *s)
}
func (s *strFlags) Set(value string) error {
*s = append(*s, value)
return nil
}
// CloseFile attempts to close the passed file
// or panics with the actual error
func CloseFile(f *os.File) {
if err := f.Close(); err != nil {
panic(err)
}
}

View File

@ -2,53 +2,81 @@ package main
import ( import (
"bufio" "bufio"
"bytes"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"os/exec"
path "path/filepath" path "path/filepath"
"regexp" "regexp"
) )
var cmdVersion = &Command{ var cmdVersion = &Command{
UsageLine: "version", UsageLine: "version",
Short: "show the Bee, Beego and Go version", Short: "prints the current Bee version",
Long: ` Long: `
show the Bee, Beego and Go version Prints the current Bee, Beego and Go version alongside the platform information
bee version
bee :1.2.3
beego :1.4.2
Go :go version go1.3.3 linux/amd64
`, `,
} }
const verboseVersionBanner string = `%s%s______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v{{ .BeeVersion }}%s
%s%s
Beego : {{ .BeegoVersion }}
GoVersion : {{ .GoVersion }}
GOOS : {{ .GOOS }}
GOARCH : {{ .GOARCH }}
NumCPU : {{ .NumCPU }}
GOPATH : {{ .GOPATH }}
GOROOT : {{ .GOROOT }}
Compiler : {{ .Compiler }}
Date : {{ Now "Monday, 2 Jan 2006" }}%s
`
const shortVersionBanner = `%s%s______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v{{ .BeeVersion }}%s
`
func init() { func init() {
cmdVersion.Run = versionCmd cmdVersion.Run = versionCmd
} }
func versionCmd(cmd *Command, args []string) int { func versionCmd(cmd *Command, args []string) int {
fmt.Println("bee :" + version) ShowVerboseVersionBanner()
fmt.Println("beego :" + getbeegoVersion())
//fmt.Println("Go :" + runtime.Version())
goversion, err := exec.Command("go", "version").Output()
if err != nil {
log.Fatal(err)
}
fmt.Println("Go :" + string(goversion))
return 0 return 0
} }
func getbeegoVersion() string { // ShowVerboseVersionBanner prints the verbose version banner
func ShowVerboseVersionBanner() {
w := NewColorWriter(os.Stdout)
coloredBanner := fmt.Sprintf(verboseVersionBanner, "\x1b[35m", "\x1b[1m", "\x1b[0m",
"\x1b[32m", "\x1b[1m", "\x1b[0m")
InitBanner(w, bytes.NewBufferString(coloredBanner))
}
// ShowShortVersionBanner prints the short version banner
func ShowShortVersionBanner() {
w := NewColorWriter(os.Stdout)
coloredBanner := fmt.Sprintf(shortVersionBanner, "\x1b[35m", "\x1b[1m", "\x1b[0m")
InitBanner(w, bytes.NewBufferString(coloredBanner))
}
func getBeegoVersion() string {
gopath := os.Getenv("GOPATH") gopath := os.Getenv("GOPATH")
re, err := regexp.Compile(`const VERSION = "([0-9.]+)"`) re, err := regexp.Compile(`VERSION = "([0-9.]+)"`)
if err != nil { if err != nil {
return "" return ""
} }
if gopath == "" { if gopath == "" {
err = fmt.Errorf("you should set GOPATH in the env") err = fmt.Errorf("You should set GOPATH env variable")
return "" return ""
} }
wgopath := path.SplitList(gopath) wgopath := path.SplitList(gopath)
@ -60,11 +88,11 @@ func getbeegoVersion() string {
if os.IsNotExist(err) { if os.IsNotExist(err) {
continue continue
} }
ColorLog("[ERRO] get beego.go has error\n") ColorLog("[ERRO] Get `beego.go` has error\n")
} }
fd, err := os.Open(filename) fd, err := os.Open(filename)
if err != nil { if err != nil {
ColorLog("[ERRO] open beego.go has error\n") ColorLog("[ERRO] Open `beego.go` has error\n")
continue continue
} }
reader := bufio.NewReader(fd) reader := bufio.NewReader(fd)
@ -84,5 +112,5 @@ func getbeegoVersion() string {
} }
} }
return "you don't install beego,install first: github.com/astaxie/beego" return "Beego not installed. Please install it first: https://github.com/astaxie/beego"
} }

View File

@ -17,14 +17,14 @@ package main
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/howeyc/fsnotify"
"os" "os"
"os/exec" "os/exec"
"regexp"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/howeyc/fsnotify"
) )
var ( var (
@ -47,8 +47,8 @@ func NewWatcher(paths []string, files []string, isgenerate bool) {
case e := <-watcher.Event: case e := <-watcher.Event:
isbuild := true isbuild := true
// Skip TMP files for Sublime Text. // Skip ignored files
if checkTMPFile(e.Name) { if shouldIgnoreFile(e.Name) {
continue continue
} }
if !checkIfWatchExt(e.Name) { if !checkIfWatchExt(e.Name) {
@ -121,8 +121,8 @@ func Autobuild(files []string, isgenerate bool) {
defer state.Unlock() defer state.Unlock()
ColorLog("[INFO] Start building...\n") ColorLog("[INFO] Start building...\n")
path, _ := os.Getwd()
os.Chdir(path) os.Chdir(currpath)
cmdName := "go" cmdName := "go"
if conf.Gopm.Enable { if conf.Gopm.Enable {
@ -136,6 +136,7 @@ func Autobuild(files []string, isgenerate bool) {
icmd := exec.Command("go", "list", "./...") icmd := exec.Command("go", "list", "./...")
buf := bytes.NewBuffer([]byte("")) buf := bytes.NewBuffer([]byte(""))
icmd.Stdout = buf icmd.Stdout = buf
icmd.Env = append(os.Environ(), "GOGC=off")
err = icmd.Run() err = icmd.Run()
if err == nil { if err == nil {
list := strings.Split(buf.String(), "\n")[1:] list := strings.Split(buf.String(), "\n")[1:]
@ -146,6 +147,7 @@ func Autobuild(files []string, isgenerate bool) {
icmd = exec.Command(cmdName, "install", pkg) icmd = exec.Command(cmdName, "install", pkg)
icmd.Stdout = os.Stdout icmd.Stdout = os.Stdout
icmd.Stderr = os.Stderr icmd.Stderr = os.Stderr
icmd.Env = append(os.Environ(), "GOGC=off")
err = icmd.Run() err = icmd.Run()
if err != nil { if err != nil {
break break
@ -156,6 +158,7 @@ func Autobuild(files []string, isgenerate bool) {
if isgenerate { if isgenerate {
icmd := exec.Command("bee", "generate", "docs") icmd := exec.Command("bee", "generate", "docs")
icmd.Env = append(os.Environ(), "GOGC=off")
icmd.Stdout = os.Stdout icmd.Stdout = os.Stdout
icmd.Stderr = os.Stderr icmd.Stderr = os.Stderr
icmd.Run() icmd.Run()
@ -170,9 +173,13 @@ func Autobuild(files []string, isgenerate bool) {
args := []string{"build"} args := []string{"build"}
args = append(args, "-o", appName) args = append(args, "-o", appName)
if buildTags != "" {
args = append(args, "-tags", buildTags)
}
args = append(args, files...) args = append(args, files...)
bcmd := exec.Command(cmdName, args...) bcmd := exec.Command(cmdName, args...)
bcmd.Env = append(os.Environ(), "GOGC=off")
bcmd.Stdout = os.Stdout bcmd.Stdout = os.Stdout
bcmd.Stderr = os.Stderr bcmd.Stderr = os.Stderr
err = bcmd.Run() err = bcmd.Run()
@ -223,15 +230,30 @@ func Start(appname string) {
started <- true started <- true
} }
// checkTMPFile returns true if the event was for TMP files. // Should ignore filenames generated by
func checkTMPFile(name string) bool { // Emacs, Vim or SublimeText
if strings.HasSuffix(strings.ToLower(name), ".tmp") { func shouldIgnoreFile(filename string) bool {
return true for _, regex := range ignoredFilesRegExps {
r, err := regexp.Compile(regex)
if err != nil {
panic("Could not compile the regex: " + regex)
}
if r.MatchString(filename) {
return true
} else {
continue
}
} }
return false return false
} }
var watchExts = []string{".go"} var watchExts = []string{".go"}
var ignoredFilesRegExps = []string{
`.#(\w+).go`,
`.(\w+).go.swp`,
`(\w+).go~`,
`(\w+).tmp`,
}
// checkIfWatchExt returns true if the name HasSuffix <watch_ext>. // checkIfWatchExt returns true if the name HasSuffix <watch_ext>.
func checkIfWatchExt(name string) bool { func checkIfWatchExt(name string) bool {