1
0
mirror of https://github.com/beego/bee.git synced 2024-11-26 16:41:30 +00:00

Merge pull request #1 from beego/master

Update from master
This commit is contained in:
Laoliu 2016-11-01 11:43:39 +08:00 committed by GitHub
commit 4525474bd1
36 changed files with 3091 additions and 1298 deletions

29
.gitignore vendored
View File

@ -1,4 +1,31 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
.idea
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
#Custom
.DS_Store
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
===
[![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
- Go version >= 1.1.
- Go version >= 1.3.
## Installation
Begin by installing `bee` using `go get` command.
To install `bee` use the `go get` command:
```bash
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:
```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:
@ -32,242 +31,225 @@ go get -u github.com/beego/bee
## Basic commands
Bee provides a variety of commands which can be helpful at various stage 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 provides a variety of commands which can be helpful at various stages of development. The top level commands include:
```
## 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
$ 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
$ bee new myapp
[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/
/home/zheng/gopath/src/myapp/tests/
/home/zheng/gopath/src/myapp/static/
/home/zheng/gopath/src/myapp/static/js/
/home/zheng/gopath/src/myapp/static/css/
/home/zheng/gopath/src/myapp/static/img/
/home/zheng/gopath/src/myapp/views/
/home/zheng/gopath/src/myapp/conf/app.conf
/home/zheng/gopath/src/myapp/controllers/default.go
/home/zheng/gopath/src/myapp/views/index.tpl
/home/zheng/gopath/src/myapp/routers/router.go
/home/zheng/gopath/src/myapp/tests/default_test.go
/home/zheng/gopath/src/myapp/main.go
2014/08/29 15:45:47 [SUCC] New application successfully created!
$ bee new my-web-app
______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v1.5.0
2016/08/22 14:53:45 [INFO] Creating application...
create C:\Users\beeuser\go\src\github.com\user\my-web-app\
create C:\Users\beeuser\go\src\github.com\user\my-web-app\conf\
create C:\Users\beeuser\go\src\github.com\user\my-web-app\controllers\
create C:\Users\beeuser\go\src\github.com\user\my-web-app\models\
create C:\Users\beeuser\go\src\github.com\user\my-web-app\routers\
create C:\Users\beeuser\go\src\github.com\user\my-web-app\tests\
create C:\Users\beeuser\go\src\github.com\user\my-web-app\static\
create C:\Users\beeuser\go\src\github.com\user\my-web-app\static\js\
create C:\Users\beeuser\go\src\github.com\user\my-web-app\static\css\
create C:\Users\beeuser\go\src\github.com\user\my-web-app\static\img\
create C:\Users\beeuser\go\src\github.com\user\my-web-app\views\
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
$ cd myapp
$ bee run
$ cd my-web-app && 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
usage: 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
-be=[] additional ENV Variables of go build. eg: GOARCH=arm
-o compressed file output dir. default use current path
-f="" format. [ tar.gz / zip ]. default tar.gz
-exp="" relpath exclude prefix. default: .
-exs="" relpath exclude suffix. default: .go:.DS_Store:.tmp
all path use : as separator
-exr=[] file/directory name exclude by Regexp. default: ^.
-fs=false follow symlink. default false
-ss=false skip symlink. default false
default embed symlink into compressed file
-v=false verbose
$ bee pack
______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v1.5.0
2016/08/22 15:11:01 Packaging application: C:\Users\beeuser\go\src\github.com\user\my-web-app
2016/08/22 15:11:01 Building application...
2016/08/22 15:11:01 Env: GOOS=windows GOARCH=amd64
2016/08/22 15:11:08 Build successful
2016/08/22 15:11:08 Excluding relpath prefix: .
2016/08/22 15:11:08 Excluding relpath suffix: .go:.DS_Store:.tmp
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`
```
## bee api
For more information on the usage, run `bee help pack`.
### bee api
To create a Beego API application:
```bash
usage: bee api [appname]
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
-conn: the connection string used by the driver, the default is ''
e.g. for mysql: root:@tcp(127.0.0.1:3306)/test
e.g. for postgres: postgres://postgres:postgres@127.0.0.1:5432/postgres
if conn is empty will create a example api application. otherwise generate api application based on an existing database.
In the current path, will create a folder named [appname]
In the appname folder has the follow struct:
├── conf
│ └── app.conf
├── controllers
│ └── object.go
│ └── user.go
├── routers
│ └── router.go
├── tests
│ └── default_test.go
├── main.go
└── models
└── object.go
└── user.go
$ bee api my-api
______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v1.5.0
2016/08/22 15:14:10 [INFO] Creating API...
create C:\Users\beeuser\go\src\github.com\user\my-api
create C:\Users\beeuser\go\src\github.com\user\my-api\conf
create C:\Users\beeuser\go\src\github.com\user\my-api\controllers
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
create C:\Users\beeuser\go\src\github.com\user\my-api\models
create C:\Users\beeuser\go\src\github.com\user\my-api\routers\
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
create C:\Users\beeuser\go\src\github.com\user\my-api\tests\default_test.go
create C:\Users\beeuser\go\src\github.com\user\my-api\routers\router.go
create C:\Users\beeuser\go\src\github.com\user\my-api\models\object.go
create C:\Users\beeuser\go\src\github.com\user\my-api\models\user.go
create C:\Users\beeuser\go\src\github.com\user\my-api\main.go
2016/08/22 15:14:10 [SUCC] New API successfully created!
```
## bee hprose
For more information on the usage, run `bee help api`.
### bee hprose
To create an Hprose RPC application based on Beego:
```bash
usage: bee hprose [appname]
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
-conn: the connection string used by the driver, the default is ''
e.g. for mysql: root:@tcp(127.0.0.1:3306)/test
e.g. for postgres: postgres://postgres:postgres@127.0.0.1:5432/postgres
if conn is empty will create a example rpc application. otherwise generate rpc application use hprose based on an existing database.
In the current path, will create a folder named [appname]
In the appname folder has the follow struct:
├── conf
│ └── app.conf
├── main.go
└── models
└── object.go
└── user.go
$ bee hprose my-rpc-app
______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v1.5.0
2016/08/22 16:09:13 [INFO] Creating Hprose application...
create C:\Users\beeuser\go\src\github.com\user\my-rpc-app
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
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
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
2016/08/22 16:09:13 [SUCC] New Hprose application successfully created!
```
## 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
usage: 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.
This is mainly used for zealots who are requiring 100% Go code.
$ bee bale
______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v1.5.0
2016/08/22 16:37:24 [INFO] Detected bee.json
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
usage: bee migrate [Command]
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
bee migrate rollback [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
rollback the last migration operation
-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 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
$ bee generate controller hello
______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v1.5.0
2016/08/22 16:55:30 [INFO] Using 'Hello' as controller name
2016/08/22 16:55:30 [INFO] Using 'controllers' as package name
create C:\Users\beeuser\go\src\github.com\user\my-web-app/controllers/hello.go
2016/08/22 16:55:30 [SUCC] Controller successfully generated!
```
For more information on the usage, run `bee help generate`.
## 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
# Generator Stuff
@ -278,32 +260,135 @@ alias g:v="bee generate view"
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
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:
```bash
$ 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
|
check if it's go file
|
yes no
| |
go build do nothing
|
restart app
### Found a bug?
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!
- If the bug was found in a release, it is best to base your work on `master` and submit your PR against it.
- If the bug was found on `develop` (the development branch), base your work on `develop` and submit your PR against it.
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.
```

241
apiapp.go
View File

@ -24,22 +24,23 @@ import (
var cmdApiapp = &Command{
// CustomFlags: true,
UsageLine: "api [appname]",
Short: "create an api application base on beego framework",
Short: "create an API beego application",
Long: `
create an api application base on beego framework
Create an API beego application.
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
-tables: a list of table names separated by ',' (default is empty, indicating all tables)
-driver: [mysql | postgres | sqlite] (default: mysql)
-conn: the connection string used by the driver, the default is ''
e.g. for mysql: root:@tcp(127.0.0.1:3306)/test
e.g. for postgres: postgres://postgres:postgres@127.0.0.1:5432/postgres
if conn is empty will create a example api application. otherwise generate api application based on an existing database.
If 'conn' argument is empty, bee api creates an example API application,
when 'conn' argument is provided, bee api generates an API application based
on the existing database.
In the current path, will create a folder named [appname]
In the appname folder has the follow struct:
The command 'api' creates a folder named [appname] and inside the folder deploy
the following files/directories structure:
conf
app.conf
@ -68,16 +69,15 @@ EnableDocs = true
var apiMaingo = `package main
import (
_ "{{.Appname}}/docs"
_ "{{.Appname}}/routers"
"github.com/astaxie/beego"
)
func main() {
if beego.RunMode == "dev" {
beego.DirectoryIndex = true
beego.StaticDir["/swagger"] = "swagger"
if beego.BConfig.RunMode == "dev" {
beego.BConfig.WebConfig.DirectoryIndex = true
beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
}
beego.Run()
}
@ -86,7 +86,6 @@ func main() {
var apiMainconngo = `package main
import (
_ "{{.Appname}}/docs"
_ "{{.Appname}}/routers"
"github.com/astaxie/beego"
@ -99,9 +98,9 @@ func init() {
}
func main() {
if beego.RunMode == "dev" {
beego.DirectoryIndex = true
beego.StaticDir["/swagger"] = "swagger"
if beego.BConfig.RunMode == "dev" {
beego.BConfig.WebConfig.DirectoryIndex = true
beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
}
beego.Run()
}
@ -297,18 +296,18 @@ type ObjectController struct {
beego.Controller
}
// @Title create
// @Title Create
// @Description create object
// @Param body body models.Object true "The object content"
// @Success 200 {string} models.Object.Id
// @Failure 403 body is empty
// @router / [post]
func (this *ObjectController) Post() {
func (o *ObjectController) Post() {
var ob models.Object
json.Unmarshal(this.Ctx.Input.RequestBody, &ob)
json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
objectid := models.AddOne(ob)
this.Data["json"] = map[string]string{"ObjectId": objectid}
this.ServeJson()
o.Data["json"] = map[string]string{"ObjectId": objectid}
o.ServeJSON()
}
// @Title Get
@ -317,17 +316,17 @@ func (this *ObjectController) Post() {
// @Success 200 {object} models.Object
// @Failure 403 :objectId is empty
// @router /:objectId [get]
func (this *ObjectController) Get() {
objectId := this.Ctx.Input.Params[":objectId"]
func (o *ObjectController) Get() {
objectId := o.Ctx.Input.Param(":objectId")
if objectId != "" {
ob, err := models.GetOne(objectId)
if err != nil {
this.Data["json"] = err
o.Data["json"] = err.Error()
} else {
this.Data["json"] = ob
o.Data["json"] = ob
}
}
this.ServeJson()
o.ServeJSON()
}
// @Title GetAll
@ -335,44 +334,44 @@ func (this *ObjectController) Get() {
// @Success 200 {object} models.Object
// @Failure 403 :objectId is empty
// @router / [get]
func (this *ObjectController) GetAll() {
func (o *ObjectController) GetAll() {
obs := models.GetAll()
this.Data["json"] = obs
this.ServeJson()
o.Data["json"] = obs
o.ServeJSON()
}
// @Title update
// @Title Update
// @Description update the object
// @Param objectId path string true "The objectid you want to update"
// @Param body body models.Object true "The body"
// @Success 200 {object} models.Object
// @Failure 403 :objectId is empty
// @router /:objectId [put]
func (this *ObjectController) Put() {
objectId := this.Ctx.Input.Params[":objectId"]
func (o *ObjectController) Put() {
objectId := o.Ctx.Input.Param(":objectId")
var ob models.Object
json.Unmarshal(this.Ctx.Input.RequestBody, &ob)
json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
err := models.Update(objectId, ob.Score)
if err != nil {
this.Data["json"] = err
o.Data["json"] = err.Error()
} else {
this.Data["json"] = "update success!"
o.Data["json"] = "update success!"
}
this.ServeJson()
o.ServeJSON()
}
// @Title delete
// @Title Delete
// @Description delete the object
// @Param objectId path string true "The objectId you want to delete"
// @Success 200 {string} delete success!
// @Failure 403 objectId is empty
// @router /:objectId [delete]
func (this *ObjectController) Delete() {
objectId := this.Ctx.Input.Params[":objectId"]
func (o *ObjectController) Delete() {
objectId := o.Ctx.Input.Param(":objectId")
models.Delete(objectId)
this.Data["json"] = "delete success!"
this.ServeJson()
o.Data["json"] = "delete success!"
o.ServeJSON()
}
`
@ -390,7 +389,7 @@ type UserController struct {
beego.Controller
}
// @Title createUser
// @Title CreateUser
// @Description create users
// @Param body body models.User true "body for user content"
// @Success 200 {int} models.User.Id
@ -401,17 +400,17 @@ func (u *UserController) Post() {
json.Unmarshal(u.Ctx.Input.RequestBody, &user)
uid := models.AddUser(user)
u.Data["json"] = map[string]string{"uid": uid}
u.ServeJson()
u.ServeJSON()
}
// @Title Get
// @Title GetAll
// @Description get all Users
// @Success 200 {object} models.User
// @router / [get]
func (u *UserController) GetAll() {
users := models.GetAllUsers()
u.Data["json"] = users
u.ServeJson()
u.ServeJSON()
}
// @Title Get
@ -425,15 +424,15 @@ func (u *UserController) Get() {
if uid != "" {
user, err := models.GetUser(uid)
if err != nil {
u.Data["json"] = err
u.Data["json"] = err.Error()
} else {
u.Data["json"] = user
}
}
u.ServeJson()
u.ServeJSON()
}
// @Title update
// @Title Update
// @Description update the user
// @Param uid path string true "The uid you want to update"
// @Param body body models.User true "body for user content"
@ -447,15 +446,15 @@ func (u *UserController) Put() {
json.Unmarshal(u.Ctx.Input.RequestBody, &user)
uu, err := models.UpdateUser(uid, &user)
if err != nil {
u.Data["json"] = err
u.Data["json"] = err.Error()
} else {
u.Data["json"] = uu
}
}
u.ServeJson()
u.ServeJSON()
}
// @Title delete
// @Title Delete
// @Description delete the user
// @Param uid path string true "The uid you want to delete"
// @Success 200 {string} delete success!
@ -465,14 +464,14 @@ func (u *UserController) Delete() {
uid := u.GetString(":uid")
models.DeleteUser(uid)
u.Data["json"] = "delete success!"
u.ServeJson()
u.ServeJSON()
}
// @Title login
// @Title Login
// @Description Logs user into the system
// @Param username query string true "The username for login"
// @Param password query string true "The password for login"
// @Success 200 {string} lonin success
// @Success 200 {string} login success
// @Failure 403 user not exist
// @router /login [get]
func (u *UserController) Login() {
@ -483,7 +482,7 @@ func (u *UserController) Login() {
} else {
u.Data["json"] = "user not exist"
}
u.ServeJson()
u.ServeJSON()
}
// @Title logout
@ -492,7 +491,7 @@ func (u *UserController) Login() {
// @router /logout [get]
func (u *UserController) Logout() {
u.Data["json"] = "logout success"
u.ServeJson()
u.ServeJSON()
}
`
@ -545,10 +544,19 @@ func init() {
}
func createapi(cmd *Command, args []string) int {
curpath, _ := os.Getwd()
ShowShortVersionBanner()
w := NewColorWriter(os.Stdout)
if len(args) < 1 {
ColorLog("[ERRO] Argument [appname] is missing\n")
os.Exit(2)
}
if len(args) > 1 {
cmd.Flag.Parse(args[1:])
}
apppath, packpath, err := checkEnv(args[0])
if err != nil {
fmt.Println(err)
@ -559,23 +567,23 @@ func createapi(cmd *Command, args []string) int {
}
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"))
writetofile(path.Join(apppath, "conf", "app.conf"),
strings.Replace(apiconf, "{{.Appname}}", args[0], -1))
ColorLog("[INFO] Creating API...\n")
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 != "" {
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(maingoContent, "{{.DriverName}}", string(driver), -1)
if driver == "mysql" {
@ -583,7 +591,7 @@ func createapi(cmd *Command, args []string) int {
} else if driver == "postgres" {
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1)
}
writetofile(path.Join(apppath, "main.go"),
WriteToFile(path.Join(apppath, "main.go"),
strings.Replace(
maingoContent,
"{{.conn}}",
@ -594,82 +602,73 @@ func createapi(cmd *Command, args []string) int {
ColorLog("[INFO] Using '%s' as 'driver'\n", driver)
ColorLog("[INFO] Using '%s' as 'conn'\n", conn)
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 {
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)
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"))
writetofile(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"),
strings.Replace(apiControllers, "{{.Appname}}", packpath, -1))
fmt.Println("create controllers user.go:", path.Join(apppath, "controllers", "user.go"))
writetofile(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"),
strings.Replace(apiControllers2, "{{.Appname}}", packpath, -1))
fmt.Println("create tests default.go:", path.Join(apppath, "tests", "default_test.go"))
writetofile(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(apiTests, "{{.Appname}}", packpath, -1))
fmt.Println("create routers router.go:", path.Join(apppath, "routers", "router.go"))
writetofile(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(apirouter, "{{.Appname}}", packpath, -1))
fmt.Println("create models object.go:", path.Join(apppath, "models", "object.go"))
writetofile(path.Join(apppath, "models", "object.go"), apiModels)
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)
fmt.Println("create models user.go:", path.Join(apppath, "models", "user.go"))
writetofile(path.Join(apppath, "models", "user.go"), apiModels2)
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)
fmt.Println("create docs doc.go:", path.Join(apppath, "docs", "doc.go"))
writetofile(path.Join(apppath, "docs", "doc.go"), "package docs")
fmt.Println("create main.go:", path.Join(apppath, "main.go"))
writetofile(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(apiMaingo, "{{.Appname}}", packpath, -1))
}
ColorLog("[SUCC] New API successfully created!\n")
return 0
}
func checkEnv(appname string) (apppath, packpath string, err error) {
curpath, err := os.Getwd()
if err != nil {
return
gps := GetGOPATHs()
if len(gps) == 0 {
ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty")
os.Exit(2)
}
gopath := os.Getenv("GOPATH")
Debugf("gopath:%s", gopath)
if gopath == "" {
err = fmt.Errorf("you should set GOPATH in the env")
return
}
appsrcpath := ""
haspath := false
wgopath := path.SplitList(gopath)
for _, wg := range wgopath {
wg, _ = path.EvalSymlinks(path.Join(wg, "src"))
if path.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) {
haspath = true
appsrcpath = wg
break
currpath, _ := os.Getwd()
currpath = path.Join(currpath, appname)
for _, gpath := range gps {
gsrcpath := path.Join(gpath, "src")
if strings.HasPrefix(currpath, gsrcpath) {
packpath = strings.Replace(currpath[len(gsrcpath)+1:], string(path.Separator), "/", -1)
return currpath, packpath, nil
}
}
if !haspath {
err = fmt.Errorf("can't create application outside of GOPATH `%s`\n"+
"you first should `cd $GOPATH%ssrc` then use create\n", gopath, string(path.Separator))
return
}
apppath = path.Join(curpath, appname)
// In case of multiple paths in the GOPATH, by default
// we use the first path
gopath := gps[0]
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)
gosrcpath := path.Join(gopath, "src")
apppath = path.Join(gosrcpath, appname)
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
}
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
}

26
bale.go
View File

@ -30,13 +30,15 @@ var cmdBale = &Command{
UsageLine: "bale",
Short: "packs non-Go files to Go source files",
Long: `
bale packs non-Go files to Go source files and
Bale command compress all the static files in to a single binary file.
auto-generate unpack function to main package then run it
This is usefull to not have to carry static files including js, css, images
and views when publishing a project.
during the runtime.
auto-generate unpack function to main package then run it during the runtime.
This is mainly used for zealots who are requiring 100% Go code.
This is mainly used for zealots who are requiring 100% Go code.`,
`,
}
func init() {
@ -44,6 +46,8 @@ func init() {
}
func runBale(cmd *Command, args []string) int {
ShowShortVersionBanner()
err := loadConfig()
if err != nil {
ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err)
@ -58,13 +62,13 @@ func runBale(cmd *Command, args []string) int {
ColorLog("[WARN] Skipped directory( %s )\n", p)
continue
}
ColorLog("[INFO] Packing directory( %s )\n", p)
ColorLog("[INFO] Packaging directory( %s )\n", p)
filepath.Walk(p, walkFn)
}
// Generate auto-uncompress function.
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\tbale.R")))
@ -86,7 +90,7 @@ func runBale(cmd *Command, args []string) int {
}
const (
_BALE_HEADER = `package main
BaleHeader = `package main
import(
"os"
@ -174,7 +178,7 @@ func walkFn(resPath string, info os.FileInfo, err error) error {
defer fw.Close()
// Write header.
fmt.Fprintf(fw, _HEADER, resPath)
fmt.Fprintf(fw, Header, resPath)
// Copy and compress data.
gz := gzip.NewWriter(&ByteWriter{Writer: fw})
@ -182,7 +186,7 @@ func walkFn(resPath string, info os.FileInfo, err error) error {
gz.Close()
// Write footer.
fmt.Fprint(fw, _FOOTER)
fmt.Fprint(fw, Footer)
resFiles = append(resFiles, resPath)
return nil
@ -198,7 +202,7 @@ func filterSuffix(name string) bool {
}
const (
_HEADER = `package bale
Header = `package bale
import(
"bytes"
@ -208,7 +212,7 @@ import(
func R%s() []byte {
gz, err := gzip.NewReader(bytes.NewBuffer([]byte{`
_FOOTER = `
Footer = `
}))
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"
)
const version = "1.2.2"
const version = "1.5.2"
type Command struct {
// Run runs the command.
@ -85,6 +85,7 @@ var commands = []*Command{
cmdGenerate,
//cmdRundocs,
cmdMigrate,
cmdFix,
}
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)})
}
}
return nil
}
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 (
"encoding/json"
"io/ioutil"
"os"
"gopkg.in/yaml.v2"
)
const CONF_VER = 0
const ConfVer = 0
var defaultConf = `{
"version": 0,
@ -50,20 +53,20 @@ var conf struct {
Install bool
}
// Indicates whether execute "go install" before "go build".
GoInstall bool `json:"go_install"`
WatchExt []string `json:"watch_ext"`
GoInstall bool `json:"go_install" yaml:"go_install"`
WatchExt []string `json:"watch_ext" yaml:"watch_ext"`
DirStruct struct {
WatchAll bool `json:"watch_all"`
WatchAll bool `json:"watch_all" yaml:"watch_all"`
Controllers string
Models string
Others []string // Other directories.
} `json:"dir_structure"`
CmdArgs []string `json:"cmd_args"`
} `json:"dir_structure" yaml:"dir_structure"`
CmdArgs []string `json:"cmd_args" yaml:"cmd_args"`
Envs []string
Bale struct {
Import string
Dirs []string
IngExt []string `json:"ignore_ext"`
IngExt []string `json:"ignore_ext" yaml:"ignore_ext"`
}
Database struct {
Driver string
@ -73,14 +76,9 @@ var conf struct {
// loadConfig loads customized configuration.
func loadConfig() error {
foundConf := false
f, err := os.Open("bee.json")
if err != nil {
// Use default.
err = json.Unmarshal([]byte(defaultConf), &conf)
if err != nil {
return err
}
} else {
if err == nil {
defer f.Close()
ColorLog("[INFO] Detected bee.json\n")
d := json.NewDecoder(f)
@ -88,10 +86,26 @@ func loadConfig() error {
if err != nil {
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.
if conf.Version != CONF_VER {
if conf.Version != ConfVer {
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")
}

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

View File

@ -29,9 +29,9 @@ import (
)
const (
O_MODEL byte = 1 << iota
O_CONTROLLER
O_ROUTER
OModel byte = 1 << iota
OController
ORouter
)
// 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",
"timestamp": "time.Time",
"timestamp without time zone": "time.Time",
"timestamp with time zone": "time.Time",
"interval": "string", // time interval, string for now
"real": "float32", // float & decimal
"double precision": "float64",
@ -130,6 +131,7 @@ var typeMappingPostgres = map[string]string{
"USER-DEFINED": "string", // user defined
"uuid": "string", // uuid
"json": "string", // json
"jsonb": "string",
}
// Table represent a table in a database
@ -257,11 +259,11 @@ func generateAppcode(driver, connStr, level, tables, currpath string) {
var mode byte
switch level {
case "1":
mode = O_MODEL
mode = OModel
case "2":
mode = O_MODEL | O_CONTROLLER
mode = OModel | OController
case "3":
mode = O_MODEL | O_CONTROLLER | O_ROUTER
mode = OModel | OController | ORouter
default:
ColorLog("[ERRO] Invalid 'level' option: %s\n", level)
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
// 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)
if err != nil {
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)
tables := getTableObjects(tableNames, db, trans)
mvcPath := new(MvcPath)
mvcPath.ModelPath = path.Join(currpath, "models")
mvcPath.ControllerPath = path.Join(currpath, "controllers")
mvcPath.RouterPath = path.Join(currpath, "routers")
mvcPath.ModelPath = path.Join(apppath, "models")
mvcPath.ControllerPath = path.Join(apppath, "controllers")
mvcPath.RouterPath = path.Join(apppath, "routers")
createPaths(mode, mvcPath)
pkgPath := getPackagePath(currpath)
pkgPath := getPackagePath(apppath)
writeSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames)
} else {
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) {
var typeMapping = map[string]string{}
typeMapping = typeMappingMysql
if v, ok := typeMapping[sqlType]; ok {
return v
} else {
}
ColorLog("[ERRO] data type (%s) not found!\n", sqlType)
os.Exit(2)
}
return goType
}
@ -514,7 +515,9 @@ func (*MysqlDB) GetGoDataType(sqlType string) (goType string) {
func (*PostgresDB) GetTableNames(db *sql.DB) (tables []string) {
rows, err := db.Query(`
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 {
ColorLog("[ERRO] Could not show tables: %s\n", err)
ColorLog("[HINT] Check your connection string\n")
@ -549,8 +552,10 @@ func (*PostgresDB) GetConstraints(db *sql.DB, table *Table, blackList map[string
INNER JOIN
information_schema.constraint_column_usage cu ON cu.constraint_name = c.constraint_name
WHERE
c.table_catalog = current_database() AND c.table_schema = 'public' AND c.table_name = $1
AND u.table_catalog = current_database() AND u.table_schema = 'public' AND u.table_name = $2`,
c.table_catalog = current_database() AND c.table_schema NOT IN ('pg_catalog', 'information_schema')
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,
if err != nil {
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
information_schema.columns
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)
defer colDefRows.Close()
for colDefRows.Next() {
@ -682,25 +688,26 @@ func (postgresDB *PostgresDB) GetColumns(db *sql.DB, table *Table, blackList map
table.Columns = append(table.Columns, col)
}
}
// GetGoDataType returns the Go type from the mapped Postgres type
func (*PostgresDB) GetGoDataType(sqlType string) (goType string) {
if v, ok := typeMappingPostgres[sqlType]; ok {
return v
} else {
}
ColorLog("[ERRO] data type (%s) not found!\n", sqlType)
os.Exit(2)
}
return goType
}
// deleteAndRecreatePaths removes several directories completely
func createPaths(mode byte, paths *MvcPath) {
if (mode & O_MODEL) == O_MODEL {
if (mode & OModel) == OModel {
os.Mkdir(paths.ModelPath, 0777)
}
if (mode & O_CONTROLLER) == O_CONTROLLER {
if (mode & OController) == OController {
os.Mkdir(paths.ControllerPath, 0777)
}
if (mode & O_ROUTER) == O_ROUTER {
if (mode & ORouter) == ORouter {
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
// Newly geneated files will be inside these folders.
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")
writeModelFiles(tables, paths.ModelPath, selectedTables)
}
if (O_CONTROLLER & mode) == O_CONTROLLER {
if (OController & mode) == OController {
ColorLog("[INFO] Creating controller files...\n")
writeControllerFiles(tables, paths.ControllerPath, selectedTables, pkgPath)
}
if (O_ROUTER & mode) == O_ROUTER {
if (ORouter & mode) == ORouter {
ColorLog("[INFO] Creating router files...\n")
writeRouterFile(tables, paths.RouterPath, selectedTables, pkgPath)
}
@ -725,6 +732,8 @@ func writeSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath
// writeModelFiles generates model files
func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bool) {
w := NewColorWriter(os.Stdout)
for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it
if selectedTables != nil {
@ -737,7 +746,7 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
var f *os.File
var err error
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() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil {
@ -745,7 +754,7 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
continue
}
} else {
ColorLog("[WARN] skip create file\n")
ColorLog("[WARN] Skipped create file '%s'\n", fpath)
continue
}
} else {
@ -757,12 +766,13 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
}
template := ""
if tb.Pk == "" {
template = STRUCT_MODEL_TPL
template = StructModelTPL
} else {
template = MODEL_TPL
template = ModelTPL
}
fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1)
fileStr = strings.Replace(fileStr, "{{modelName}}", camelCase(tb.Name), -1)
fileStr = strings.Replace(fileStr, "{{tableName}}", tb.Name, -1)
// if table contains time field, import time.Time package
timePkg := ""
importTimePkg := ""
@ -776,14 +786,16 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
ColorLog("[ERRO] Could not write model file to %s\n", fpath)
os.Exit(2)
}
f.Close()
ColorLog("[INFO] model => %s\n", fpath)
CloseFile(f)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath)
}
}
// writeControllerFiles generates controller files
func writeControllerFiles(tables []*Table, cPath string, selectedTables map[string]bool, pkgPath string) {
w := NewColorWriter(os.Stdout)
for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it
if selectedTables != nil {
@ -799,7 +811,7 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri
var f *os.File
var err error
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() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil {
@ -807,7 +819,7 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri
continue
}
} else {
ColorLog("[WARN] skip create file\n")
ColorLog("[WARN] Skipped create file '%s'\n", fpath)
continue
}
} else {
@ -817,20 +829,22 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri
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)
if _, err := f.WriteString(fileStr); err != nil {
ColorLog("[ERRO] Could not write controller file to %s\n", fpath)
os.Exit(2)
}
f.Close()
ColorLog("[INFO] controller => %s\n", fpath)
CloseFile(f)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath)
}
}
// writeRouterFile generates router file
func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bool, pkgPath string) {
w := NewColorWriter(os.Stdout)
var nameSpaces []string
for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it
@ -843,18 +857,18 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo
continue
}
// 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)
nameSpaces = append(nameSpaces, nameSpace)
}
// add export controller
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)
var f *os.File
var err error
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() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil {
@ -862,7 +876,7 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo
return
}
} else {
ColorLog("[WARN] skip create file\n")
ColorLog("[WARN] Skipped create file '%s'\n", fpath)
return
}
} else {
@ -873,11 +887,11 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo
}
}
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)
}
f.Close()
ColorLog("[INFO] router => %s\n", fpath)
CloseFile(f)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath)
}
@ -960,7 +974,7 @@ func getPackagePath(curpath string) (packpath string) {
gopath := os.Getenv("GOPATH")
Debugf("gopath:%s", gopath)
if gopath == "" {
ColorLog("[ERRO] you should set GOPATH in the env")
ColorLog("[ERRO] You should set GOPATH in the env")
os.Exit(2)
}
@ -982,17 +996,23 @@ func getPackagePath(curpath string) (packpath string) {
ColorLog("[ERRO] Can't generate application code outside of GOPATH '%s'\n", gopath)
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)), "/")
return
}
const (
STRUCT_MODEL_TPL = `package models
StructModelTPL = `package models
{{importTimePkg}}
{{modelStruct}}
`
MODEL_TPL = `package models
ModelTPL = `package models
import (
"errors"
@ -1005,6 +1025,10 @@ import (
{{modelStruct}}
func (t *{{modelName}}) TableName() string {
return "{{tableName}}"
}
func init() {
orm.RegisterModel(new({{modelName}}))
}
@ -1081,7 +1105,7 @@ func GetAll{{modelName}}(query map[string]string, fields []string, sortby []stri
var l []{{modelName}}
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 {
for _, v := range l {
ml = append(ml, v)
@ -1132,7 +1156,7 @@ func Delete{{modelName}}(id int) (err error) {
return
}
`
CTRL_TPL = `package controllers
CtrlTPL = `package controllers
import (
"{{pkgPath}}/models"
@ -1144,54 +1168,62 @@ import (
"github.com/astaxie/beego"
)
// oprations for {{ctrlName}}
// {{ctrlName}}Controller oprations for {{ctrlName}}
type {{ctrlName}}Controller struct {
beego.Controller
}
func (this *{{ctrlName}}Controller) URLMapping() {
this.Mapping("Post", this.Post)
this.Mapping("GetOne", this.GetOne)
this.Mapping("GetAll", this.GetAll)
this.Mapping("Put", this.Put)
this.Mapping("Delete", this.Delete)
// URLMapping ...
func (c *{{ctrlName}}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 {{ctrlName}}
// @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
// @router / [post]
func (this *{{ctrlName}}Controller) Post() {
func (c *{{ctrlName}}Controller) Post() {
var v models.{{ctrlName}}
json.Unmarshal(this.Ctx.Input.RequestBody, &v)
if id, err := models.Add{{ctrlName}}(&v); err == nil {
this.Data["json"] = map[string]int64{"id": id}
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &v); err == nil {
if _, err := models.Add{{ctrlName}}(&v); err == nil {
c.Ctx.Output.SetStatus(201)
c.Data["json"] = v
} else {
this.Data["json"] = err.Error()
c.Data["json"] = err.Error()
}
this.ServeJson()
} else {
c.Data["json"] = err.Error()
}
c.ServeJSON()
}
// @Title Get
// GetOne ...
// @Title Get One
// @Description get {{ctrlName}} by id
// @Param id path string true "The key for staticblock"
// @Success 200 {object} models.{{ctrlName}}
// @Failure 403 :id is empty
// @router /:id [get]
func (this *{{ctrlName}}Controller) GetOne() {
idStr := this.Ctx.Input.Params[":id"]
func (c *{{ctrlName}}Controller) GetOne() {
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.Atoi(idStr)
v, err := models.Get{{ctrlName}}ById(id)
if err != nil {
this.Data["json"] = err.Error()
c.Data["json"] = err.Error()
} else {
this.Data["json"] = v
c.Data["json"] = v
}
this.ServeJson()
c.ServeJSON()
}
// GetAll ...
// @Title Get All
// @Description get {{ctrlName}}
// @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..."
@ -1203,41 +1235,41 @@ func (this *{{ctrlName}}Controller) GetOne() {
// @Success 200 {object} models.{{ctrlName}}
// @Failure 403
// @router / [get]
func (this *{{ctrlName}}Controller) GetAll() {
func (c *{{ctrlName}}Controller) GetAll() {
var fields []string
var sortby []string
var order []string
var query map[string]string = make(map[string]string)
var query = make(map[string]string)
var limit int64 = 10
var offset int64 = 0
var offset int64
// fields: col1,col2,entity.col3
if v := this.GetString("fields"); v != "" {
if v := c.GetString("fields"); v != "" {
fields = strings.Split(v, ",")
}
// limit: 10 (default is 10)
if v, err := this.GetInt("limit"); err == nil {
if v, err := c.GetInt64("limit"); err == nil {
limit = v
}
// offset: 0 (default is 0)
if v, err := this.GetInt("offset"); err == nil {
if v, err := c.GetInt64("offset"); err == nil {
offset = v
}
// sortby: col1,col2
if v := this.GetString("sortby"); v != "" {
if v := c.GetString("sortby"); v != "" {
sortby = strings.Split(v, ",")
}
// order: desc,asc
if v := this.GetString("order"); v != "" {
if v := c.GetString("order"); v != "" {
order = strings.Split(v, ",")
}
// query: k:v,k:v
if v := this.GetString("query"); v != "" {
if v := c.GetString("query"); v != "" {
for _, cond := range strings.Split(v, ",") {
kv := strings.Split(cond, ":")
kv := strings.SplitN(cond, ":", 2)
if len(kv) != 2 {
this.Data["json"] = errors.New("Error: invalid query key/value pair")
this.ServeJson()
c.Data["json"] = errors.New("Error: invalid query key/value pair")
c.ServeJSON()
return
}
k, v := kv[0], kv[1]
@ -1247,51 +1279,56 @@ func (this *{{ctrlName}}Controller) GetAll() {
l, err := models.GetAll{{ctrlName}}(query, fields, sortby, order, offset, limit)
if err != nil {
this.Data["json"] = err.Error()
c.Data["json"] = err.Error()
} else {
this.Data["json"] = l
c.Data["json"] = l
}
this.ServeJson()
c.ServeJSON()
}
// @Title Update
// Put ...
// @Title Put
// @Description update the {{ctrlName}}
// @Param id path string true "The id you want to update"
// @Param body body models.{{ctrlName}} true "body for {{ctrlName}} content"
// @Success 200 {object} models.{{ctrlName}}
// @Failure 403 :id is not int
// @router /:id [put]
func (this *{{ctrlName}}Controller) Put() {
idStr := this.Ctx.Input.Params[":id"]
func (c *{{ctrlName}}Controller) Put() {
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.Atoi(idStr)
v := models.{{ctrlName}}{Id: id}
json.Unmarshal(this.Ctx.Input.RequestBody, &v)
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &v); err == nil {
if err := models.Update{{ctrlName}}ById(&v); err == nil {
this.Data["json"] = "OK"
c.Data["json"] = "OK"
} else {
this.Data["json"] = err.Error()
c.Data["json"] = err.Error()
}
this.ServeJson()
} else {
c.Data["json"] = err.Error()
}
c.ServeJSON()
}
// Delete ...
// @Title Delete
// @Description delete the {{ctrlName}}
// @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 (this *{{ctrlName}}Controller) Delete() {
idStr := this.Ctx.Input.Params[":id"]
func (c *{{ctrlName}}Controller) Delete() {
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.Atoi(idStr)
if err := models.Delete{{ctrlName}}(id); err == nil {
this.Data["json"] = "OK"
c.Data["json"] = "OK"
} else {
this.Data["json"] = err.Error()
c.Data["json"] = err.Error()
}
this.ServeJson()
c.ServeJSON()
}
`
ROUTER_TPL = `// @APIVersion 1.0.0
RouterTPL = `// @APIVersion 1.0.0
// @Title beego Test API
// @Description beego has a very cool tools to autogenerate documents for your API
// @Contact astaxie@gmail.com
@ -1313,7 +1350,7 @@ func init() {
beego.AddNamespace(ns)
}
`
NAMESPACE_TPL = `
NamespaceTPL = `
beego.NSNamespace("/{{nameSpace}}",
beego.NSInclude(
&controllers.{{ctrlName}}Controller{},

View File

@ -15,6 +15,7 @@
package main
import (
"fmt"
"os"
"path"
"strings"
@ -23,35 +24,53 @@ import (
// article
// cms/article
//
func generateController(cname, crupath string) {
func generateController(cname, currpath string) {
w := NewColorWriter(os.Stdout)
p, f := path.Split(cname)
controllerName := strings.Title(f)
packageName := "controllers"
if p != "" {
i := strings.LastIndex(p[:len(p)-1], "/")
packageName = p[i+1 : len(p)-1]
}
ColorLog("[INFO] Using '%s' as controller name\n", controllerName)
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) {
// create controller directory
// Create the controller's directory
if err := os.MkdirAll(fp, 0777); err != nil {
ColorLog("[ERRO] Could not create controllers directory: %s\n", err)
os.Exit(2)
}
}
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 {
defer f.Close()
content := strings.Replace(controllerTpl, "{{packageName}}", packageName, -1)
defer CloseFile(f)
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)
f.WriteString(content)
// gofmt generated source code
// Run 'gofmt' on the generated source code
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 {
// error creating file
ColorLog("[ERRO] Could not create controller file: %s\n", err)
os.Exit(2)
}
@ -63,39 +82,43 @@ import (
"github.com/astaxie/beego"
)
// oprations for {{controllerName}}
// {{controllerName}}Controller operations for {{controllerName}}
type {{controllerName}}Controller struct {
beego.Controller
}
func (this *{{controllerName}}Controller) URLMapping() {
this.Mapping("Post", this.Post)
this.Mapping("GetOne", this.GetOne)
this.Mapping("GetAll", this.GetAll)
this.Mapping("Put", this.Put)
this.Mapping("Delete", this.Delete)
// 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)
}
// @Title Post
// Post ...
// @Title Create
// @Description create {{controllerName}}
// @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
// @router / [post]
func (this *{{controllerName}}Controller) Post() {
func (c *{{controllerName}}Controller) Post() {
}
// @Title Get
// GetOne ...
// @Title GetOne
// @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 (this *{{controllerName}}Controller) GetOne() {
func (c *{{controllerName}}Controller) GetOne() {
}
// GetAll ...
// @Title GetAll
// @Description get {{controllerName}}
// @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..."
@ -107,28 +130,197 @@ func (this *{{controllerName}}Controller) GetOne() {
// @Success 200 {object} models.{{controllerName}}
// @Failure 403
// @router / [get]
func (this *{{controllerName}}Controller) GetAll() {
func (c *{{controllerName}}Controller) GetAll() {
}
// @Title Update
// 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 (this *{{controllerName}}Controller) Put() {
func (c *{{controllerName}}Controller) Put() {
}
// 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 (this *{{controllerName}}Controller) Delete() {
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()
}
`

643
g_docs.go
View File

@ -31,63 +31,12 @@ import (
"strings"
"unicode"
"gopkg.in/yaml.v2"
"github.com/astaxie/beego/swagger"
"github.com/astaxie/beego/utils"
)
var globalDocsTemplate = `package docs
import (
"encoding/json"
"strings"
"github.com/astaxie/beego"
"github.com/astaxie/beego/swagger"
)
var rootinfo string = {{.rootinfo}}
var subapi string = {{.subapi}}
var rootapi swagger.ResourceListing
var apilist map[string]*swagger.ApiDeclaration
func init() {
basepath := "{{.version}}"
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 (
ajson = "application/json"
axml = "application/xml"
@ -95,21 +44,19 @@ const (
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 importlist map[string]string
var apilist map[string]*swagger.ApiDeclaration
var controllerList map[string][]swagger.Api
var modelsList map[string]map[string]swagger.Model
var rootapi swagger.ResourceListing
var controllerList map[string]map[string]*swagger.Item //controllername Paths items
var modelsList map[string]map[string]swagger.Schema
var rootapi swagger.Swagger
func init() {
pkgCache = make(map[string]bool)
pkgCache = make(map[string]struct{})
controllerComments = make(map[string]string)
importlist = make(map[string]string)
apilist = make(map[string]*swagger.ApiDeclaration)
controllerList = make(map[string][]swagger.Api)
modelsList = make(map[string]map[string]swagger.Model)
controllerList = make(map[string]map[string]*swagger.Item)
modelsList = make(map[string]map[string]swagger.Schema)
}
func generateDocs(curpath string) {
@ -122,64 +69,98 @@ func generateDocs(curpath string) {
os.Exit(2)
}
rootapi.Infos = swagger.Infomation{}
rootapi.SwaggerVersion = swagger.SwaggerVersion
rootapi.Infos = swagger.Information{}
rootapi.SwaggerVersion = "2.0"
//analysis API comments
if f.Comments != nil {
for _, c := range f.Comments {
for _, s := range strings.Split(c.Text(), "\n") {
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") {
rootapi.Infos.Title = strings.TrimSpace(s[len("@Title"):])
} else if strings.HasPrefix(s, "@Description") {
rootapi.Infos.Description = strings.TrimSpace(s[len("@Description"):])
} 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") {
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") {
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") {
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 {
analisyscontrollerPkg(im.Path.Value)
localName := ""
if im.Name != nil {
localName = im.Name.Name
}
analisyscontrollerPkg(localName, im.Path.Value)
}
for _, d := range f.Decls {
switch specDecl := d.(type) {
case *ast.FuncDecl:
for _, l := range specDecl.Body.List {
switch smtp := l.(type) {
switch stmt := l.(type) {
case *ast.AssignStmt:
for _, l := range smtp.Rhs {
for _, l := range stmt.Rhs {
if v, ok := l.(*ast.CallExpr); ok {
f, params := analisysNewNamespace(v)
globalDocsTemplate = strings.Replace(globalDocsTemplate, "{{.version}}", f, -1)
// analisys NewNamespace, it will return version and the subfunction
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 {
switch pp := p.(type) {
case *ast.CallExpr:
controllerName := ""
if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" {
s, params := analisysNewNamespace(pp)
subapi := swagger.ApiRef{Path: s}
controllerName := ""
for _, sp := range params {
switch pp := sp.(type) {
case *ast.CallExpr:
if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" {
controllerName = analisysNSInclude(s, pp)
}
}
}
if v, ok := controllerComments[controllerName]; ok {
subapi.Description = v
rootapi.Tags = append(rootapi.Tags, swagger.Tag{
Name: strings.Trim(s, "/"),
Description: v,
})
}
}
}
}
rootapi.Apis = append(rootapi.Apis, subapi)
} else if selname == "NSInclude" {
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,
})
}
}
}
}
@ -190,25 +171,27 @@ func generateDocs(curpath string) {
}
}
}
apiinfo, err := json.Marshal(rootapi)
if err != nil {
panic(err)
}
subapi, err := json.Marshal(apilist)
if err != nil {
panic(err)
}
os.Mkdir(path.Join(curpath, "docs"), 0755)
fd, err := os.Create(path.Join(curpath, "docs", "docs.go"))
os.Mkdir(path.Join(curpath, "swagger"), 0755)
fd, err := os.Create(path.Join(curpath, "swagger", "swagger.json"))
fdyml, err := os.Create(path.Join(curpath, "swagger", "swagger.yml"))
if err != nil {
panic(err)
}
defer fdyml.Close()
defer fd.Close()
a := strings.Replace(globalDocsTemplate, "{{.rootinfo}}", "`"+string(apiinfo)+"`", -1)
a = strings.Replace(a, "{{.subapi}}", "`"+string(subapi)+"`", -1)
fd.WriteString(a)
dt, err := json.MarshalIndent(rootapi, "", " ")
dtyml, erryml := yaml.Marshal(rootapi)
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) {
for i, p := range ce.Args {
if i == 0 {
@ -225,45 +208,66 @@ func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) {
func analisysNSInclude(baseurl string, ce *ast.CallExpr) string {
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 {
x := p.(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.SelectorExpr)
if v, ok := importlist[fmt.Sprint(x.X)]; ok {
cname = v + x.Sel.Name
}
if apis, ok := controllerList[cname]; ok {
if len(a.Apis) > 0 {
a.Apis = append(a.Apis, apis...)
for rt, item := range apis {
tag := ""
if baseurl != "" {
rt = baseurl + rt
tag = strings.Trim(baseurl, "/")
} else {
a.Apis = apis
tag = cname
}
if item.Get != nil {
item.Get.Tags = []string{tag}
}
if models, ok := modelsList[cname]; ok {
for _, m := range models {
a.Models[m.Id] = m
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
}
func analisyscontrollerPkg(pkgpath string) {
func analisyscontrollerPkg(localName, pkgpath string) {
pkgpath = strings.Trim(pkgpath, "\"")
if isSystemPackage(pkgpath) {
return
}
pps := strings.Split(pkgpath, "/")
importlist[pps[len(pps)-1]] = pkgpath
if pkgpath == "github.com/astaxie/beego" {
return
}
if localName != "" {
importlist[localName] = pkgpath
} else {
pps := strings.Split(pkgpath, "/")
importlist[pps[len(pps)-1]] = pkgpath
}
gopath := os.Getenv("GOPATH")
if gopath == "" {
panic("please set gopath")
@ -282,6 +286,7 @@ func analisyscontrollerPkg(pkgpath string) {
if _, ok := pkgCache[pkgpath]; ok {
return
}
pkgCache[pkgpath] = struct{}{}
} else {
ColorLog("[ERRO] the %s pkg not exist in gopath\n", pkgpath)
os.Exit(1)
@ -303,15 +308,18 @@ func analisyscontrollerPkg(pkgpath string) {
case *ast.FuncDecl:
if specDecl.Recv != nil && len(specDecl.Recv.List) > 0 {
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)
}
}
case *ast.GenDecl:
if specDecl.Tok.String() == "type" {
if specDecl.Tok == token.TYPE {
for _, s := range specDecl.Specs {
switch tp := s.(*ast.TypeSpec).Type.(type) {
case *ast.StructType:
_ = tp.Struct
//parse controller definition comments
if strings.TrimSpace(specDecl.Doc.Text()) != "" {
controllerComments[pkgpath+s.(*ast.TypeSpec).Name.String()] = specDecl.Doc.Text()
}
}
@ -321,6 +329,7 @@ func analisyscontrollerPkg(pkgpath string) {
}
}
}
}
func isSystemPackage(pkgpath string) bool {
goroot := runtime.GOROOT()
@ -331,13 +340,34 @@ func isSystemPackage(pkgpath string) bool {
if utils.FileExists(wg) {
return true
}
//TODO(zh):support go1.4
wg, _ = filepath.EvalSymlinks(filepath.Join(goroot, "src", pkgpath))
if utils.FileExists(wg) {
return true
}
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
func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error {
innerapi := swagger.Api{}
opts := swagger.Operation{}
var routerPath string
var HTTPMethod string
opts := swagger.Operation{
Responses: make(map[string]swagger.Response),
}
if comments != nil && comments.List != nil {
for _, c := range comments.List {
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
@ -347,66 +377,64 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
if len(e1) < 1 {
return errors.New("you should has router infomation")
}
innerapi.Path = e1[0]
routerPath = e1[0]
if len(e1) == 2 && e1[1] != "" {
e1 = strings.SplitN(e1[1], " ", 2)
opts.HttpMethod = strings.ToUpper(strings.Trim(e1[0], "[]"))
HTTPMethod = strings.ToUpper(strings.Trim(e1[0], "[]"))
} else {
opts.HttpMethod = "GET"
HTTPMethod = "GET"
}
} 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") {
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") {
ss := strings.TrimSpace(t[len("@Success"):])
rs := swagger.ResponseMessage{}
st := make([]string, 3)
j := 0
var tmp []rune
start := false
for i, c := range ss {
if unicode.IsSpace(c) {
if !start && j < 2 {
continue
rs := swagger.Response{}
respCode, pos := peekNextSplitString(ss)
ss = strings.TrimSpace(ss[pos:])
respType, pos := peekNextSplitString(ss)
if respType == "{object}" || respType == "{array}" {
isArray := respType == "{array}"
ss = strings.TrimSpace(ss[pos:])
schemaName, pos := peekNextSplitString(ss)
if schemaName == "" {
ColorLog("[ERRO][%s.%s] Schema must follow {object} or {array}\n", controllerName, funcName)
os.Exit(-1)
}
if j == 0 || j == 1 {
st[j] = string(tmp)
tmp = make([]rune, 0)
j += 1
start = false
continue
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 {
st[j] = strings.TrimSpace(ss[i+1:])
break
}
} else {
start = true
tmp = append(tmp, c)
}
}
if len(tmp) > 0 && st[2] == "" {
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
cmpath, m, mod, realTypes := getModel(schemaName)
schema.Ref = "#/definitions/" + m
if _, ok := modelsList[pkgpath+controllerName]; !ok {
modelsList[pkgpath+controllerName] = make(map[string]swagger.Model, 0)
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0)
}
modelsList[pkgpath+controllerName][st[2]] = mod
modelsList[pkgpath+controllerName][schemaName] = mod
appendModels(cmpath, pkgpath, controllerName, realTypes)
}
rs.Code, _ = strconv.Atoi(st[0])
opts.ResponseMessages = append(opts.ResponseMessages, rs)
if isArray {
rs.Schema = &swagger.Schema{
Type: "array",
Items: &schema,
}
} else {
rs.Schema = &schema
}
rs.Description = strings.TrimSpace(ss[pos:])
} else {
rs.Description = strings.TrimSpace(ss)
}
opts.Responses[respCode] = rs
} else if strings.HasPrefix(t, "@Param") {
para := swagger.Parameter{}
p := getparams(strings.TrimSpace(t[len("@Param "):]))
@ -414,25 +442,78 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
panic(controllerName + "_" + funcName + "'s comments @Param at least should has 4 params")
}
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], ".")
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 {
para.Required, _ = strconv.ParseBool(p[3])
para.Description = p[4]
para.Description = strings.Trim(p[4], `" `)
} else {
para.Description = p[3]
para.Description = strings.Trim(p[3], `" `)
}
opts.Parameters = append(opts.Parameters, para)
} else if strings.HasPrefix(t, "@Failure") {
rs := swagger.ResponseMessage{}
rs := swagger.Response{}
st := strings.TrimSpace(t[len("@Failure"):])
var cd []rune
var start bool
for i, s := range st {
if unicode.IsSpace(s) {
if start {
rs.Message = strings.TrimSpace(st[i+1:])
rs.Description = strings.TrimSpace(st[i+1:])
break
} else {
continue
@ -441,10 +522,9 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
start = true
cd = append(cd, s)
}
rs.Code, _ = strconv.Atoi(string(cd))
opts.ResponseMessages = append(opts.ResponseMessages, rs)
} else if strings.HasPrefix(t, "@Type") {
opts.Type = strings.TrimSpace(t[len("@Type"):])
opts.Responses[string(cd)] = rs
} else if strings.HasPrefix(t, "@Deprecated") {
opts.Deprecated, _ = strconv.ParseBool(strings.TrimSpace(t[len("@Deprecated"):]))
} else if strings.HasPrefix(t, "@Accept") {
accepts := strings.Split(strings.TrimSpace(strings.TrimSpace(t[len("@Accept"):])), ",")
for _, a := range accepts {
@ -466,14 +546,35 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
}
}
}
innerapi.Operations = append(innerapi.Operations, opts)
if innerapi.Path != "" {
if _, ok := controllerList[pkgpath+controllerName]; ok {
controllerList[pkgpath+controllerName] = append(controllerList[pkgpath+controllerName], innerapi)
if routerPath != "" {
var item *swagger.Item
if itemList, ok := controllerList[pkgpath+controllerName]; ok {
if it, ok := itemList[routerPath]; !ok {
item = &swagger.Item{}
} else {
controllerList[pkgpath+controllerName] = make([]swagger.Api, 1)
controllerList[pkgpath+controllerName][0] = innerapi
item = it
}
} else {
controllerList[pkgpath+controllerName] = make(map[string]*swagger.Item)
item = &swagger.Item{}
}
switch HTTPMethod {
case "GET":
item.Get = &opts
case "POST":
item.Post = &opts
case "PUT":
item.Put = &opts
case "PATCH":
item.Patch = &opts
case "DELETE":
item.Delete = &opts
case "HEAD":
item.Head = &opts
case "OPTIONS":
item.Options = &opts
}
controllerList[pkgpath+controllerName][routerPath] = item
}
return nil
}
@ -509,7 +610,7 @@ func getparams(str string) []string {
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, ".")
objectname = strs[len(strs)-1]
pkgpath = strings.Join(strs[:len(strs)-1], "/")
@ -525,7 +626,7 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Model, realType
ColorLog("[ERRO] the model %s parser.ParseDir error\n", str)
os.Exit(1)
}
m.Type = "object"
for _, pkg := range astPkgs {
for _, fl := range pkg.Files {
for k, d := range fl.Scope.Objects {
@ -533,41 +634,96 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Model, realType
if k != objectname {
continue
}
parseObject(d, k, &m, &realTypes, astPkgs)
}
}
}
}
if m.Title == "" {
ColorLog("[WARN]can't find the object: %s\n", str)
// 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
}
func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string, astPkgs map[string]*ast.Package) {
ts, ok := d.Decl.(*ast.TypeSpec)
if !ok {
ColorLog("Unknown type without TypeSec: %v", d)
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
}
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
}
// if the tag contains json tag, set the name to the left most json tag
var name = field.Names[0].Name
if field.Tag != nil {
var tagValues []string
stag := reflect.StructTag(strings.Trim(field.Tag.Value, "`"))
if tag := stag.Get("json"); tag != "" {
name = tag
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] != "" {
@ -580,66 +736,77 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Model, realType
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)
}
}
return
}
}
}
}
if m.Id == "" {
ColorLog("can't find the object: %v", str)
os.Exit(1)
}
return
}
func typeAnalyser(f *ast.Field) (isSlice bool, realType string) {
func typeAnalyser(f *ast.Field) (isSlice bool, realType, swaggerType string) {
if arr, ok := f.Type.(*ast.ArrayType); ok {
if isBasicType(fmt.Sprint(arr.Elt)) {
return false, fmt.Sprintf("[]%v", arr.Elt)
return false, fmt.Sprintf("[]%v", arr.Elt), basicTypes[fmt.Sprint(arr.Elt)]
}
if mp, ok := arr.Elt.(*ast.MapType); ok {
return false, fmt.Sprintf("map[%v][%v]", mp.Key, mp.Value)
return false, fmt.Sprintf("map[%v][%v]", mp.Key, mp.Value), "object"
}
if star, ok := arr.Elt.(*ast.StarExpr); ok {
return true, fmt.Sprint(star.X)
} else {
return true, fmt.Sprint(arr.Elt)
return true, fmt.Sprint(star.X), "object"
}
return true, fmt.Sprint(arr.Elt), "object"
}
} else {
switch t := f.Type.(type) {
case *ast.StarExpr:
return false, fmt.Sprint(t.X)
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, fmt.Sprint(f.Type)
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 {
for _, v := range basicTypes {
if v == Type {
if _, ok := basicTypes[Type]; ok {
return true
}
}
return false
}
// refer to builtin.go
var basicTypes = []string{
"bool",
"uint", "uint8", "uint16", "uint32", "uint64",
"int", "int8", "int16", "int32", "int64",
"float32", "float64",
"string",
"complex64", "complex128",
"byte", "rune", "uintptr",
var basicTypes = map[string]string{
"bool": "boolean:",
"uint": "integer:int32", "uint8": "integer:int32", "uint16": "integer:int32", "uint32": "integer:int32", "uint64": "integer:int64",
"int": "integer:int64", "int8": "integer:int32", "int16:int32": "integer:int32", "int32": "integer:int32", "int64": "integer:int64",
"uintptr": "integer:int64",
"float32": "number:float", "float64": "number:double",
"string": "string:",
"complex64": "number:float", "complex128": "number:double",
"byte": "string:byte", "rune": "string:byte",
}
// regexp get json tag
func grepJsonTag(tag string) string {
func grepJSONTag(tag string) string {
r, _ := regexp.Compile(`json:"([^"]*)"`)
matches := r.FindAllStringSubmatch(tag, -1)
if len(matches) > 0 {
@ -669,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 (
"database/sql"
"fmt"
"os"
"path"
"strings"
@ -31,11 +32,11 @@ func generateHproseAppcode(driver, connStr, level, tables, currpath string) {
var mode byte
switch level {
case "1":
mode = O_MODEL
mode = OModel
case "2":
mode = O_MODEL | O_CONTROLLER
mode = OModel | OController
case "3":
mode = O_MODEL | O_CONTROLLER | O_ROUTER
mode = OModel | OController | ORouter
default:
ColorLog("[ERRO] Invalid 'level' option: %s\n", level)
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
// Newly geneated files will be inside these folders.
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")
writeHproseModelFiles(tables, paths.ModelPath, selectedTables)
}
@ -98,6 +99,8 @@ func writeHproseSourceFiles(pkgPath string, tables []*Table, mode byte, paths *M
// writeHproseModelFiles generates model files
func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[string]bool) {
w := NewColorWriter(os.Stdout)
for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it
if selectedTables != nil {
@ -110,7 +113,7 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str
var f *os.File
var err error
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() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil {
@ -118,7 +121,7 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str
continue
}
} else {
ColorLog("[WARN] skip create file\n")
ColorLog("[WARN] Skipped create file '%s'\n", fpath)
continue
}
} else {
@ -130,10 +133,10 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str
}
template := ""
if tb.Pk == "" {
template = HPROSE_STRUCT_MODEL_TPL
template = HproseStructModelTPL
} else {
template = HPROSE_MODEL_TPL
hproseAddFunctions = append(hproseAddFunctions, strings.Replace(HPROSE_ADDFUNCTION, "{{modelName}}", camelCase(tb.Name), -1))
template = HproseModelTPL
hproseAddFunctions = append(hproseAddFunctions, strings.Replace(HproseAddFunction, "{{modelName}}", camelCase(tb.Name), -1))
}
fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 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, "{{importTimePkg}}", importTimePkg, -1)
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)
}
f.Close()
ColorLog("[INFO] model => %s\n", fpath)
CloseFile(f)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath)
}
}
const (
HPROSE_ADDFUNCTION = `
HproseAddFunction = `
// publish about {{modelName}} function
service.AddFunction("Add{{modelName}}", models.Add{{modelName}})
service.AddFunction("Get{{modelName}}ById", models.Get{{modelName}}ById)
@ -166,12 +169,12 @@ const (
service.AddFunction("Delete{{modelName}}", models.Delete{{modelName}})
`
HPROSE_STRUCT_MODEL_TPL = `package models
HproseStructModelTPL = `package models
{{importTimePkg}}
{{modelStruct}}
`
HPROSE_MODEL_TPL = `package models
HproseModelTPL = `package models
import (
"errors"
@ -260,7 +263,7 @@ func GetAll{{modelName}}(query map[string]string, fields []string, sortby []stri
var l []{{modelName}}
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 {
for _, v := range l {
ml = append(ml, v)

View File

@ -23,15 +23,170 @@ import (
)
const (
M_PATH = "migrations"
M_DATE_FORMAT = "20060102_150405"
MPath = "migrations"
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.
// The generated file template consists of an up() method for updating schema and
// a down() method for reverting the update.
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) {
// create migrations directory
if err := os.MkdirAll(migrationFilePath, 0777); err != nil {
@ -40,26 +195,25 @@ func generateMigration(mname, upsql, downsql, curpath string) {
}
}
// 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))
if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer f.Close()
content := strings.Replace(MIGRATION_TPL, "{{StructName}}", camelCase(mname)+"_"+today, -1)
defer CloseFile(f)
content := strings.Replace(MigrationTPL, "{{StructName}}", camelCase(mname)+"_"+today, -1)
content = strings.Replace(content, "{{CurrTime}}", today, -1)
content = strings.Replace(content, "{{UpSQL}}", upsql, -1)
content = strings.Replace(content, "{{DownSQL}}", downsql, -1)
f.WriteString(content)
// gofmt generated source code
// Run 'gofmt' on the generated source code
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 {
// error creating file
ColorLog("[ERRO] Could not create migration file: %s\n", err)
os.Exit(2)
}
}
const MIGRATION_TPL = `package main
const MigrationTPL = `package main
import (
"github.com/astaxie/beego/migration"
@ -79,13 +233,13 @@ func init() {
// Run the migrations
func (m *{{StructName}}) Up() {
// use m.Sql("CREATE TABLE ...") to make schema update
// use m.SQL("CREATE TABLE ...") to make schema update
{{UpSQL}}
}
// Reverse the migrations
func (m *{{StructName}}) Down() {
// use m.Sql("DROP TABLE ...") to reverse schema update
// use m.SQL("DROP TABLE ...") to reverse schema update
{{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
import (
"errors"
"fmt"
"os"
"path"
"strings"
)
func generateModel(mname, fields, crupath string) {
func generateModel(mname, fields, currpath string) {
w := NewColorWriter(os.Stdout)
p, f := path.Split(mname)
modelName := strings.Title(f)
packageName := "models"
@ -15,24 +32,28 @@ func generateModel(mname, fields, crupath string) {
i := strings.LastIndex(p[: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 {
ColorLog("[ERRO] Could not genrate models struct: %s\n", err)
ColorLog("[ERRO] Could not generate the model struct: %s\n", err)
os.Exit(2)
}
ColorLog("[INFO] Using '%s' as model name\n", modelName)
ColorLog("[INFO] Using '%s' as package name\n", packageName)
fp := path.Join(crupath, "models", p)
fp := path.Join(currpath, "models", p)
if _, err := os.Stat(fp); os.IsNotExist(err) {
// create controller directory
// Create the model's directory
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)
}
}
fpath := path.Join(fp, strings.ToLower(modelName)+".go")
if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer f.Close()
defer CloseFile(f)
content := strings.Replace(modelTpl, "{{packageName}}", packageName, -1)
content = strings.Replace(content, "{{modelName}}", modelName, -1)
content = strings.Replace(content, "{{modelStruct}}", modelStruct, -1)
@ -42,42 +63,45 @@ func generateModel(mname, fields, crupath string) {
content = strings.Replace(content, "{{timePkg}}", "", -1)
}
f.WriteString(content)
// gofmt generated source code
// Run 'gofmt' on the generated source code
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 {
// error creating file
ColorLog("[ERRO] Could not create model file: %s\n", err)
os.Exit(2)
}
}
func getStruct(structname, fields string) (string, error, bool) {
func getStruct(structname, fields string) (string, bool, error) {
if fields == "" {
return "", errors.New("fields can't empty"), false
return "", false, errors.New("fields cannot be empty")
}
hastime := false
structStr := "type " + structname + " struct{\n"
fds := strings.Split(fields, ",")
for i, v := range fds {
kv := strings.SplitN(v, ":", 2)
if len(kv) != 2 {
return "", errors.New("the filds format is wrong. should key:type,key:type " + v), false
return "", false, errors.New("the fields format is wrong. Should be key:type,key:type " + v)
}
typ, tag, hastimeinner := getType(kv[1])
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" {
structStr = structStr + "Id int64 `orm:\"auto\"`\n"
}
if hastimeinner {
hastime = true
}
structStr = structStr + camelString(kv[0]) + " " + typ + " " + tag + "\n"
}
structStr += "}\n"
return structStr, nil, hastime
return structStr, hastime, nil
}
// fields support type
@ -88,9 +112,8 @@ func getType(ktype string) (kt, tag string, hasTime bool) {
case "string":
if len(kv) == 2 {
return "string", "`orm:\"size(" + kv[1] + ")\"`", false
} else {
return "string", "`orm:\"size(128)\"`", false
}
return "string", "`orm:\"size(128)\"`", false
case "text":
return "string", "`orm:\"type(longtext)\"`", false
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
// 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()
v = &{{modelName}}{Id: id}
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}}
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 {
for _, v := range l {
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
// 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()
v := {{modelName}}{Id: id}
// ascertain id exists in the database

View File

@ -1,101 +1,48 @@
package main
import (
"fmt"
"strings"
)
import "strings"
func generateScaffold(sname, fields, crupath, driver, conn string) {
// generate model
ColorLog("[INFO] Do you want me to create a %v model? [yes|no]] ", sname)
func generateScaffold(sname, fields, currpath, driver, conn string) {
ColorLog("[INFO] Do you want to create a '%v' model? [Yes|No] ", sname)
// Generate the model
if askForConfirmation() {
generateModel(sname, fields, crupath)
generateModel(sname, fields, currpath)
}
// generate controller
ColorLog("[INFO] Do you want me to create a %v controller? [yes|no]] ", sname)
// Generate the controller
ColorLog("[INFO] Do you want to create a '%v' controller? [Yes|No] ", sname)
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() {
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() {
upsql := ""
downsql := ""
if fields != "" {
upsql = `m.Sql("CREATE TABLE ` + sname + "(" + generateSQLFromFields(fields) + `)");`
downsql = `m.Sql("DROP TABLE ` + "`" + sname + "`" + `")`
dbMigrator := newDBDriver()
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() {
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))
}
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
import (
"fmt"
"os"
"path"
)
// recipe
// admin/recipe
func generateView(vpath, crupath string) {
absvpath := path.Join(crupath, "views", vpath)
os.MkdirAll(absvpath, os.ModePerm)
cfile := path.Join(absvpath, "index.tpl")
func generateView(viewpath, currpath string) {
w := NewColorWriter(os.Stdout)
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 {
defer f.Close()
defer CloseFile(f)
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 {
ColorLog("[ERRO] Could not create view file: %s\n", err)
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 {
defer f.Close()
defer CloseFile(f)
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 {
ColorLog("[ERRO] Could not create view file: %s\n", err)
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 {
defer f.Close()
defer CloseFile(f)
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 {
ColorLog("[ERRO] Could not create view file: %s\n", err)
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 {
defer f.Close()
defer CloseFile(f)
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 {
ColorLog("[ERRO] Could not create view file: %s\n", err)
os.Exit(2)

View File

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

View File

@ -23,6 +23,7 @@ import (
"strconv"
"strings"
"time"
"runtime"
)
var cmdMigrate = &Command{
@ -31,22 +32,22 @@ var cmdMigrate = &Command{
Long: `
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
-driver: [mysql | postgres | sqlite] (default: mysql)
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test
bee migrate rollback [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
rollback the last migration operation
-driver: [mysql | postgresql | sqlite], the default is mysql
-driver: [mysql | postgres | sqlite] (default: mysql)
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test
bee migrate reset [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
rollback all migrations
-driver: [mysql | postgresql | sqlite], the default is mysql
-driver: [mysql | postgres | sqlite] (default: 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
-driver: [mysql | postgres | sqlite] (default: mysql)
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test
`,
}
@ -56,21 +57,24 @@ var mConn docValue
func init() {
cmdMigrate.Run = runMigration
cmdMigrate.Flag.Var(&mDriver, "driver", "database driver: mysql, postgresql, etc.")
cmdMigrate.Flag.Var(&mDriver, "driver", "database driver: mysql, postgres, sqlite, etc.")
cmdMigrate.Flag.Var(&mConn, "conn", "connection string used by the driver to connect to a database instance")
}
// runMigration is the entry point for starting a migration
func runMigration(cmd *Command, args []string) int {
crupath, _ := os.Getwd()
ShowShortVersionBanner()
gopath := os.Getenv("GOPATH")
Debugf("gopath:%s", gopath)
if gopath == "" {
ColorLog("[ERRO] $GOPATH not found\n")
ColorLog("[HINT] Set $GOPATH in your environment vairables\n")
currpath, _ := os.Getwd()
gps := GetGOPATHs()
if len(gps) == 0 {
ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty")
os.Exit(2)
}
gopath := gps[0]
Debugf("GOPATH: %s", gopath)
// load config
err := loadConfig()
if err != nil {
@ -98,19 +102,19 @@ func runMigration(cmd *Command, args []string) int {
if len(args) == 0 {
// run all outstanding migrations
ColorLog("[INFO] Running all outstanding migrations\n")
migrateUpdate(crupath, driverStr, connStr)
migrateUpdate(currpath, driverStr, connStr)
} else {
mcmd := args[0]
switch mcmd {
case "rollback":
ColorLog("[INFO] Rolling back the last migration operation\n")
migrateRollback(crupath, driverStr, connStr)
migrateRollback(currpath, driverStr, connStr)
case "reset":
ColorLog("[INFO] Reseting all migrations\n")
migrateReset(crupath, driverStr, connStr)
migrateReset(currpath, driverStr, connStr)
case "refresh":
ColorLog("[INFO] Refreshing all migrations\n")
migrateRefresh(crupath, driverStr, connStr)
migrateRefresh(currpath, driverStr, connStr)
default:
ColorLog("[ERRO] Command is missing\n")
os.Exit(2)
@ -121,38 +125,43 @@ func runMigration(cmd *Command, args []string) int {
}
// migrateUpdate does the schema update
func migrateUpdate(crupath, driver, connStr string) {
migrate("upgrade", crupath, driver, connStr)
func migrateUpdate(currpath, driver, connStr string) {
migrate("upgrade", currpath, driver, connStr)
}
// migrateRollback rolls back the latest migration
func migrateRollback(crupath, driver, connStr string) {
migrate("rollback", crupath, driver, connStr)
func migrateRollback(currpath, driver, connStr string) {
migrate("rollback", currpath, driver, connStr)
}
// migrateReset rolls back all migrations
func migrateReset(crupath, driver, connStr string) {
migrate("reset", crupath, driver, connStr)
func migrateReset(currpath, driver, connStr string) {
migrate("reset", currpath, driver, connStr)
}
// migrationRefresh rolls back all migrations and start over again
func migrateRefresh(crupath, driver, connStr string) {
migrate("refresh", crupath, driver, connStr)
func migrateRefresh(currpath, driver, connStr string) {
migrate("refresh", currpath, driver, connStr)
}
// migrate generates source code, build it, and invoke the binary who does the actual migration
func migrate(goal, crupath, driver, connStr string) {
dir := path.Join(crupath, "database", "migrations")
binary := "m"
func migrate(goal, currpath, driver, connStr string) {
dir := path.Join(currpath, "database", "migrations")
postfix := ""
if runtime.GOOS == "windows" {
postfix = ".exe"
}
binary := "m" + postfix
source := binary + ".go"
// connect to database
db, err := sql.Open(driver, connStr)
if err != nil {
ColorLog("[ERRO] Could not connect to %s: %s\n", driver, connStr)
ColorLog("[ERRO] Error: %v", err.Error())
os.Exit(2)
}
defer db.Close()
checkForSchemaUpdateTable(db)
checkForSchemaUpdateTable(db, driver)
latestName, latestTime := getLatestMigration(db, goal)
writeMigrationSourceFile(dir, source, driver, connStr, latestTime, latestName, goal)
buildMigrationBinary(dir, binary)
@ -163,20 +172,24 @@ func migrate(goal, crupath, driver, connStr string) {
// checkForSchemaUpdateTable checks the existence of migrations table.
// It checks for the proper table structures and creates the table using MYSQL_MIGRATION_DDL if it does not exist.
func checkForSchemaUpdateTable(db *sql.DB) {
if rows, err := db.Query("SHOW TABLES LIKE 'migrations'"); err != nil {
func checkForSchemaUpdateTable(db *sql.DB, driver string) {
showTableSQL := showMigrationsTableSQL(driver)
if rows, err := db.Query(showTableSQL); err != nil {
ColorLog("[ERRO] Could not show migrations table: %s\n", err)
os.Exit(2)
} else if !rows.Next() {
// no migrations table, create anew
createTableSQL := createMigrationsTableSQL(driver)
ColorLog("[INFO] Creating 'migrations' table...\n")
if _, err := db.Query(MYSQL_MIGRATION_DDL); err != nil {
if _, err := db.Query(createTableSQL); err != nil {
ColorLog("[ERRO] Could not create migrations table: %s\n", err)
os.Exit(2)
}
}
// checking that migrations table schema are expected
if rows, err := db.Query("DESC migrations"); err != nil {
selectTableSQL := selectMigrationsTableSQL(driver)
if rows, err := db.Query(selectTableSQL); err != nil {
ColorLog("[ERRO] Could not show columns of migrations table: %s\n", err)
os.Exit(2)
} else {
@ -212,6 +225,39 @@ func checkForSchemaUpdateTable(db *sql.DB) {
}
}
func showMigrationsTableSQL(driver string) string {
switch driver {
case "mysql":
return "SHOW TABLES LIKE 'migrations'"
case "postgres":
return "SELECT * FROM pg_catalog.pg_tables WHERE tablename = 'migrations';"
default:
return "SHOW TABLES LIKE 'migrations'"
}
}
func createMigrationsTableSQL(driver string) string {
switch driver {
case "mysql":
return MYSQLMigrationDDL
case "postgres":
return POSTGRESMigrationDDL
default:
return MYSQLMigrationDDL
}
}
func selectMigrationsTableSQL(driver string) string {
switch driver {
case "mysql":
return "DESC migrations"
case "postgres":
return "SELECT * FROM migrations WHERE false ORDER BY id_migration;"
default:
return "DESC migrations"
}
}
// getLatestMigration retrives latest migration with status 'update'
func getLatestMigration(db *sql.DB, goal string) (file string, createdAt int64) {
sql := "SELECT name FROM migrations where status = 'update' ORDER BY id_migration DESC LIMIT 1"
@ -250,7 +296,7 @@ func writeMigrationSourceFile(dir, source, driver, connStr string, latestTime in
ColorLog("[ERRO] Could not create file: %s\n", err)
os.Exit(2)
} 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, "{{LatestTime}}", strconv.FormatInt(latestTime, 10), -1)
content = strings.Replace(content, "{{LatestName}}", latestName, -1)
@ -259,7 +305,7 @@ func writeMigrationSourceFile(dir, source, driver, connStr string, latestTime in
ColorLog("[ERRO] Could not write to file: %s\n", err)
os.Exit(2)
}
f.Close()
CloseFile(f)
}
}
@ -329,7 +375,7 @@ func formatShellOutput(o string) {
}
const (
MIGRATION_MAIN_TPL = `package main
MigrationMainTPL = `package main
import(
"os"
@ -338,6 +384,7 @@ import(
"github.com/astaxie/beego/migration"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
)
func init(){
@ -367,7 +414,7 @@ func main(){
}
`
MYSQL_MIGRATION_DDL = `
MYSQLMigrationDDL = `
CREATE TABLE migrations (
id_migration int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key',
name varchar(255) DEFAULT NULL COMMENT 'migration name, unique',
@ -378,4 +425,16 @@ CREATE TABLE migrations (
PRIMARY KEY (id_migration)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
`
POSTGRESMigrationDDL = `
CREATE TYPE migrations_status AS ENUM('update', 'rollback');
CREATE TABLE migrations (
id_migration SERIAL PRIMARY KEY,
name varchar(255) DEFAULT NULL,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
statements text,
rollback_statements text,
status migrations_status
)`
)

211
new.go

File diff suppressed because one or more lines are too long

65
pack.go
View File

@ -37,22 +37,23 @@ import (
var cmdPack = &Command{
CustomFlags: true,
UsageLine: "pack",
Short: "compress an beego project",
Short: "Compress a beego project into a single file",
Long: `
compress an beego project
Pack is used to compress a beego project into a single file.
This eases the deployment by extracting the zip file to a server.
-p app path. default is current path
-b build specify platform app. default true
-p app path (default is the current path).
-b build specify platform app (default: true).
-ba additional args of go build
-be=[] additional ENV Variables of go build. eg: GOARCH=arm
-o compressed file output dir. default use current path
-f="" format. [ tar.gz / zip ]. default tar.gz
-exp="" relpath exclude prefix. default: .
-exs="" relpath exclude suffix. default: .go:.DS_Store:.tmp
-f="" format: tar.gz, zip (default: tar.gz)
-exp="" relpath exclude prefix (default: .). use : as separator
-exs="" relpath exclude suffix (default: .go:.DS_Store:.tmp). use : as separator
all path use : as separator
-exr=[] file/directory name exclude by Regexp. default: ^.
-fs=false follow symlink. default false
-ss=false skip symlink. default false
-exr=[] file/directory name exclude by Regexp (default: ^).
-fs=false follow symlink (default: false).
-ss=false skip symlink (default: false)
default embed symlink into compressed file
-v=false verbose
`,
@ -71,6 +72,7 @@ var (
buildEnvs ListOpts
verbose bool
format string
w io.Writer
)
type ListOpts []string
@ -100,6 +102,7 @@ func init() {
fs.BoolVar(&verbose, "v", false, "verbose")
cmdPack.Flag = *fs
cmdPack.Run = packApp
w = NewColorWriter(os.Stdout)
}
func exitPrint(con string) {
@ -198,6 +201,7 @@ func (wft *walkFileTree) virPath(fpath string) string {
return ""
}
name = name[1:]
name = path.ToSlash(name)
return name
}
@ -240,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 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
return err
} else {
return err
}
return err
}
func (wft *walkFileTree) iterDirectory(fpath string, fi os.FileInfo) error {
@ -337,7 +340,7 @@ func (wft *tarWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
if err != nil {
return false, err
}
defer fr.Close()
defer CloseFile(fr)
_, err = io.Copy(tw, fr)
if err != nil {
return false, err
@ -373,7 +376,7 @@ func (wft *zipWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
if err != nil {
return false, err
}
defer fr.Close()
defer CloseFile(fr)
_, err = io.Copy(w, fr)
if err != nil {
return false, err
@ -395,10 +398,10 @@ func (wft *zipWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
func packDirectory(excludePrefix []string, excludeSuffix []string,
excludeRegexp []*regexp.Regexp, includePath ...string) (err error) {
fmt.Printf("exclude relpath prefix: %s\n", strings.Join(excludePrefix, ":"))
fmt.Printf("exclude relpath suffix: %s\n", strings.Join(excludeSuffix, ":"))
ColorLog("Excluding relpath prefix: %s\n", strings.Join(excludePrefix, ":"))
ColorLog("Excluding relpath suffix: %s\n", strings.Join(excludeSuffix, ":"))
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)
@ -454,7 +457,7 @@ func packDirectory(excludePrefix []string, excludeSuffix []string,
func isBeegoProject(thePath string) bool {
fh, _ := os.Open(thePath)
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 {
if fi.IsDir() == false && strings.HasSuffix(fi.Name(), ".go") {
data, err := ioutil.ReadFile(path.Join(thePath, fi.Name()))
@ -470,6 +473,8 @@ func isBeegoProject(thePath string) bool {
}
func packApp(cmd *Command, args []string) int {
ShowShortVersionBanner()
curPath, _ := os.Getwd()
thePath := ""
@ -491,17 +496,17 @@ func packApp(cmd *Command, args []string) int {
thePath, err := path.Abs(appPath)
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 {
exitPrint(fmt.Sprintf("not exist app path: %s", thePath))
exitPrint(fmt.Sprintf("App path does not exist: %s", thePath))
}
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)
@ -516,14 +521,12 @@ func packApp(cmd *Command, args []string) int {
str := strconv.FormatInt(time.Now().UnixNano(), 10)[9:]
gobin := path.Join(runtime.GOROOT(), "bin", "go")
tmpdir := path.Join(os.TempDir(), "beePack-"+str)
os.Mkdir(tmpdir, 0700)
if build {
fmt.Println("build", appName)
ColorLog("Building application...\n")
var envs []string
for _, env := range buildEnvs {
parts := strings.SplitN(env, "=", 2)
@ -545,7 +548,7 @@ func packApp(cmd *Command, args []string) int {
os.Setenv("GOOS", goos)
os.Setenv("GOARCH", goarch)
fmt.Println("GOOS", goos, "GOARCH", goarch)
ColorLog("Env: GOOS=%s GOARCH=%s\n", goos, goarch)
binPath := path.Join(tmpdir, appName)
if goos == "windows" {
@ -558,10 +561,10 @@ func packApp(cmd *Command, args []string) int {
}
if verbose {
fmt.Println(gobin, " ", 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(gobin, args...)
execmd := exec.Command("go", args...)
execmd.Env = append(os.Environ(), envs...)
execmd.Stdout = os.Stdout
execmd.Stderr = os.Stderr
@ -571,7 +574,7 @@ func packApp(cmd *Command, args []string) int {
exitPrint(err.Error())
}
fmt.Println("build success")
ColorLog("Build successful\n")
}
switch format {
@ -623,6 +626,6 @@ func packApp(cmd *Command, args []string) int {
exitPrint(err.Error())
}
fmt.Printf("file write to `%s`\n", outputP)
ColorLog("Writing to output: `%s`\n", outputP)
return 0
}

169
run.go
View File

@ -15,6 +15,7 @@
package main
import (
"fmt"
"io/ioutil"
"os"
path "path/filepath"
@ -23,52 +24,94 @@ import (
)
var cmdRun = &Command{
UsageLine: "run [appname] [watchall] [-main=*.go] [-downdoc=true] [-gendoc=true]",
Short: "run the app which can hot compile",
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",
Long: `
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
when the file has changed bee will auto go build and restart the app
file changed
|
check if it's go file
|
yes no
| |
go build do nothing
|
restart app
`,
}
var mainFiles ListOpts
var downdoc docValue
var gendoc docValue
var (
mainFiles ListOpts
downdoc 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() {
cmdRun.Run = runApp
cmdRun.Flag.Var(&mainFiles, "main", "specify main go files")
cmdRun.Flag.Var(&gendoc, "gendoc", "auto generate the docs")
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 {
exit := make(chan bool)
crupath, _ := os.Getwd()
ShowShortVersionBanner()
if len(args) == 0 || args[0] == "watchall" {
appname = path.Base(crupath)
ColorLog("[INFO] Uses '%s' as 'appname'\n", appname)
currpath, _ = os.Getwd()
if found, _gopath, _ := SearchGOPATHs(currpath); found {
appname = path.Base(currpath)
currentGoPath = _gopath
} else {
appname = args[0]
exitPrint(fmt.Sprintf("Bee does not support non Beego project: %s", currpath))
}
ColorLog("[INFO] Using '%s' as 'appname'\n", appname)
} else {
// Check if passed Bee application path/name exists in the GOPATH(s)
if found, _gopath, _path := SearchGOPATHs(args[0]); found {
currpath = _path
currentGoPath = _gopath
appname = path.Base(currpath)
} else {
panic(fmt.Sprintf("No Beego application '%s' found in your GOPATH", args[0]))
}
ColorLog("[INFO] Using '%s' as 'appname'\n", appname)
if strings.HasSuffix(appname, ".go") && isExist(currpath) {
ColorLog("[WARN] The appname is in conflict with currpath's file, do you want to build appname as %s\n", appname)
ColorLog("[INFO] Do you want to overwrite it? [yes|no]] ")
if !askForConfirmation() {
return 0
}
}
}
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"))
}
Debugf("current path:%s\n", crupath)
err := loadConfig()
if err != nil {
@ -76,19 +119,12 @@ func runApp(cmd *Command, args []string) int {
}
var paths []string
readAppDirectories(crupath, &paths)
readAppDirectories(currpath, &paths)
// Because monitor files has some issues, we watch current directory
// 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 {
paths = append(paths, strings.Replace(p, "$GOPATH", gopath, -1))
paths = append(paths, strings.Replace(p, "$GOPATH", currentGoPath, -1))
}
files := []string{}
@ -97,7 +133,14 @@ func runApp(cmd *Command, args []string) int {
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" {
NewWatcher(paths, files, true)
Autobuild(files, true)
@ -105,21 +148,13 @@ func runApp(cmd *Command, args []string) int {
NewWatcher(paths, 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 {
select {
case <-exit:
runtime.Goexit()
}
}
return 0
}
func readAppDirectories(directory string, paths *[]string) {
@ -128,25 +163,57 @@ func readAppDirectories(directory string, paths *[]string) {
return
}
useDiectory := false
useDirectory := false
for _, fileInfo := range fileInfos {
if strings.HasSuffix(fileInfo.Name(), "docs") {
continue
}
if strings.HasSuffix(fileInfo.Name(), "swagger") {
continue
}
if !vendorWatch && strings.HasSuffix(fileInfo.Name(), "vendor") {
continue
}
if isExcluded(path.Join(directory, fileInfo.Name())) {
continue
}
if fileInfo.IsDir() == true && fileInfo.Name()[0] != '.' {
readAppDirectories(directory+"/"+fileInfo.Name(), paths)
continue
}
if useDiectory == true {
if useDirectory == true {
continue
}
if path.Ext(fileInfo.Name()) == ".go" {
*paths = append(*paths, directory)
useDiectory = true
useDirectory = true
}
}
return
}
// If a file is excluded
func isExcluded(filePath string) bool {
for _, p := range excludedPaths {
absP, err := path.Abs(p)
if err != nil {
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"
"net/http"
"os"
"path/filepath"
"strings"
)
var cmdRundocs = &Command{
@ -33,8 +33,9 @@ var cmdRundocs = &Command{
`,
}
const (
swaggerlink = "https://github.com/beego/swagger/archive/v1.zip"
var (
swaggerVersion = "2"
swaggerlink = "https://github.com/beego/swagger/archive/v" + swaggerVersion + ".zip"
)
type docValue string
@ -59,8 +60,8 @@ func init() {
func runDocs(cmd *Command, args []string) int {
if isDownload == "true" {
downloadFromUrl(swaggerlink, "swagger.zip")
err := unzipAndDelete("swagger.zip", "swagger")
downloadFromURL(swaggerlink, "swagger.zip")
err := unzipAndDelete("swagger.zip")
if err != nil {
fmt.Println("has err exet unzipAndDelete", err)
}
@ -77,40 +78,50 @@ func runDocs(cmd *Command, args []string) int {
return 0
}
func downloadFromUrl(url, fileName string) {
fmt.Println("Downloading", url, "to", fileName)
func downloadFromURL(url, fileName string) {
var down bool
if fd, err := os.Stat(fileName); err != nil && os.IsNotExist(err) {
down = true
} else if fd.Size() == int64(0) {
down = true
} else {
ColorLog("[%s] Filename %s already exist\n", INFO, fileName)
return
}
if down {
ColorLog("[%s]Downloading %s to %s\n", SUCC, url, fileName)
output, err := os.Create(fileName)
if err != nil {
fmt.Println("Error while creating", fileName, "-", err)
ColorLog("[%s]Error while creating %s: %s\n", ERRO, fileName, err)
return
}
defer output.Close()
response, err := http.Get(url)
if err != nil {
fmt.Println("Error while downloading", url, "-", err)
ColorLog("[%s]Error while downloading %s:%s\n", ERRO, url, err)
return
}
defer response.Body.Close()
n, err := io.Copy(output, response.Body)
if err != nil {
fmt.Println("Error while downloading", url, "-", err)
ColorLog("[%s]Error while downloading %s:%s\n", ERRO, url, err)
return
}
fmt.Println(n, "bytes downloaded.")
ColorLog("[%s] %d bytes downloaded.\n", SUCC, n)
}
}
func unzipAndDelete(src, dest string) error {
fmt.Println("start to unzip file from " + src + " to " + dest)
func unzipAndDelete(src string) error {
ColorLog("[%s]start to unzip file from %s\n", INFO, src)
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
rp := strings.NewReplacer("swagger-"+swaggerVersion, "swagger")
for _, f := range r.File {
rc, err := f.Open()
if err != nil {
@ -118,12 +129,12 @@ func unzipAndDelete(src, dest string) error {
}
defer rc.Close()
path := filepath.Join(dest, f.Name)
fname := rp.Replace(f.Name)
if f.FileInfo().IsDir() {
os.MkdirAll(path, f.Mode())
os.MkdirAll(fname, f.Mode())
} else {
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 {
return err
}
@ -135,11 +146,6 @@ func unzipAndDelete(src, dest string) error {
}
}
}
fmt.Println("Start delete src file " + src)
err = os.RemoveAll(src)
if err != nil {
return err
}
return nil
ColorLog("[%s]Start delete src file %s\n", INFO, src)
return os.RemoveAll(src)
}

View File

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

View File

@ -8,21 +8,21 @@ type Router struct {
beego.Controller
}
func (this *Router) Get() {
func (r *Router) Get() {
}
func (this *Router) Post() {
func (r *Router) Post() {
}
type Controller struct {
}
func (this *Controller) Put() {
func (c *Controller) Put() {
}
func (this *Controller) Delete() {
func (c *Controller) Delete() {
}

52
util.go
View File

@ -15,13 +15,14 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"path"
"fmt"
)
// Go is a basic promise implementation: it wraps calls a function in a goroutine
@ -152,7 +153,6 @@ func getColorLevel(level string) string {
default:
return level
}
return level
}
// IsExist returns whether a file or directory exists.
@ -174,6 +174,34 @@ func GetGOPATHs() []string {
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
// 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
@ -247,3 +275,23 @@ func camelString(s string) string {
}
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,51 +2,81 @@ package main
import (
"bufio"
"bytes"
"fmt"
"io"
"log"
"os"
"os/exec"
path "path/filepath"
"regexp"
)
var cmdVersion = &Command{
UsageLine: "version",
Short: "show the bee & beego version",
Short: "prints the current Bee version",
Long: `
show the bee & beego version
Prints the current Bee, Beego and Go version alongside the platform information
bee version
bee: 1.1.1
beego: 1.2
`,
}
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() {
cmdVersion.Run = versionCmd
}
func versionCmd(cmd *Command, args []string) int {
fmt.Println("bee :" + version)
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))
ShowVerboseVersionBanner()
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")
re, err := regexp.Compile(`const VERSION = "([0-9.]+)"`)
re, err := regexp.Compile(`VERSION = "([0-9.]+)"`)
if err != nil {
return ""
}
if gopath == "" {
err = fmt.Errorf("you should set GOPATH in the env")
err = fmt.Errorf("You should set GOPATH env variable")
return ""
}
wgopath := path.SplitList(gopath)
@ -58,11 +88,11 @@ func getbeegoVersion() string {
if os.IsNotExist(err) {
continue
}
ColorLog("[ERRO] get beego.go has error\n")
ColorLog("[ERRO] Get `beego.go` has error\n")
}
fd, err := os.Open(filename)
if err != nil {
ColorLog("[ERRO] open beego.go has error\n")
ColorLog("[ERRO] Open `beego.go` has error\n")
continue
}
reader := bufio.NewReader(fd)
@ -82,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,21 +17,21 @@ package main
import (
"bytes"
"fmt"
"github.com/howeyc/fsnotify"
"os"
"os/exec"
"regexp"
"runtime"
"strings"
"sync"
"time"
"github.com/howeyc/fsnotify"
)
var (
cmd *exec.Cmd
state sync.Mutex
eventTime = make(map[string]int64)
buildPeriod time.Time
scheduleTime time.Time
)
func NewWatcher(paths []string, files []string, isgenerate bool) {
@ -47,20 +47,14 @@ func NewWatcher(paths []string, files []string, isgenerate bool) {
case e := <-watcher.Event:
isbuild := true
// Skip TMP files for Sublime Text.
if checkTMPFile(e.Name) {
// Skip ignored files
if shouldIgnoreFile(e.Name) {
continue
}
if !chekcIfWatchExt(e.Name) {
if !checkIfWatchExt(e.Name) {
continue
}
// Prevent duplicated builds.
if buildPeriod.Add(1 * time.Second).After(time.Now()) {
continue
}
buildPeriod = time.Now()
mt := getFileModTime(e.Name)
if t := eventTime[e.Name]; mt == t {
ColorLog("[SKIP] # %s #\n", e.String())
@ -71,7 +65,19 @@ func NewWatcher(paths []string, files []string, isgenerate bool) {
if isbuild {
ColorLog("[EVEN] %s\n", e)
go Autobuild(files, isgenerate)
go func() {
// Wait 1s before autobuild util there is no file change.
scheduleTime = time.Now().Add(1 * time.Second)
for {
time.Sleep(scheduleTime.Sub(time.Now()))
if time.Now().After(scheduleTime) {
break
}
return
}
Autobuild(files, isgenerate)
}()
}
case err := <-watcher.Error:
ColorLog("[WARN] %s\n", err.Error()) // No need to exit here
@ -115,8 +121,8 @@ func Autobuild(files []string, isgenerate bool) {
defer state.Unlock()
ColorLog("[INFO] Start building...\n")
path, _ := os.Getwd()
os.Chdir(path)
os.Chdir(currpath)
cmdName := "go"
if conf.Gopm.Enable {
@ -130,6 +136,7 @@ func Autobuild(files []string, isgenerate bool) {
icmd := exec.Command("go", "list", "./...")
buf := bytes.NewBuffer([]byte(""))
icmd.Stdout = buf
icmd.Env = append(os.Environ(), "GOGC=off")
err = icmd.Run()
if err == nil {
list := strings.Split(buf.String(), "\n")[1:]
@ -140,6 +147,7 @@ func Autobuild(files []string, isgenerate bool) {
icmd = exec.Command(cmdName, "install", pkg)
icmd.Stdout = os.Stdout
icmd.Stderr = os.Stderr
icmd.Env = append(os.Environ(), "GOGC=off")
err = icmd.Run()
if err != nil {
break
@ -150,6 +158,7 @@ func Autobuild(files []string, isgenerate bool) {
if isgenerate {
icmd := exec.Command("bee", "generate", "docs")
icmd.Env = append(os.Environ(), "GOGC=off")
icmd.Stdout = os.Stdout
icmd.Stderr = os.Stderr
icmd.Run()
@ -164,9 +173,13 @@ func Autobuild(files []string, isgenerate bool) {
args := []string{"build"}
args = append(args, "-o", appName)
if buildTags != "" {
args = append(args, "-tags", buildTags)
}
args = append(args, files...)
bcmd := exec.Command(cmdName, args...)
bcmd.Env = append(os.Environ(), "GOGC=off")
bcmd.Stdout = os.Stdout
bcmd.Stderr = os.Stderr
err = bcmd.Run()
@ -217,18 +230,33 @@ func Start(appname string) {
started <- true
}
// checkTMPFile returns true if the event was for TMP files.
func checkTMPFile(name string) bool {
if strings.HasSuffix(strings.ToLower(name), ".tmp") {
// Should ignore filenames generated by
// Emacs, Vim or SublimeText
func shouldIgnoreFile(filename string) bool {
for _, regex := range ignoredFilesRegExps {
r, err := regexp.Compile(regex)
if err != nil {
panic("Could not compile the regex: " + regex)
}
if r.MatchString(filename) {
return true
} else {
continue
}
}
return false
}
var watchExts = []string{".go"}
var ignoredFilesRegExps = []string{
`.#(\w+).go`,
`.(\w+).go.swp`,
`(\w+).go~`,
`(\w+).tmp`,
}
// chekcIfWatchExt returns true if the name HasSuffix <watch_ext>.
func chekcIfWatchExt(name string) bool {
// checkIfWatchExt returns true if the name HasSuffix <watch_ext>.
func checkIfWatchExt(name string) bool {
for _, s := range watchExts {
if strings.HasSuffix(name, s) {
return true