1
0
mirror of https://github.com/beego/bee.git synced 2024-06-16 03:33:34 +00:00

Merge remote-tracking branch 'org-bee/develop' into develop

This commit is contained in:
sunqida 2016-11-23 21:57:28 +08:00
commit cda38e63f6
30 changed files with 1292 additions and 801 deletions

View File

@ -538,19 +538,17 @@ func TestGet(t *testing.T) {
func init() { func init() {
cmdApiapp.Run = createapi cmdApiapp.Run = createapi
cmdApiapp.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
cmdApiapp.Flag.Var(&tables, "tables", "specify tables to generate model") cmdApiapp.Flag.Var(&tables, "tables", "specify tables to generate model")
cmdApiapp.Flag.Var(&driver, "driver", "database driver: mysql, postgresql, etc.") cmdApiapp.Flag.Var(&driver, "driver", "database driver: mysql, postgresql, etc.")
cmdApiapp.Flag.Var(&conn, "conn", "connection string used by the driver to connect to a database instance") cmdApiapp.Flag.Var(&conn, "conn", "connection string used by the driver to connect to a database instance")
} }
func createapi(cmd *Command, args []string) int { func createapi(cmd *Command, args []string) int {
ShowShortVersionBanner()
w := NewColorWriter(os.Stdout) w := NewColorWriter(os.Stdout)
if len(args) < 1 { if len(args) < 1 {
ColorLog("[ERRO] Argument [appname] is missing\n") logger.Fatal("Argument [appname] is missing")
os.Exit(2)
} }
if len(args) > 1 { if len(args) > 1 {
@ -559,8 +557,7 @@ func createapi(cmd *Command, args []string) int {
apppath, packpath, err := checkEnv(args[0]) apppath, packpath, err := checkEnv(args[0])
if err != nil { if err != nil {
fmt.Println(err) logger.Fatalf("%s", err)
os.Exit(2)
} }
if driver == "" { if driver == "" {
driver = "mysql" driver = "mysql"
@ -568,7 +565,7 @@ func createapi(cmd *Command, args []string) int {
if conn == "" { if conn == "" {
} }
ColorLog("[INFO] Creating API...\n") logger.Info("Creating API...")
os.MkdirAll(apppath, 0755) os.MkdirAll(apppath, 0755)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath, "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath, "\x1b[0m")
@ -599,9 +596,9 @@ func createapi(cmd *Command, args []string) int {
-1, -1,
), ),
) )
ColorLog("[INFO] Using '%s' as 'driver'\n", driver) logger.Infof("Using '%s' as 'driver'", driver)
ColorLog("[INFO] Using '%s' as 'conn'\n", conn) logger.Infof("Using '%s' as 'conn'", conn)
ColorLog("[INFO] Using '%s' as 'tables'\n", tables) logger.Infof("Using '%s' as 'tables'", tables)
generateAppcode(string(driver), string(conn), "3", string(tables), apppath) generateAppcode(string(driver), string(conn), "3", string(tables), apppath)
} else { } else {
os.Mkdir(path.Join(apppath, "models"), 0755) os.Mkdir(path.Join(apppath, "models"), 0755)
@ -635,15 +632,14 @@ func createapi(cmd *Command, args []string) int {
WriteToFile(path.Join(apppath, "main.go"), WriteToFile(path.Join(apppath, "main.go"),
strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1)) strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1))
} }
ColorLog("[SUCC] New API successfully created!\n") logger.Success("New API successfully created!")
return 0 return 0
} }
func checkEnv(appname string) (apppath, packpath string, err error) { func checkEnv(appname string) (apppath, packpath string, err error) {
gps := GetGOPATHs() gps := GetGOPATHs()
if len(gps) == 0 { if len(gps) == 0 {
ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty") logger.Fatal("GOPATH environment variable is not set or empty")
os.Exit(2)
} }
currpath, _ := os.Getwd() currpath, _ := os.Getwd()
currpath = path.Join(currpath, appname) currpath = path.Join(currpath, appname)
@ -658,15 +654,16 @@ func checkEnv(appname string) (apppath, packpath string, err error) {
// In case of multiple paths in the GOPATH, by default // In case of multiple paths in the GOPATH, by default
// we use the first path // we use the first path
gopath := gps[0] 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) logger.Warn("You current workdir is not inside $GOPATH/src.")
logger.Debugf("GOPATH: %s", __FILE__(), __LINE__(), gopath)
gosrcpath := path.Join(gopath, "src") gosrcpath := path.Join(gopath, "src")
apppath = path.Join(gosrcpath, appname) apppath = path.Join(gosrcpath, appname)
if _, e := os.Stat(apppath); os.IsNotExist(e) == false { if _, e := os.Stat(apppath); os.IsNotExist(e) == false {
err = fmt.Errorf("Cannot create application without removing '%s' first.", apppath) err = fmt.Errorf("Cannot create application without removing '%s' first.", apppath)
ColorLog("[ERRO] Path '%s' already exists\n", apppath) logger.Errorf("Path '%s' already exists", apppath)
return return
} }
packpath = strings.Join(strings.Split(apppath[len(gosrcpath)+1:], string(path.Separator)), "/") packpath = strings.Join(strings.Split(apppath[len(gosrcpath)+1:], string(path.Separator)), "/")

View File

@ -66,7 +66,7 @@ func getControllerInfo(path string) (map[string][]string, error) {
files := make([]*source, 0, len(fis)) files := make([]*source, 0, len(fis))
for _, fi := range fis { for _, fi := range fis {
// Only load go files. // Only load Go files
if strings.HasSuffix(fi.Name(), ".go") { if strings.HasSuffix(fi.Name(), ".go") {
f, err := os.Open(path + "/" + fi.Name()) f, err := os.Open(path + "/" + fi.Name())
if err != nil { if err != nil {
@ -107,7 +107,7 @@ func getControllerInfo(path string) (map[string][]string, error) {
return cm, nil return cm, nil
} }
// A source describles a source code file. // source represents a source code file.
type source struct { type source struct {
name string name string
data []byte data []byte
@ -120,27 +120,25 @@ func (s *source) ModTime() time.Time { return time.Time{} }
func (s *source) IsDir() bool { return false } func (s *source) IsDir() bool { return false }
func (s *source) Sys() interface{} { return nil } func (s *source) Sys() interface{} { return nil }
// A routerWalker holds the state used when building the documentation. // routerWalker holds the state used when building the documentation.
type routerWalker struct { type routerWalker struct {
pdoc *Package pdoc *Package
srcs map[string]*source // Source files. srcs map[string]*source // Source files
fset *token.FileSet fset *token.FileSet
buf []byte // scratch space for printNode method. buf []byte // scratch space for printNode method
} }
// Package represents full information and documentation for a package. // Package represents full information and documentation for a package.
type Package struct { type Package struct {
ImportPath string ImportPath string
Types []*Type // Top-level declarations
// Top-level declarations.
Types []*Type
} }
// Type represents structs and interfaces. // Type represents structs and interfaces.
type Type struct { type Type struct {
Name string // Type name. Name string // Type name
Decl string Decl string
Methods []*Func // Exported methods. Methods []*Func // Exported methods
} }
// Func represents functions // Func represents functions
@ -150,7 +148,7 @@ type Func struct {
// build generates data from source files. // build generates data from source files.
func (w *routerWalker) build(srcs []*source) (*Package, error) { func (w *routerWalker) build(srcs []*source) (*Package, error) {
// Add source files to walker, I skipped references here. // Add source files to walker, I skipped references here
w.srcs = make(map[string]*source) w.srcs = make(map[string]*source)
for _, src := range srcs { for _, src := range srcs {
w.srcs[src.name] = src w.srcs[src.name] = src
@ -158,7 +156,7 @@ func (w *routerWalker) build(srcs []*source) (*Package, error) {
w.fset = token.NewFileSet() w.fset = token.NewFileSet()
// Find the package and associated files. // Find the package and associated files
ctxt := gobuild.Context{ ctxt := gobuild.Context{
GOOS: runtime.GOOS, GOOS: runtime.GOOS,
GOARCH: runtime.GOARCH, GOARCH: runtime.GOARCH,
@ -174,7 +172,7 @@ func (w *routerWalker) build(srcs []*source) (*Package, error) {
} }
bpkg, err := ctxt.ImportDir(w.pdoc.ImportPath, 0) bpkg, err := ctxt.ImportDir(w.pdoc.ImportPath, 0)
// Continue if there are no Go source files; we still want the directory info. // Continue if there are no Go source files; we still want the directory info
_, nogo := err.(*gobuild.NoGoError) _, nogo := err.(*gobuild.NoGoError)
if err != nil { if err != nil {
if nogo { if nogo {
@ -272,9 +270,8 @@ func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, e
name = strings.TrimPrefix(name, "biogo.") name = strings.TrimPrefix(name, "biogo.")
// It's also common for the last element of the path to contain an // It's also common for the last element of the path to contain an
// extra "go" prefix, but not always. TODO: examine unresolved ids to // extra "go" prefix, but not always.
// detect when trimming the "go" prefix is appropriate. // TODO: examine unresolved ids to detect when trimming the "go" prefix is appropriate.
pkg = ast.NewObj(ast.Pkg, name) pkg = ast.NewObj(ast.Pkg, name)
pkg.Data = ast.NewScope(nil) pkg.Data = ast.NewScope(nil)
imports[path] = pkg imports[path] = pkg

44
bale.go
View File

@ -32,7 +32,7 @@ var cmdBale = &Command{
Long: ` Long: `
Bale command compress all the static files in to a single binary file. Bale command compress all the static files in to a single binary file.
This is usefull to not have to carry static files including js, css, images This is useful to not have to carry static files including js, css, images
and views when publishing a project. and views when publishing a project.
auto-generate unpack function to main package then run it during the runtime. auto-generate unpack function to main package then run it during the runtime.
@ -43,26 +43,25 @@ This is mainly used for zealots who are requiring 100% Go code.
func init() { func init() {
cmdBale.Run = runBale cmdBale.Run = runBale
cmdBale.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
} }
func runBale(cmd *Command, args []string) int { func runBale(cmd *Command, args []string) int {
ShowShortVersionBanner()
err := loadConfig() err := loadConfig()
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err) logger.Fatalf("Failed to load configuration: %s", err)
} }
os.RemoveAll("bale") os.RemoveAll("bale")
os.Mkdir("bale", os.ModePerm) os.Mkdir("bale", os.ModePerm)
// Pack and compress data. // Pack and compress data
for _, p := range conf.Bale.Dirs { for _, p := range conf.Bale.Dirs {
if !isExist(p) { if !isExist(p) {
ColorLog("[WARN] Skipped directory( %s )\n", p) logger.Warnf("Skipped directory: %s", p)
continue continue
} }
ColorLog("[INFO] Packaging directory( %s )\n", p) logger.Infof("Packaging directory: %s", p)
filepath.Walk(p, walkFn) filepath.Walk(p, walkFn)
} }
@ -74,22 +73,21 @@ func runBale(cmd *Command, args []string) int {
fw, err := os.Create("bale.go") fw, err := os.Create("bale.go")
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to create file[ %s ]\n", err) logger.Fatalf("Failed to create file: %s", err)
os.Exit(2)
} }
defer fw.Close() defer fw.Close()
_, err = fw.Write(buf.Bytes()) _, err = fw.Write(buf.Bytes())
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to write data[ %s ]\n", err) logger.Fatalf("Failed to write data: %s", err)
os.Exit(2)
} }
ColorLog("[SUCC] Baled resources successfully!\n") logger.Success("Baled resources successfully!")
return 0 return 0
} }
const ( const (
// BaleHeader ...
BaleHeader = `package main BaleHeader = `package main
import( import(
@ -150,14 +148,13 @@ func walkFn(resPath string, info os.FileInfo, err error) error {
return nil return nil
} }
// Open resource files. // Open resource files
fr, err := os.Open(resPath) fr, err := os.Open(resPath)
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to read file[ %s ]\n", err) logger.Fatalf("Failed to read file: %s", err)
os.Exit(2)
} }
// Convert path. // Convert path
resPath = strings.Replace(resPath, "_", "_0_", -1) resPath = strings.Replace(resPath, "_", "_0_", -1)
resPath = strings.Replace(resPath, ".", "_1_", -1) resPath = strings.Replace(resPath, ".", "_1_", -1)
resPath = strings.Replace(resPath, "-", "_2_", -1) resPath = strings.Replace(resPath, "-", "_2_", -1)
@ -168,19 +165,18 @@ func walkFn(resPath string, info os.FileInfo, err error) error {
} }
resPath = strings.Replace(resPath, sep, "_4_", -1) resPath = strings.Replace(resPath, sep, "_4_", -1)
// Create corresponding Go source files. // Create corresponding Go source files
os.MkdirAll(path.Dir(resPath), os.ModePerm) os.MkdirAll(path.Dir(resPath), os.ModePerm)
fw, err := os.Create("bale/" + resPath + ".go") fw, err := os.Create("bale/" + resPath + ".go")
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to create file[ %s ]\n", err) logger.Fatalf("Failed to create file: %s", err)
os.Exit(2)
} }
defer fw.Close() defer fw.Close()
// Write header. // Write header
fmt.Fprintf(fw, Header, resPath) fmt.Fprintf(fw, Header, resPath)
// Copy and compress data. // Copy and compress data
gz := gzip.NewWriter(&ByteWriter{Writer: fw}) gz := gzip.NewWriter(&ByteWriter{Writer: fw})
io.Copy(gz, fr) io.Copy(gz, fr)
gz.Close() gz.Close()
@ -202,6 +198,7 @@ func filterSuffix(name string) bool {
} }
const ( const (
// Header ...
Header = `package bale Header = `package bale
import( import(
@ -212,6 +209,7 @@ import(
func R%s() []byte { func R%s() []byte {
gz, err := gzip.NewReader(bytes.NewBuffer([]byte{` gz, err := gzip.NewReader(bytes.NewBuffer([]byte{`
// Footer ...
Footer = ` Footer = `
})) }))
@ -229,6 +227,7 @@ func R%s() []byte {
var newline = []byte{'\n'} var newline = []byte{'\n'}
// ByteWriter ...
type ByteWriter struct { type ByteWriter struct {
io.Writer io.Writer
c int c int
@ -244,12 +243,9 @@ func (w *ByteWriter) Write(p []byte) (n int, err error) {
w.Writer.Write(newline) w.Writer.Write(newline)
w.c = 0 w.c = 0
} }
fmt.Fprintf(w.Writer, "0x%02x,", p[n]) fmt.Fprintf(w.Writer, "0x%02x,", p[n])
w.c++ w.c++
} }
n++ n++
return return
} }

View File

@ -6,7 +6,6 @@ import (
"os" "os"
"runtime" "runtime"
"text/template" "text/template"
"time"
) )
type vars struct { type vars struct {
@ -21,25 +20,17 @@ type vars struct {
BeegoVersion 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 // InitBanner loads the banner and prints it to output
// All errors are ignored, the application will not // All errors are ignored, the application will not
// print the banner in case of error. // print the banner in case of error.
func InitBanner(out io.Writer, in io.Reader) { func InitBanner(out io.Writer, in io.Reader) {
if in == nil { if in == nil {
ColorLog("[ERRO] The input is nil\n") logger.Fatal("The input is nil")
os.Exit(2)
} }
banner, err := ioutil.ReadAll(in) banner, err := ioutil.ReadAll(in)
if err != nil { if err != nil {
ColorLog("[ERRO] Error trying to read the banner\n") logger.Fatalf("Error while trying to read the banner: %s", err)
ColorLog("[HINT] %v\n", err)
os.Exit(2)
} }
show(out, string(banner)) show(out, string(banner))
@ -51,13 +42,11 @@ func show(out io.Writer, content string) {
Parse(content) Parse(content)
if err != nil { if err != nil {
ColorLog("[ERRO] Cannot parse the banner template\n") logger.Fatalf("Cannot parse the banner template: %s", err)
ColorLog("[HINT] %v\n", err)
os.Exit(2)
} }
err = t.Execute(out, vars{ err = t.Execute(out, vars{
runtime.Version(), getGoVersion(),
runtime.GOOS, runtime.GOOS,
runtime.GOARCH, runtime.GOARCH,
runtime.NumCPU(), runtime.NumCPU(),
@ -67,7 +56,5 @@ func show(out io.Writer, content string) {
version, version,
getBeegoVersion(), getBeegoVersion(),
}) })
if err != nil { MustCheck(err)
panic(err)
}
} }

23
bee.go
View File

@ -25,13 +25,17 @@ import (
"strings" "strings"
) )
const version = "1.5.1" const version = "1.5.2"
// Command is the unit of execution
type Command struct { type Command struct {
// Run runs the command. // Run runs the command.
// The args are the arguments after the command name. // The args are the arguments after the command name.
Run func(cmd *Command, args []string) int Run func(cmd *Command, args []string) int
// PreRun performs an operation before running the command
PreRun func(cmd *Command, args []string)
// UsageLine is the one-line usage message. // UsageLine is the one-line usage message.
// The first word in the line is taken to be the command name. // The first word in the line is taken to be the command name.
UsageLine string UsageLine string
@ -60,6 +64,7 @@ func (c *Command) Name() string {
return name return name
} }
// Usage puts out the usage for the command.
func (c *Command) Usage() { func (c *Command) Usage() {
fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine) fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine)
fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(string(c.Long))) fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(string(c.Long)))
@ -88,7 +93,11 @@ var commands = []*Command{
cmdFix, cmdFix,
} }
var logger = GetBeeLogger(os.Stdout)
func main() { func main() {
currentpath, _ := os.Getwd()
flag.Usage = usage flag.Usage = usage
flag.Parse() flag.Parse()
log.SetFlags(0) log.SetFlags(0)
@ -112,6 +121,17 @@ func main() {
cmd.Flag.Parse(args[1:]) cmd.Flag.Parse(args[1:])
args = cmd.Flag.Args() args = cmd.Flag.Args()
} }
if cmd.PreRun != nil {
cmd.PreRun(cmd, args)
}
// Check if current directory is inside the GOPATH,
// if so parse the packages inside it.
if strings.Contains(currentpath, GetGOPATHs()[0]+"/src") {
parsePackagesFromDir(currentpath)
}
os.Exit(cmd.Run(cmd, args)) os.Exit(cmd.Run(cmd, args))
return return
} }
@ -138,7 +158,6 @@ Additional help topics:
{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
Use "bee help [topic]" for more information about that topic. Use "bee help [topic]" for more information about that topic.
` `
var helpTemplate = `{{if .Runnable}}usage: bee {{.UsageLine}} var helpTemplate = `{{if .Runnable}}usage: bee {{.UsageLine}}

View File

@ -109,12 +109,12 @@ func (v *annotationVisitor) Visit(n ast.Node) ast.Visitor {
v.ignoreName() v.ignoreName()
ast.Walk(v, n.Type) ast.Walk(v, n.Type)
case *ast.Field: case *ast.Field:
for _ = range n.Names { for range n.Names {
v.ignoreName() v.ignoreName()
} }
ast.Walk(v, n.Type) ast.Walk(v, n.Type)
case *ast.ValueSpec: case *ast.ValueSpec:
for _ = range n.Names { for range n.Names {
v.add(AnchorAnnotation, "") v.add(AnchorAnnotation, "")
} }
if n.Type != nil { if n.Type != nil {

View File

@ -14,7 +14,10 @@
package main package main
import "io" import (
"fmt"
"io"
)
type outputMode int type outputMode int
@ -49,3 +52,77 @@ func NewModeColorWriter(w io.Writer, mode outputMode) io.Writer {
} }
return w return w
} }
func bold(message string) string {
return fmt.Sprintf("\x1b[1m%s\x1b[21m", message)
}
// Cyan returns a cyan string
func Cyan(message string) string {
return fmt.Sprintf("\x1b[36m%s\x1b[0m", message)
}
// Blue returns a blue string
func Blue(message string) string {
return fmt.Sprintf("\x1b[34m%s\x1b[0m", message)
}
// Red returns a red string
func Red(message string) string {
return fmt.Sprintf("\x1b[31m%s\x1b[0m", message)
}
// Green returns a green string
func Green(message string) string {
return fmt.Sprintf("\x1b[32m%s\x1b[0m", message)
}
// Yellow returns a yellow string
func Yellow(message string) string {
return fmt.Sprintf("\x1b[33m%s\x1b[0m", message)
}
// Gray returns a gray string
func Gray(message string) string {
return fmt.Sprintf("\x1b[37m%s\x1b[0m", message)
}
// Magenta returns a magenta string
func Magenta(message string) string {
return fmt.Sprintf("\x1b[35m%s\x1b[0m", message)
}
// CyanBold returns a cyan bold string
func CyanBold(message string) string {
return fmt.Sprintf("\x1b[36m%s\x1b[0m", bold(message))
}
// BlueBold returns a blue bold string
func BlueBold(message string) string {
return fmt.Sprintf("\x1b[34m%s\x1b[0m", bold(message))
}
// RedBold returns a red bold string
func RedBold(message string) string {
return fmt.Sprintf("\x1b[31m%s\x1b[0m", bold(message))
}
// GreenBold returns a green bold string
func GreenBold(message string) string {
return fmt.Sprintf("\x1b[32m%s\x1b[0m", bold(message))
}
// YellowBold returns a yellow bold string
func YellowBold(message string) string {
return fmt.Sprintf("\x1b[33m%s\x1b[0m", bold(message))
}
// GrayBold returns a gray bold string
func GrayBold(message string) string {
return fmt.Sprintf("\x1b[37m%s\x1b[0m", bold(message))
}
// MagentaBold returns a magenta bold string
func MagentaBold(message string) string {
return fmt.Sprintf("\x1b[35m%s\x1b[0m", bold(message))
}

View File

@ -419,7 +419,7 @@ func (cw *colorWriter) Write(p []byte) (int, error) {
} }
if cw.mode != DiscardNonColorEscSeq || cw.state == outsideCsiCode { if cw.mode != DiscardNonColorEscSeq || cw.state == outsideCsiCode {
nw, err = cw.w.Write(p[first:len(p)]) nw, err = cw.w.Write(p[first:])
r += nw r += nw
} }

109
conf.go
View File

@ -19,10 +19,13 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"io"
"path/filepath"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
const ConfVer = 0 const confVer = 0
var defaultConf = `{ var defaultConf = `{
"version": 0, "version": 0,
@ -75,42 +78,58 @@ var conf struct {
} }
// loadConfig loads customized configuration. // loadConfig loads customized configuration.
func loadConfig() error { func loadConfig() (err error) {
foundConf := false err = filepath.Walk(".", func(path string, fileInfo os.FileInfo, err error) error {
f, err := os.Open("bee.json")
if err == nil {
defer f.Close()
ColorLog("[INFO] Detected bee.json\n")
d := json.NewDecoder(f)
err = d.Decode(&conf)
if err != nil { if err != nil {
return err return nil
} }
foundConf = true
} if fileInfo.IsDir() {
byml, erryml := ioutil.ReadFile("Beefile") return nil
if erryml == nil {
ColorLog("[INFO] Detected Beefile\n")
err = yaml.Unmarshal(byml, &conf)
if err != nil {
return err
} }
foundConf = true
} if fileInfo.Name() == "bee.json" {
if !foundConf { logger.Info("Loading configuration from 'bee.json'...")
// Use default. err = parseJSON(path, conf)
if err != nil {
logger.Errorf("Failed to parse JSON file: %s", err)
return err
}
return io.EOF
}
if fileInfo.Name() == "Beefile" {
logger.Info("Loading configuration from 'Beefile'...")
err = parseYAML(path, conf)
if err != nil {
logger.Errorf("Failed to parse YAML file: %s", err)
return err
}
return io.EOF
}
return nil
})
// In case no configuration file found or an error different than io.EOF,
// fallback to default configuration
if err != io.EOF {
logger.Info("Loading default configuration...")
err = json.Unmarshal([]byte(defaultConf), &conf) err = json.Unmarshal([]byte(defaultConf), &conf)
if err != nil { if err != nil {
return err return
} }
} }
// Check format version.
if conf.Version != ConfVer { // No need to return io.EOF error
ColorLog("[WARN] Your bee.json is out-of-date, please update!\n") err = nil
ColorLog("[HINT] Compare bee.json under bee source code path and yours\n")
// Check format version
if conf.Version != confVer {
logger.Warn("Your configuration file is outdated. Please do consider updating it.")
logger.Hint("Check the latest version of bee's configuration file.")
} }
// Set variables. // Set variables
if len(conf.DirStruct.Controllers) == 0 { if len(conf.DirStruct.Controllers) == 0 {
conf.DirStruct.Controllers = "controllers" conf.DirStruct.Controllers = "controllers"
} }
@ -118,7 +137,39 @@ func loadConfig() error {
conf.DirStruct.Models = "models" conf.DirStruct.Models = "models"
} }
// Append watch exts. // Append watch exts
watchExts = append(watchExts, conf.WatchExt...) watchExts = append(watchExts, conf.WatchExt...)
return
}
func parseJSON(path string, v interface{}) error {
var (
data []byte
err error
)
data, err = ioutil.ReadFile(path)
if err != nil {
return err
}
err = json.Unmarshal(data, &v)
if err != nil {
return err
}
return nil
}
func parseYAML(path string, v interface{}) error {
var (
data []byte
err error
)
data, err = ioutil.ReadFile(path)
if err != nil {
return err
}
err = yaml.Unmarshal(data, &v)
if err != nil {
return err
}
return nil return nil
} }

23
fix.go
View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"go/parser" "go/parser"
"go/token" "go/token"
"io/ioutil" "io/ioutil"
@ -8,7 +9,6 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"fmt"
) )
var cmdFix = &Command{ var cmdFix = &Command{
@ -23,16 +23,17 @@ bee fix help to upgrade the application to beego 1.6
func init() { func init() {
cmdFix.Run = runFix cmdFix.Run = runFix
cmdFix.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
} }
func runFix(cmd *Command, args []string) int { func runFix(cmd *Command, args []string) int {
ShowShortVersionBanner() logger.Info("Upgrading the application...")
ColorLog("[INFO] Upgrading the application...\n")
dir, err := os.Getwd() dir, err := os.Getwd()
if err != nil { if err != nil {
ColorLog("[ERRO] GetCurrent Path:%s\n", err) logger.Fatalf("Error while getting the current working directory: %s", err)
} }
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if info.IsDir() { if info.IsDir() {
if strings.HasPrefix(info.Name(), ".") { if strings.HasPrefix(info.Name(), ".") {
@ -49,11 +50,11 @@ func runFix(cmd *Command, args []string) int {
err = fixFile(path) err = fixFile(path)
fmt.Println("\tfix\t", path) fmt.Println("\tfix\t", path)
if err != nil { if err != nil {
ColorLog("[ERRO] Could not fix file: %s\n", err) logger.Errorf("Could not fix file: %s", err)
} }
return err return err
}) })
ColorLog("[INFO] Upgrade done!\n") logger.Success("Upgrade done!")
return 0 return 0
} }
@ -167,18 +168,18 @@ func fixFile(file string) error {
} }
fixed := rp.Replace(string(content)) fixed := rp.Replace(string(content))
// forword the RequestBody from the replace // Forword the RequestBody from the replace
// "Input.Request", "Input.Context.Request", // "Input.Request", "Input.Context.Request",
fixed = strings.Replace(fixed, "Input.Context.RequestBody", "Input.RequestBody", -1) fixed = strings.Replace(fixed, "Input.Context.RequestBody", "Input.RequestBody", -1)
// regexp replace // Regexp replace
pareg := regexp.MustCompile(`(Input.Params\[")(.*)("])`) pareg := regexp.MustCompile(`(Input.Params\[")(.*)("])`)
fixed = pareg.ReplaceAllString(fixed, "Input.Param(\"$2\")") fixed = pareg.ReplaceAllString(fixed, "Input.Param(\"$2\")")
pareg = regexp.MustCompile(`(Input.Data\[\")(.*)(\"\])(\s)(=)(\s)(.*)`) pareg = regexp.MustCompile(`(Input.Data\[\")(.*)(\"\])(\s)(=)(\s)(.*)`)
fixed = pareg.ReplaceAllString(fixed, "Input.SetData(\"$2\", $7)") fixed = pareg.ReplaceAllString(fixed, "Input.SetData(\"$2\", $7)")
pareg = regexp.MustCompile(`(Input.Data\[\")(.*)(\"\])`) pareg = regexp.MustCompile(`(Input.Data\[\")(.*)(\"\])`)
fixed = pareg.ReplaceAllString(fixed, "Input.Data(\"$2\")") fixed = pareg.ReplaceAllString(fixed, "Input.Data(\"$2\")")
// fix the cache object Put method // Fix the cache object Put method
pareg = regexp.MustCompile(`(\.Put\(\")(.*)(\",)(\s)(.*)(,\s*)([^\*.]*)(\))`) pareg = regexp.MustCompile(`(\.Put\(\")(.*)(\",)(\s)(.*)(,\s*)([^\*.]*)(\))`)
if pareg.MatchString(fixed) && strings.HasSuffix(file, ".go") { if pareg.MatchString(fixed) && strings.HasSuffix(file, ".go") {
fixed = pareg.ReplaceAllString(fixed, ".Put(\"$2\", $5, $7*time.Second)") fixed = pareg.ReplaceAllString(fixed, ".Put(\"$2\", $5, $7*time.Second)")
@ -199,11 +200,11 @@ func fixFile(file string) error {
fixed = strings.Replace(fixed, "import (", "import (\n\t\"time\"", 1) fixed = strings.Replace(fixed, "import (", "import (\n\t\"time\"", 1)
} }
} }
// replace the v.Apis in docs.go // Replace the v.Apis in docs.go
if strings.Contains(file, "docs.go") { if strings.Contains(file, "docs.go") {
fixed = strings.Replace(fixed, "v.Apis", "v.APIs", -1) fixed = strings.Replace(fixed, "v.Apis", "v.APIs", -1)
} }
// replace the config file // Replace the config file
if strings.HasSuffix(file, ".conf") { if strings.HasSuffix(file, ".conf") {
fixed = strings.Replace(fixed, "HttpCertFile", "HTTPSCertFile", -1) fixed = strings.Replace(fixed, "HttpCertFile", "HTTPSCertFile", -1)
fixed = strings.Replace(fixed, "HttpKeyFile", "HTTPSKeyFile", -1) fixed = strings.Replace(fixed, "HttpKeyFile", "HTTPSKeyFile", -1)

66
g.go
View File

@ -69,6 +69,7 @@ var fields docValue
func init() { func init() {
cmdGenerate.Run = generateCode cmdGenerate.Run = generateCode
cmdGenerate.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
cmdGenerate.Flag.Var(&tables, "tables", "specify tables to generate model") cmdGenerate.Flag.Var(&tables, "tables", "specify tables to generate model")
cmdGenerate.Flag.Var(&driver, "driver", "database driver: mysql, postgresql, etc.") cmdGenerate.Flag.Var(&driver, "driver", "database driver: mysql, postgresql, etc.")
cmdGenerate.Flag.Var(&conn, "conn", "connection string used by the driver to connect to a database instance") cmdGenerate.Flag.Var(&conn, "conn", "connection string used by the driver to connect to a database instance")
@ -77,33 +78,30 @@ func init() {
} }
func generateCode(cmd *Command, args []string) int { func generateCode(cmd *Command, args []string) int {
ShowShortVersionBanner()
currpath, _ := os.Getwd() currpath, _ := os.Getwd()
if len(args) < 1 { if len(args) < 1 {
ColorLog("[ERRO] command is missing\n") logger.Fatal("Command is missing")
os.Exit(2)
} }
gps := GetGOPATHs() gps := GetGOPATHs()
if len(gps) == 0 { if len(gps) == 0 {
ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty") logger.Fatal("GOPATH environment variable is not set or empty")
os.Exit(2)
} }
gopath := gps[0] gopath := gps[0]
Debugf("GOPATH: %s", gopath)
logger.Debugf("GOPATH: %s", __FILE__(), __LINE__(), gopath)
gcmd := args[0] gcmd := args[0]
switch gcmd { switch gcmd {
case "scaffold": case "scaffold":
if len(args) < 2 { if len(args) < 2 {
ColorLog("[ERRO] Wrong number of arguments\n") logger.Fatal("Wrong number of arguments. Run: bee help generate")
ColorLog("[HINT] Usage: bee generate scaffold [scaffoldname] [-fields=\"\"]\n")
os.Exit(2)
} }
// Load the configuration
err := loadConfig() err := loadConfig()
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err) logger.Fatalf("Failed to load configuration: %s", err)
} }
cmd.Flag.Parse(args[2:]) cmd.Flag.Parse(args[2:])
if driver == "" { if driver == "" {
@ -119,19 +117,18 @@ func generateCode(cmd *Command, args []string) int {
} }
} }
if fields == "" { if fields == "" {
ColorLog("[ERRO] Wrong number of arguments\n") logger.Hint("fields option should not be empty, i.e. -fields=\"title:string,body:text\"")
ColorLog("[HINT] Usage: bee generate scaffold [scaffoldname] [-fields=\"title:string,body:text\"]\n") logger.Fatal("Wrong number of arguments. Run: bee help generate")
os.Exit(2)
} }
sname := args[1] sname := args[1]
generateScaffold(sname, fields.String(), currpath, driver.String(), conn.String()) generateScaffold(sname, fields.String(), currpath, driver.String(), conn.String())
case "docs": case "docs":
generateDocs(currpath) generateDocs(currpath)
case "appcode": case "appcode":
// load config // Load the configuration
err := loadConfig() err := loadConfig()
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err) logger.Fatalf("Failed to load configuration: %s", err)
} }
cmd.Flag.Parse(args[1:]) cmd.Flag.Parse(args[1:])
if driver == "" { if driver == "" {
@ -153,20 +150,20 @@ func generateCode(cmd *Command, args []string) int {
if level == "" { if level == "" {
level = "3" level = "3"
} }
ColorLog("[INFO] Using '%s' as 'driver'\n", driver) logger.Infof("Using '%s' as 'driver'", driver)
ColorLog("[INFO] Using '%s' as 'conn'\n", conn) logger.Infof("Using '%s' as 'conn'", conn)
ColorLog("[INFO] Using '%s' as 'tables'\n", tables) logger.Infof("Using '%s' as 'tables'", tables)
ColorLog("[INFO] Using '%s' as 'level'\n", level) logger.Infof("Using '%s' as 'level'", level)
generateAppcode(driver.String(), conn.String(), level.String(), tables.String(), currpath) generateAppcode(driver.String(), conn.String(), level.String(), tables.String(), currpath)
case "migration": case "migration":
if len(args) < 2 { if len(args) < 2 {
ColorLog("[ERRO] Wrong number of arguments\n") logger.Fatal("Wrong number of arguments. Run: bee help generate")
ColorLog("[HINT] Usage: bee generate migration [migrationname] [-fields=\"\"]\n")
os.Exit(2)
} }
cmd.Flag.Parse(args[2:]) cmd.Flag.Parse(args[2:])
mname := args[1] mname := args[1]
ColorLog("[INFO] Using '%s' as migration name\n", mname)
logger.Infof("Using '%s' as migration name", mname)
upsql := "" upsql := ""
downsql := "" downsql := ""
if fields != "" { if fields != "" {
@ -180,21 +177,16 @@ func generateCode(cmd *Command, args []string) int {
cname := args[1] cname := args[1]
generateController(cname, currpath) generateController(cname, currpath)
} else { } else {
ColorLog("[ERRO] Wrong number of arguments\n") logger.Fatal("Wrong number of arguments. Run: bee help generate")
ColorLog("[HINT] Usage: bee generate controller [controllername]\n")
os.Exit(2)
} }
case "model": case "model":
if len(args) < 2 { if len(args) < 2 {
ColorLog("[ERRO] Wrong number of arguments\n") logger.Fatal("Wrong number of arguments. Run: bee help generate")
ColorLog("[HINT] Usage: bee generate model [modelname] [-fields=\"\"]\n")
os.Exit(2)
} }
cmd.Flag.Parse(args[2:]) cmd.Flag.Parse(args[2:])
if fields == "" { if fields == "" {
ColorLog("[ERRO] Wrong number of arguments\n") logger.Hint("fields option should not be empty, i.e. -fields=\"title:string,body:text\"")
ColorLog("[HINT] Usage: bee generate model [modelname] [-fields=\"title:string,body:text\"]\n") logger.Fatal("Wrong number of arguments. Run: bee help generate")
os.Exit(2)
} }
sname := args[1] sname := args[1]
generateModel(sname, fields.String(), currpath) generateModel(sname, fields.String(), currpath)
@ -203,13 +195,11 @@ func generateCode(cmd *Command, args []string) int {
cname := args[1] cname := args[1]
generateView(cname, currpath) generateView(cname, currpath)
} else { } else {
ColorLog("[ERRO] Wrong number of arguments\n") logger.Fatal("Wrong number of arguments. Run: bee help generate")
ColorLog("[HINT] Usage: bee generate view [viewpath]\n")
os.Exit(2)
} }
default: default:
ColorLog("[ERRO] Command is missing\n") logger.Fatal("Command is missing")
} }
ColorLog("[SUCC] %s successfully generated!\n", strings.Title(gcmd)) logger.Successf("%s successfully generated!", strings.Title(gcmd))
return 0 return 0
} }

View File

@ -18,7 +18,6 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"os" "os"
"os/exec"
"path" "path"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -39,7 +38,7 @@ type DbTransformer interface {
GetTableNames(conn *sql.DB) []string GetTableNames(conn *sql.DB) []string
GetConstraints(conn *sql.DB, table *Table, blackList map[string]bool) GetConstraints(conn *sql.DB, table *Table, blackList map[string]bool)
GetColumns(conn *sql.DB, table *Table, blackList map[string]bool) GetColumns(conn *sql.DB, table *Table, blackList map[string]bool)
GetGoDataType(sqlType string) string GetGoDataType(sqlType string) (string, error)
} }
// MysqlDB is the MySQL version of DbTransformer // MysqlDB is the MySQL version of DbTransformer
@ -265,9 +264,7 @@ func generateAppcode(driver, connStr, level, tables, currpath string) {
case "3": case "3":
mode = OModel | OController | ORouter mode = OModel | OController | ORouter
default: default:
ColorLog("[ERRO] Invalid 'level' option: %s\n", level) logger.Fatal("Invalid level value. Must be either \"1\", \"2\", or \"3\"")
ColorLog("[HINT] Level must be either 1, 2 or 3\n")
os.Exit(2)
} }
var selectedTables map[string]bool var selectedTables map[string]bool
if tables != "" { if tables != "" {
@ -280,12 +277,9 @@ func generateAppcode(driver, connStr, level, tables, currpath string) {
case "mysql": case "mysql":
case "postgres": case "postgres":
case "sqlite": case "sqlite":
ColorLog("[ERRO] Generating app code from SQLite database is not supported yet.\n") logger.Fatal("Generating app code from SQLite database is not supported yet.")
os.Exit(2)
default: default:
ColorLog("[ERRO] Unknown database driver: %s\n", driver) logger.Fatal("Unknown database driver. Must be either \"mysql\", \"postgres\" or \"sqlite\"")
ColorLog("[HINT] Driver must be one of mysql, postgres or sqlite\n")
os.Exit(2)
} }
gen(driver, connStr, mode, selectedTables, currpath) gen(driver, connStr, mode, selectedTables, currpath)
} }
@ -295,12 +289,11 @@ func generateAppcode(driver, connStr, level, tables, currpath string) {
func gen(dbms, connStr string, mode byte, selectedTableNames map[string]bool, apppath string) { func gen(dbms, connStr string, mode byte, selectedTableNames map[string]bool, apppath string) {
db, err := sql.Open(dbms, connStr) db, err := sql.Open(dbms, connStr)
if err != nil { if err != nil {
ColorLog("[ERRO] Could not connect to %s database: %s, %s\n", dbms, connStr, err) logger.Fatalf("Could not connect to '%s' database using '%s': %s", dbms, connStr, err)
os.Exit(2)
} }
defer db.Close() defer db.Close()
if trans, ok := dbDriver[dbms]; ok { if trans, ok := dbDriver[dbms]; ok {
ColorLog("[INFO] Analyzing database tables...\n") logger.Info("Analyzing database tables...")
tableNames := trans.GetTableNames(db) tableNames := trans.GetTableNames(db)
tables := getTableObjects(tableNames, db, trans) tables := getTableObjects(tableNames, db, trans)
mvcPath := new(MvcPath) mvcPath := new(MvcPath)
@ -311,25 +304,21 @@ func gen(dbms, connStr string, mode byte, selectedTableNames map[string]bool, ap
pkgPath := getPackagePath(apppath) pkgPath := getPackagePath(apppath)
writeSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames) writeSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames)
} else { } else {
ColorLog("[ERRO] Generating app code from %s database is not supported yet.\n", dbms) logger.Fatalf("Generating app code from '%s' database is not supported yet.", dbms)
os.Exit(2)
} }
} }
// getTables gets a list table names in current database // GetTableNames returns a slice of table names in the current database
func (*MysqlDB) GetTableNames(db *sql.DB) (tables []string) { func (*MysqlDB) GetTableNames(db *sql.DB) (tables []string) {
rows, err := db.Query("SHOW TABLES") rows, err := db.Query("SHOW TABLES")
if err != nil { if err != nil {
ColorLog("[ERRO] Could not show tables\n") logger.Fatalf("Could not show tables: %s", err)
ColorLog("[HINT] Check your connection string\n")
os.Exit(2)
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var name string var name string
if err := rows.Scan(&name); err != nil { if err := rows.Scan(&name); err != nil {
ColorLog("[ERRO] Could not show tables\n") logger.Fatalf("Could not show tables: %s", err)
os.Exit(2)
} }
tables = append(tables, name) tables = append(tables, name)
} }
@ -358,8 +347,8 @@ func getTableObjects(tableNames []string, db *sql.DB, dbTransformer DbTransforme
return return
} }
// getConstraints gets primary key, unique key and foreign keys of a table from information_schema // GetConstraints gets primary key, unique key and foreign keys of a table from
// and fill in Table struct // information_schema and fill in the Table struct
func (*MysqlDB) GetConstraints(db *sql.DB, table *Table, blackList map[string]bool) { func (*MysqlDB) GetConstraints(db *sql.DB, table *Table, blackList map[string]bool) {
rows, err := db.Query( rows, err := db.Query(
`SELECT `SELECT
@ -372,14 +361,12 @@ func (*MysqlDB) GetConstraints(db *sql.DB, table *Table, blackList map[string]bo
c.table_schema = database() AND c.table_name = ? AND u.table_schema = database() AND u.table_name = ?`, c.table_schema = database() AND c.table_name = ? AND u.table_schema = database() AND u.table_name = ?`,
table.Name, table.Name) // u.position_in_unique_constraint, table.Name, table.Name) // u.position_in_unique_constraint,
if err != nil { if err != nil {
ColorLog("[ERRO] Could not query INFORMATION_SCHEMA for PK/UK/FK information\n") logger.Fatal("Could not query INFORMATION_SCHEMA for PK/UK/FK information")
os.Exit(2)
} }
for rows.Next() { for rows.Next() {
var constraintTypeBytes, columnNameBytes, refTableSchemaBytes, refTableNameBytes, refColumnNameBytes, refOrdinalPosBytes []byte var constraintTypeBytes, columnNameBytes, refTableSchemaBytes, refTableNameBytes, refColumnNameBytes, refOrdinalPosBytes []byte
if err := rows.Scan(&constraintTypeBytes, &columnNameBytes, &refTableSchemaBytes, &refTableNameBytes, &refColumnNameBytes, &refOrdinalPosBytes); err != nil { if err := rows.Scan(&constraintTypeBytes, &columnNameBytes, &refTableSchemaBytes, &refTableNameBytes, &refColumnNameBytes, &refOrdinalPosBytes); err != nil {
ColorLog("[ERRO] Could not read INFORMATION_SCHEMA for PK/UK/FK information\n") logger.Fatal("Could not read INFORMATION_SCHEMA for PK/UK/FK information")
os.Exit(2)
} }
constraintType, columnName, refTableSchema, refTableName, refColumnName, refOrdinalPos := constraintType, columnName, refTableSchema, refTableName, refColumnName, refOrdinalPos :=
string(constraintTypeBytes), string(columnNameBytes), string(refTableSchemaBytes), string(constraintTypeBytes), string(columnNameBytes), string(refTableSchemaBytes),
@ -389,7 +376,7 @@ func (*MysqlDB) GetConstraints(db *sql.DB, table *Table, blackList map[string]bo
table.Pk = columnName table.Pk = columnName
} else { } else {
table.Pk = "" table.Pk = ""
// add table to blacklist so that other struct will not reference it, because we are not // Add table to blacklist so that other struct will not reference it, because we are not
// registering blacklisted tables // registering blacklisted tables
blackList[table.Name] = true blackList[table.Name] = true
} }
@ -406,11 +393,11 @@ func (*MysqlDB) GetConstraints(db *sql.DB, table *Table, blackList map[string]bo
} }
} }
// getColumns retrieve columns details from information_schema // GetColumns retrieves columns details from
// and fill in the Column struct // information_schema and fill in the Column struct
func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[string]bool) { func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[string]bool) {
// retrieve columns // retrieve columns
colDefRows, _ := db.Query( colDefRows, err := db.Query(
`SELECT `SELECT
column_name, data_type, column_type, is_nullable, column_default, extra column_name, data_type, column_type, is_nullable, column_default, extra
FROM FROM
@ -418,20 +405,28 @@ func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[strin
WHERE WHERE
table_schema = database() AND table_name = ?`, table_schema = database() AND table_name = ?`,
table.Name) table.Name)
if err != nil {
logger.Fatalf("Could not query the database: %s", err)
}
defer colDefRows.Close() defer colDefRows.Close()
for colDefRows.Next() { for colDefRows.Next() {
// datatype as bytes so that SQL <null> values can be retrieved // datatype as bytes so that SQL <null> values can be retrieved
var colNameBytes, dataTypeBytes, columnTypeBytes, isNullableBytes, columnDefaultBytes, extraBytes []byte var colNameBytes, dataTypeBytes, columnTypeBytes, isNullableBytes, columnDefaultBytes, extraBytes []byte
if err := colDefRows.Scan(&colNameBytes, &dataTypeBytes, &columnTypeBytes, &isNullableBytes, &columnDefaultBytes, &extraBytes); err != nil { if err := colDefRows.Scan(&colNameBytes, &dataTypeBytes, &columnTypeBytes, &isNullableBytes, &columnDefaultBytes, &extraBytes); err != nil {
ColorLog("[ERRO] Could not query INFORMATION_SCHEMA for column information\n") logger.Fatal("Could not query INFORMATION_SCHEMA for column information")
os.Exit(2)
} }
colName, dataType, columnType, isNullable, columnDefault, extra := colName, dataType, columnType, isNullable, columnDefault, extra :=
string(colNameBytes), string(dataTypeBytes), string(columnTypeBytes), string(isNullableBytes), string(columnDefaultBytes), string(extraBytes) string(colNameBytes), string(dataTypeBytes), string(columnTypeBytes), string(isNullableBytes), string(columnDefaultBytes), string(extraBytes)
// create a column // create a column
col := new(Column) col := new(Column)
col.Name = camelCase(colName) col.Name = camelCase(colName)
col.Type = mysqlDB.GetGoDataType(dataType) col.Type, err = mysqlDB.GetGoDataType(dataType)
if err != nil {
logger.Fatalf("%s", err)
}
// Tag info // Tag info
tag := new(OrmTag) tag := new(OrmTag)
tag.Column = colName tag.Column = colName
@ -466,7 +461,10 @@ func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[strin
if isSQLSignedIntType(dataType) { if isSQLSignedIntType(dataType) {
sign := extractIntSignness(columnType) sign := extractIntSignness(columnType)
if sign == "unsigned" && extra != "auto_increment" { if sign == "unsigned" && extra != "auto_increment" {
col.Type = mysqlDB.GetGoDataType(dataType + " " + sign) col.Type, err = mysqlDB.GetGoDataType(dataType + " " + sign)
if err != nil {
logger.Fatalf("%s", err)
}
} }
} }
if isSQLStringType(dataType) { if isSQLStringType(dataType) {
@ -500,15 +498,13 @@ func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[strin
} }
// GetGoDataType maps an SQL data type to Golang data type // GetGoDataType maps an SQL data type to Golang data type
func (*MysqlDB) GetGoDataType(sqlType string) (goType string) { func (*MysqlDB) GetGoDataType(sqlType string) (string, error) {
var typeMapping = map[string]string{} var typeMapping = map[string]string{}
typeMapping = typeMappingMysql typeMapping = typeMappingMysql
if v, ok := typeMapping[sqlType]; ok { if v, ok := typeMapping[sqlType]; ok {
return v return v, nil
} }
ColorLog("[ERRO] data type (%s) not found!\n", sqlType) return "", fmt.Errorf("data type '%s' not found", sqlType)
os.Exit(2)
return goType
} }
// GetTableNames for PostgreSQL // GetTableNames for PostgreSQL
@ -519,16 +515,14 @@ func (*PostgresDB) GetTableNames(db *sql.DB) (tables []string) {
table_type = 'BASE TABLE' AND table_type = 'BASE TABLE' AND
table_schema NOT IN ('pg_catalog', 'information_schema')`) table_schema NOT IN ('pg_catalog', 'information_schema')`)
if err != nil { if err != nil {
ColorLog("[ERRO] Could not show tables: %s\n", err) logger.Fatalf("Could not show tables: %s", err)
ColorLog("[HINT] Check your connection string\n")
os.Exit(2)
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var name string var name string
if err := rows.Scan(&name); err != nil { if err := rows.Scan(&name); err != nil {
ColorLog("[ERRO] Could not show tables\n") logger.Fatalf("Could not show tables: %s", err)
os.Exit(2)
} }
tables = append(tables, name) tables = append(tables, name)
} }
@ -558,14 +552,13 @@ func (*PostgresDB) GetConstraints(db *sql.DB, table *Table, blackList map[string
AND u.table_name = $2`, AND u.table_name = $2`,
table.Name, table.Name) // u.position_in_unique_constraint, table.Name, table.Name) // u.position_in_unique_constraint,
if err != nil { if err != nil {
ColorLog("[ERRO] Could not query INFORMATION_SCHEMA for PK/UK/FK information: %s\n", err) logger.Fatalf("Could not query INFORMATION_SCHEMA for PK/UK/FK information: %s", err)
os.Exit(2)
} }
for rows.Next() { for rows.Next() {
var constraintTypeBytes, columnNameBytes, refTableSchemaBytes, refTableNameBytes, refColumnNameBytes, refOrdinalPosBytes []byte var constraintTypeBytes, columnNameBytes, refTableSchemaBytes, refTableNameBytes, refColumnNameBytes, refOrdinalPosBytes []byte
if err := rows.Scan(&constraintTypeBytes, &columnNameBytes, &refTableSchemaBytes, &refTableNameBytes, &refColumnNameBytes, &refOrdinalPosBytes); err != nil { if err := rows.Scan(&constraintTypeBytes, &columnNameBytes, &refTableSchemaBytes, &refTableNameBytes, &refColumnNameBytes, &refOrdinalPosBytes); err != nil {
ColorLog("[ERRO] Could not read INFORMATION_SCHEMA for PK/UK/FK information\n") logger.Fatalf("Could not read INFORMATION_SCHEMA for PK/UK/FK information: %s", err)
os.Exit(2)
} }
constraintType, columnName, refTableSchema, refTableName, refColumnName, refOrdinalPos := constraintType, columnName, refTableSchema, refTableName, refColumnName, refOrdinalPos :=
string(constraintTypeBytes), string(columnNameBytes), string(refTableSchemaBytes), string(constraintTypeBytes), string(columnNameBytes), string(refTableSchemaBytes),
@ -595,7 +588,7 @@ func (*PostgresDB) GetConstraints(db *sql.DB, table *Table, blackList map[string
// GetColumns for PostgreSQL // GetColumns for PostgreSQL
func (postgresDB *PostgresDB) GetColumns(db *sql.DB, table *Table, blackList map[string]bool) { func (postgresDB *PostgresDB) GetColumns(db *sql.DB, table *Table, blackList map[string]bool) {
// retrieve columns // retrieve columns
colDefRows, _ := db.Query( colDefRows, err := db.Query(
`SELECT `SELECT
column_name, column_name,
data_type, data_type,
@ -614,20 +607,27 @@ func (postgresDB *PostgresDB) GetColumns(db *sql.DB, table *Table, blackList map
table_catalog = current_database() AND table_schema NOT IN ('pg_catalog', 'information_schema') table_catalog = current_database() AND table_schema NOT IN ('pg_catalog', 'information_schema')
AND table_name = $1`, AND table_name = $1`,
table.Name) table.Name)
if err != nil {
logger.Fatalf("Could not query INFORMATION_SCHEMA for column information: %s", err)
}
defer colDefRows.Close() defer colDefRows.Close()
for colDefRows.Next() { for colDefRows.Next() {
// datatype as bytes so that SQL <null> values can be retrieved // datatype as bytes so that SQL <null> values can be retrieved
var colNameBytes, dataTypeBytes, columnTypeBytes, isNullableBytes, columnDefaultBytes, extraBytes []byte var colNameBytes, dataTypeBytes, columnTypeBytes, isNullableBytes, columnDefaultBytes, extraBytes []byte
if err := colDefRows.Scan(&colNameBytes, &dataTypeBytes, &columnTypeBytes, &isNullableBytes, &columnDefaultBytes, &extraBytes); err != nil { if err := colDefRows.Scan(&colNameBytes, &dataTypeBytes, &columnTypeBytes, &isNullableBytes, &columnDefaultBytes, &extraBytes); err != nil {
ColorLog("[ERRO] Could not query INFORMATION_SCHEMA for column information\n") logger.Fatalf("Could not query INFORMATION_SCHEMA for column information: %s", err)
os.Exit(2)
} }
colName, dataType, columnType, isNullable, columnDefault, extra := colName, dataType, columnType, isNullable, columnDefault, extra :=
string(colNameBytes), string(dataTypeBytes), string(columnTypeBytes), string(isNullableBytes), string(columnDefaultBytes), string(extraBytes) string(colNameBytes), string(dataTypeBytes), string(columnTypeBytes), string(isNullableBytes), string(columnDefaultBytes), string(extraBytes)
// create a column // Create a column
col := new(Column) col := new(Column)
col.Name = camelCase(colName) col.Name = camelCase(colName)
col.Type = postgresDB.GetGoDataType(dataType) col.Type, err = postgresDB.GetGoDataType(dataType)
if err != nil {
logger.Fatalf("%s", err)
}
// Tag info // Tag info
tag := new(OrmTag) tag := new(OrmTag)
tag.Column = colName tag.Column = colName
@ -690,13 +690,11 @@ func (postgresDB *PostgresDB) GetColumns(db *sql.DB, table *Table, blackList map
} }
// GetGoDataType returns the Go type from the mapped Postgres type // GetGoDataType returns the Go type from the mapped Postgres type
func (*PostgresDB) GetGoDataType(sqlType string) (goType string) { func (*PostgresDB) GetGoDataType(sqlType string) (string, error) {
if v, ok := typeMappingPostgres[sqlType]; ok { if v, ok := typeMappingPostgres[sqlType]; ok {
return v return v, nil
} }
ColorLog("[ERRO] data type (%s) not found!\n", sqlType) return "", fmt.Errorf("data type '%s' not found", sqlType)
os.Exit(2)
return goType
} }
// deleteAndRecreatePaths removes several directories completely // deleteAndRecreatePaths removes several directories completely
@ -717,15 +715,15 @@ func createPaths(mode byte, paths *MvcPath) {
// Newly geneated files will be inside these folders. // Newly geneated files will be inside these folders.
func writeSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) { func writeSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) {
if (OModel & mode) == OModel { if (OModel & mode) == OModel {
ColorLog("[INFO] Creating model files...\n") logger.Info("Creating model files...")
writeModelFiles(tables, paths.ModelPath, selectedTables) writeModelFiles(tables, paths.ModelPath, selectedTables)
} }
if (OController & mode) == OController { if (OController & mode) == OController {
ColorLog("[INFO] Creating controller files...\n") logger.Info("Creating controller files...")
writeControllerFiles(tables, paths.ControllerPath, selectedTables, pkgPath) writeControllerFiles(tables, paths.ControllerPath, selectedTables, pkgPath)
} }
if (ORouter & mode) == ORouter { if (ORouter & mode) == ORouter {
ColorLog("[INFO] Creating router files...\n") logger.Info("Creating router files...")
writeRouterFile(tables, paths.RouterPath, selectedTables, pkgPath) writeRouterFile(tables, paths.RouterPath, selectedTables, pkgPath)
} }
} }
@ -746,21 +744,21 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
var f *os.File var f *os.File
var err error var err error
if isExist(fpath) { if isExist(fpath) {
ColorLog("[WARN] '%v' already exists. Do you want to overwrite it? [Yes|No] ", fpath) logger.Warnf("'%s' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if askForConfirmation() { if askForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil { if err != nil {
ColorLog("[WARN] %v\n", err) logger.Warnf("%s", err)
continue continue
} }
} else { } else {
ColorLog("[WARN] Skipped create file '%s'\n", fpath) logger.Warnf("Skipped create file '%s'", fpath)
continue continue
} }
} else { } else {
f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666) f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666)
if err != nil { if err != nil {
ColorLog("[WARN] %v\n", err) logger.Warnf("%s", err)
continue continue
} }
} }
@ -773,7 +771,8 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1) fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1)
fileStr = strings.Replace(fileStr, "{{modelName}}", camelCase(tb.Name), -1) fileStr = strings.Replace(fileStr, "{{modelName}}", camelCase(tb.Name), -1)
fileStr = strings.Replace(fileStr, "{{tableName}}", tb.Name, -1) fileStr = strings.Replace(fileStr, "{{tableName}}", tb.Name, -1)
// if table contains time field, import time.Time package
// If table contains time field, import time.Time package
timePkg := "" timePkg := ""
importTimePkg := "" importTimePkg := ""
if tb.ImportTimePkg { if tb.ImportTimePkg {
@ -783,8 +782,7 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
fileStr = strings.Replace(fileStr, "{{timePkg}}", timePkg, -1) fileStr = strings.Replace(fileStr, "{{timePkg}}", timePkg, -1)
fileStr = strings.Replace(fileStr, "{{importTimePkg}}", importTimePkg, -1) fileStr = strings.Replace(fileStr, "{{importTimePkg}}", importTimePkg, -1)
if _, err := f.WriteString(fileStr); err != nil { if _, err := f.WriteString(fileStr); err != nil {
ColorLog("[ERRO] Could not write model file to %s\n", fpath) logger.Fatalf("Could not write model file to '%s': %s", fpath, err)
os.Exit(2)
} }
CloseFile(f) CloseFile(f)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
@ -797,7 +795,7 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri
w := NewColorWriter(os.Stdout) w := NewColorWriter(os.Stdout)
for _, tb := range tables { for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it // If selectedTables map is not nil and this table is not selected, ignore it
if selectedTables != nil { if selectedTables != nil {
if _, selected := selectedTables[tb.Name]; !selected { if _, selected := selectedTables[tb.Name]; !selected {
continue continue
@ -811,29 +809,28 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri
var f *os.File var f *os.File
var err error var err error
if isExist(fpath) { if isExist(fpath) {
ColorLog("[WARN] '%v' already exists. Do you want to overwrite it? [Yes|No] ", fpath) logger.Warnf("'%s' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if askForConfirmation() { if askForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil { if err != nil {
ColorLog("[WARN] %v\n", err) logger.Warnf("%s", err)
continue continue
} }
} else { } else {
ColorLog("[WARN] Skipped create file '%s'\n", fpath) logger.Warnf("Skipped create file '%s'", fpath)
continue continue
} }
} else { } else {
f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666) f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666)
if err != nil { if err != nil {
ColorLog("[WARN] %v\n", err) logger.Warnf("%s", err)
continue continue
} }
} }
fileStr := strings.Replace(CtrlTPL, "{{ctrlName}}", camelCase(tb.Name), -1) fileStr := strings.Replace(CtrlTPL, "{{ctrlName}}", camelCase(tb.Name), -1)
fileStr = strings.Replace(fileStr, "{{pkgPath}}", pkgPath, -1) fileStr = strings.Replace(fileStr, "{{pkgPath}}", pkgPath, -1)
if _, err := f.WriteString(fileStr); err != nil { if _, err := f.WriteString(fileStr); err != nil {
ColorLog("[ERRO] Could not write controller file to %s\n", fpath) logger.Fatalf("Could not write controller file to '%s': %s", fpath, err)
os.Exit(2)
} }
CloseFile(f) CloseFile(f)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
@ -847,7 +844,7 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo
var nameSpaces []string var nameSpaces []string
for _, tb := range tables { for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it // If selectedTables map is not nil and this table is not selected, ignore it
if selectedTables != nil { if selectedTables != nil {
if _, selected := selectedTables[tb.Name]; !selected { if _, selected := selectedTables[tb.Name]; !selected {
continue continue
@ -856,63 +853,44 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo
if tb.Pk == "" { if tb.Pk == "" {
continue continue
} }
// add namespaces // Add namespaces
nameSpace := strings.Replace(NamespaceTPL, "{{nameSpace}}", tb.Name, -1) nameSpace := strings.Replace(NamespaceTPL, "{{nameSpace}}", tb.Name, -1)
nameSpace = strings.Replace(nameSpace, "{{ctrlName}}", camelCase(tb.Name), -1) nameSpace = strings.Replace(nameSpace, "{{ctrlName}}", camelCase(tb.Name), -1)
nameSpaces = append(nameSpaces, nameSpace) nameSpaces = append(nameSpaces, nameSpace)
} }
// add export controller // Add export controller
fpath := path.Join(rPath, "router.go") fpath := path.Join(rPath, "router.go")
routerStr := strings.Replace(RouterTPL, "{{nameSpaces}}", strings.Join(nameSpaces, ""), 1) routerStr := strings.Replace(RouterTPL, "{{nameSpaces}}", strings.Join(nameSpaces, ""), 1)
routerStr = strings.Replace(routerStr, "{{pkgPath}}", pkgPath, 1) routerStr = strings.Replace(routerStr, "{{pkgPath}}", pkgPath, 1)
var f *os.File var f *os.File
var err error var err error
if isExist(fpath) { if isExist(fpath) {
ColorLog("[WARN] '%v' already exists. Do you want to overwrite it? [Yes|No] ", fpath) logger.Warnf("'%s' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if askForConfirmation() { if askForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil { if err != nil {
ColorLog("[WARN] %v\n", err) logger.Warnf("%s", err)
return return
} }
} else { } else {
ColorLog("[WARN] Skipped create file '%s'\n", fpath) logger.Warnf("Skipped create file '%s'", fpath)
return return
} }
} else { } else {
f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666) f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666)
if err != nil { if err != nil {
ColorLog("[WARN] %v\n", err) logger.Warnf("%s", err)
return return
} }
} }
if _, err := f.WriteString(routerStr); err != nil { if _, err := f.WriteString(routerStr); err != nil {
ColorLog("[ERRO] Could not write router file to '%s'\n", fpath) logger.Fatalf("Could not write router file to '%s': %s", fpath, err)
os.Exit(2)
} }
CloseFile(f) CloseFile(f)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath) formatSourceCode(fpath)
} }
// formatSourceCode formats source files
func formatSourceCode(filename string) {
cmd := exec.Command("gofmt", "-w", filename)
if err := cmd.Run(); err != nil {
ColorLog("[WARN] gofmt err: %s\n", err)
}
}
// camelCase converts a _ delimited string to camel case
// e.g. very_important_person => VeryImportantPerson
func camelCase(in string) string {
tokens := strings.Split(in, "_")
for i := range tokens {
tokens[i] = strings.Title(strings.Trim(tokens[i], " "))
}
return strings.Join(tokens, "")
}
func isSQLTemporalType(t string) bool { func isSQLTemporalType(t string) bool {
return t == "date" || t == "datetime" || t == "timestamp" || t == "time" return t == "date" || t == "datetime" || t == "timestamp" || t == "time"
} }
@ -972,12 +950,12 @@ func getFileName(tbName string) (filename string) {
func getPackagePath(curpath string) (packpath string) { func getPackagePath(curpath string) (packpath string) {
gopath := os.Getenv("GOPATH") gopath := os.Getenv("GOPATH")
Debugf("gopath:%s", gopath)
if gopath == "" { if gopath == "" {
ColorLog("[ERRO] You should set GOPATH in the env") logger.Fatal("GOPATH environment variable is not set or empty")
os.Exit(2)
} }
logger.Debugf("GOPATH: %s", __FILE__(), __LINE__(), gopath)
appsrcpath := "" appsrcpath := ""
haspath := false haspath := false
wgopath := filepath.SplitList(gopath) wgopath := filepath.SplitList(gopath)
@ -993,13 +971,11 @@ func getPackagePath(curpath string) (packpath string) {
} }
if !haspath { if !haspath {
ColorLog("[ERRO] Can't generate application code outside of GOPATH '%s'\n", gopath) logger.Fatalf("Cannot generate application code outside of GOPATH '%s'", gopath)
os.Exit(2)
} }
if curpath == appsrcpath { if curpath == appsrcpath {
ColorLog("[ERRO] Can't generate application code outside of application PATH \n") logger.Fatal("Cannot generate application code outside of application path")
os.Exit(2)
} }
packpath = strings.Join(strings.Split(curpath[len(appsrcpath)+1:], string(filepath.Separator)), "/") packpath = strings.Join(strings.Split(curpath[len(appsrcpath)+1:], string(filepath.Separator)), "/")
@ -1062,7 +1038,11 @@ func GetAll{{modelName}}(query map[string]string, fields []string, sortby []stri
for k, v := range query { for k, v := range query {
// rewrite dot-notation to Object__Attribute // rewrite dot-notation to Object__Attribute
k = strings.Replace(k, ".", "__", -1) k = strings.Replace(k, ".", "__", -1)
qs = qs.Filter(k, v) if strings.Contains(k, "isnull") {
qs = qs.Filter(k, (v == "true" || v == "1"))
} else {
qs = qs.Filter(k, v)
}
} }
// order by: // order by:
var sortFields []string var sortFields []string
@ -1168,11 +1148,12 @@ import (
"github.com/astaxie/beego" "github.com/astaxie/beego"
) )
// oprations for {{ctrlName}} // {{ctrlName}}Controller oprations for {{ctrlName}}
type {{ctrlName}}Controller struct { type {{ctrlName}}Controller struct {
beego.Controller beego.Controller
} }
// URLMapping ...
func (c *{{ctrlName}}Controller) URLMapping() { func (c *{{ctrlName}}Controller) URLMapping() {
c.Mapping("Post", c.Post) c.Mapping("Post", c.Post)
c.Mapping("GetOne", c.GetOne) c.Mapping("GetOne", c.GetOne)
@ -1181,6 +1162,7 @@ func (c *{{ctrlName}}Controller) URLMapping() {
c.Mapping("Delete", c.Delete) c.Mapping("Delete", c.Delete)
} }
// Post ...
// @Title Post // @Title Post
// @Description create {{ctrlName}} // @Description create {{ctrlName}}
// @Param body body models.{{ctrlName}} true "body for {{ctrlName}} content" // @Param body body models.{{ctrlName}} true "body for {{ctrlName}} content"
@ -1202,7 +1184,8 @@ func (c *{{ctrlName}}Controller) Post() {
c.ServeJSON() c.ServeJSON()
} }
// @Title Get // GetOne ...
// @Title Get One
// @Description get {{ctrlName}} by id // @Description get {{ctrlName}} by id
// @Param id path string true "The key for staticblock" // @Param id path string true "The key for staticblock"
// @Success 200 {object} models.{{ctrlName}} // @Success 200 {object} models.{{ctrlName}}
@ -1220,6 +1203,7 @@ func (c *{{ctrlName}}Controller) GetOne() {
c.ServeJSON() c.ServeJSON()
} }
// GetAll ...
// @Title Get All // @Title Get All
// @Description get {{ctrlName}} // @Description get {{ctrlName}}
// @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..." // @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..."
@ -1235,9 +1219,9 @@ func (c *{{ctrlName}}Controller) GetAll() {
var fields []string var fields []string
var sortby []string var sortby []string
var order []string var order []string
var query map[string]string = make(map[string]string) var query = make(map[string]string)
var limit int64 = 10 var limit int64 = 10
var offset int64 = 0 var offset int64
// fields: col1,col2,entity.col3 // fields: col1,col2,entity.col3
if v := c.GetString("fields"); v != "" { if v := c.GetString("fields"); v != "" {
@ -1282,7 +1266,8 @@ func (c *{{ctrlName}}Controller) GetAll() {
c.ServeJSON() c.ServeJSON()
} }
// @Title Update // Put ...
// @Title Put
// @Description update the {{ctrlName}} // @Description update the {{ctrlName}}
// @Param id path string true "The id you want to update" // @Param id path string true "The id you want to update"
// @Param body body models.{{ctrlName}} true "body for {{ctrlName}} content" // @Param body body models.{{ctrlName}} true "body for {{ctrlName}} content"
@ -1305,6 +1290,7 @@ func (c *{{ctrlName}}Controller) Put() {
c.ServeJSON() c.ServeJSON()
} }
// Delete ...
// @Title Delete // @Title Delete
// @Description delete the {{ctrlName}} // @Description delete the {{ctrlName}}
// @Param id path string true "The id you want to delete" // @Param id path string true "The id you want to delete"

View File

@ -21,9 +21,6 @@ import (
"strings" "strings"
) )
// article
// cms/article
//
func generateController(cname, currpath string) { func generateController(cname, currpath string) {
w := NewColorWriter(os.Stdout) w := NewColorWriter(os.Stdout)
@ -36,15 +33,14 @@ func generateController(cname, currpath string) {
packageName = p[i+1 : len(p)-1] packageName = p[i+1 : len(p)-1]
} }
ColorLog("[INFO] Using '%s' as controller name\n", controllerName) logger.Infof("Using '%s' as controller name", controllerName)
ColorLog("[INFO] Using '%s' as package name\n", packageName) logger.Infof("Using '%s' as package name", packageName)
fp := path.Join(currpath, "controllers", p) fp := path.Join(currpath, "controllers", p)
if _, err := os.Stat(fp); os.IsNotExist(err) { if _, err := os.Stat(fp); os.IsNotExist(err) {
// Create the controller's directory // Create the controller's directory
if err := os.MkdirAll(fp, 0777); err != nil { if err := os.MkdirAll(fp, 0777); err != nil {
ColorLog("[ERRO] Could not create controllers directory: %s\n", err) logger.Fatalf("Could not create controllers directory: %s", err)
os.Exit(2)
} }
} }
@ -56,7 +52,7 @@ func generateController(cname, currpath string) {
var content string var content string
if _, err := os.Stat(modelPath); err == nil { if _, err := os.Stat(modelPath); err == nil {
ColorLog("[INFO] Using matching model '%s'\n", controllerName) logger.Infof("Using matching model '%s'", controllerName)
content = strings.Replace(controllerModelTpl, "{{packageName}}", packageName, -1) content = strings.Replace(controllerModelTpl, "{{packageName}}", packageName, -1)
pkgPath := getPackagePath(currpath) pkgPath := getPackagePath(currpath)
content = strings.Replace(content, "{{pkgPath}}", pkgPath, -1) content = strings.Replace(content, "{{pkgPath}}", pkgPath, -1)
@ -71,8 +67,7 @@ func generateController(cname, currpath string) {
formatSourceCode(fpath) formatSourceCode(fpath)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
} else { } else {
ColorLog("[ERRO] Could not create controller file: %s\n", err) logger.Fatalf("Could not create controller file: %s", err)
os.Exit(2)
} }
} }
@ -82,11 +77,12 @@ import (
"github.com/astaxie/beego" "github.com/astaxie/beego"
) )
// operations for {{controllerName}} // {{controllerName}}Controller operations for {{controllerName}}
type {{controllerName}}Controller struct { type {{controllerName}}Controller struct {
beego.Controller beego.Controller
} }
// URLMapping ...
func (c *{{controllerName}}Controller) URLMapping() { func (c *{{controllerName}}Controller) URLMapping() {
c.Mapping("Post", c.Post) c.Mapping("Post", c.Post)
c.Mapping("GetOne", c.GetOne) c.Mapping("GetOne", c.GetOne)
@ -95,6 +91,7 @@ func (c *{{controllerName}}Controller) URLMapping() {
c.Mapping("Delete", c.Delete) c.Mapping("Delete", c.Delete)
} }
// Post ...
// @Title Create // @Title Create
// @Description create {{controllerName}} // @Description create {{controllerName}}
// @Param body body models.{{controllerName}} true "body for {{controllerName}} content" // @Param body body models.{{controllerName}} true "body for {{controllerName}} content"
@ -105,6 +102,7 @@ func (c *{{controllerName}}Controller) Post() {
} }
// GetOne ...
// @Title GetOne // @Title GetOne
// @Description get {{controllerName}} by id // @Description get {{controllerName}} by id
// @Param id path string true "The key for staticblock" // @Param id path string true "The key for staticblock"
@ -115,6 +113,7 @@ func (c *{{controllerName}}Controller) GetOne() {
} }
// GetAll ...
// @Title GetAll // @Title GetAll
// @Description get {{controllerName}} // @Description get {{controllerName}}
// @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..." // @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..."
@ -130,7 +129,8 @@ func (c *{{controllerName}}Controller) GetAll() {
} }
// @Title Update // Put ...
// @Title Put
// @Description update the {{controllerName}} // @Description update the {{controllerName}}
// @Param id path string true "The id you want to update" // @Param id path string true "The id you want to update"
// @Param body body models.{{controllerName}} true "body for {{controllerName}} content" // @Param body body models.{{controllerName}} true "body for {{controllerName}} content"
@ -141,6 +141,7 @@ func (c *{{controllerName}}Controller) Put() {
} }
// Delete ...
// @Title Delete // @Title Delete
// @Description delete the {{controllerName}} // @Description delete the {{controllerName}}
// @Param id path string true "The id you want to delete" // @Param id path string true "The id you want to delete"
@ -164,11 +165,12 @@ import (
"github.com/astaxie/beego" "github.com/astaxie/beego"
) )
// oprations for {{controllerName}} // {{controllerName}}Controller oprations for {{controllerName}}
type {{controllerName}}Controller struct { type {{controllerName}}Controller struct {
beego.Controller beego.Controller
} }
// URLMapping ...
func (c *{{controllerName}}Controller) URLMapping() { func (c *{{controllerName}}Controller) URLMapping() {
c.Mapping("Post", c.Post) c.Mapping("Post", c.Post)
c.Mapping("GetOne", c.GetOne) c.Mapping("GetOne", c.GetOne)
@ -177,6 +179,7 @@ func (c *{{controllerName}}Controller) URLMapping() {
c.Mapping("Delete", c.Delete) c.Mapping("Delete", c.Delete)
} }
// Post ...
// @Title Post // @Title Post
// @Description create {{controllerName}} // @Description create {{controllerName}}
// @Param body body models.{{controllerName}} true "body for {{controllerName}} content" // @Param body body models.{{controllerName}} true "body for {{controllerName}} content"
@ -195,7 +198,8 @@ func (c *{{controllerName}}Controller) Post() {
c.ServeJSON() c.ServeJSON()
} }
// @Title Get // GetOne ...
// @Title Get One
// @Description get {{controllerName}} by id // @Description get {{controllerName}} by id
// @Param id path string true "The key for staticblock" // @Param id path string true "The key for staticblock"
// @Success 200 {object} models.{{controllerName}} // @Success 200 {object} models.{{controllerName}}
@ -213,6 +217,7 @@ func (c *{{controllerName}}Controller) GetOne() {
c.ServeJSON() c.ServeJSON()
} }
// GetAll ...
// @Title Get All // @Title Get All
// @Description get {{controllerName}} // @Description get {{controllerName}}
// @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..." // @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..."
@ -228,9 +233,9 @@ func (c *{{controllerName}}Controller) GetAll() {
var fields []string var fields []string
var sortby []string var sortby []string
var order []string var order []string
var query map[string]string = make(map[string]string) var query = make(map[string]string)
var limit int64 = 10 var limit int64 = 10
var offset int64 = 0 var offset int64
// fields: col1,col2,entity.col3 // fields: col1,col2,entity.col3
if v := c.GetString("fields"); v != "" { if v := c.GetString("fields"); v != "" {
@ -275,7 +280,8 @@ func (c *{{controllerName}}Controller) GetAll() {
c.ServeJSON() c.ServeJSON()
} }
// @Title Update // Put ...
// @Title Put
// @Description update the {{controllerName}} // @Description update the {{controllerName}}
// @Param id path string true "The id you want to update" // @Param id path string true "The id you want to update"
// @Param body body models.{{controllerName}} true "body for {{controllerName}} content" // @Param body body models.{{controllerName}} true "body for {{controllerName}} content"
@ -295,6 +301,7 @@ func (c *{{controllerName}}Controller) Put() {
c.ServeJSON() c.ServeJSON()
} }
// Delete ...
// @Title Delete // @Title Delete
// @Description delete the {{controllerName}} // @Description delete the {{controllerName}}
// @Param id path string true "The id you want to delete" // @Param id path string true "The id you want to delete"

324
g_docs.go
View File

@ -26,11 +26,12 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
"regexp" "regexp"
"runtime"
"strconv" "strconv"
"strings" "strings"
"unicode" "unicode"
"gopkg.in/yaml.v2"
"github.com/astaxie/beego/swagger" "github.com/astaxie/beego/swagger"
"github.com/astaxie/beego/utils" "github.com/astaxie/beego/utils"
) )
@ -48,6 +49,30 @@ var importlist map[string]string
var controllerList map[string]map[string]*swagger.Item //controllername Paths items var controllerList map[string]map[string]*swagger.Item //controllername Paths items
var modelsList map[string]map[string]swagger.Schema var modelsList map[string]map[string]swagger.Schema
var rootapi swagger.Swagger var rootapi swagger.Swagger
var astPkgs map[string]*ast.Package
// refer to builtin.go
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",
}
func init() { func init() {
pkgCache = make(map[string]struct{}) pkgCache = make(map[string]struct{})
@ -55,21 +80,68 @@ func init() {
importlist = make(map[string]string) importlist = make(map[string]string)
controllerList = make(map[string]map[string]*swagger.Item) controllerList = make(map[string]map[string]*swagger.Item)
modelsList = make(map[string]map[string]swagger.Schema) modelsList = make(map[string]map[string]swagger.Schema)
astPkgs = map[string]*ast.Package{}
}
func parsePackagesFromDir(dirpath string) {
c := make(chan error)
go func() {
filepath.Walk(dirpath, func(fpath string, fileInfo os.FileInfo, err error) error {
if err != nil {
return nil
}
if !fileInfo.IsDir() {
return nil
}
if fileInfo.Name() != "vendor" {
err = parsePackageFromDir(fpath)
if err != nil {
// Send the error to through the channel and continue walking
c <- fmt.Errorf("Error while parsing directory: %s", err.Error())
return nil
}
}
return nil
})
close(c)
}()
for err := range c {
logger.Warnf("%s", err)
}
}
func parsePackageFromDir(path string) error {
fileSet := token.NewFileSet()
folderPkgs, err := parser.ParseDir(fileSet, path, func(info os.FileInfo) bool {
name := info.Name()
return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
}, parser.ParseComments)
if err != nil {
return err
}
for k, v := range folderPkgs {
astPkgs[k] = v
}
return nil
} }
func generateDocs(curpath string) { func generateDocs(curpath string) {
fset := token.NewFileSet() fset := token.NewFileSet()
f, err := parser.ParseFile(fset, path.Join(curpath, "routers", "router.go"), nil, parser.ParseComments) f, err := parser.ParseFile(fset, path.Join(curpath, "routers", "router.go"), nil, parser.ParseComments)
if err != nil { if err != nil {
ColorLog("[ERRO] parse router.go error\n") logger.Fatalf("Error while parsing router.go: %s", err)
os.Exit(2)
} }
rootapi.Infos = swagger.Information{} rootapi.Infos = swagger.Information{}
rootapi.SwaggerVersion = "2.0" rootapi.SwaggerVersion = "2.0"
//analysis API comments
// Analyse API comments
if f.Comments != nil { if f.Comments != nil {
for _, c := range f.Comments { for _, c := range f.Comments {
for _, s := range strings.Split(c.Text(), "\n") { for _, s := range strings.Split(c.Text(), "\n") {
@ -83,21 +155,38 @@ func generateDocs(curpath string) {
rootapi.Infos.TermsOfService = strings.TrimSpace(s[len("@TermsOfServiceUrl"):]) rootapi.Infos.TermsOfService = strings.TrimSpace(s[len("@TermsOfServiceUrl"):])
} else if strings.HasPrefix(s, "@Contact") { } else if strings.HasPrefix(s, "@Contact") {
rootapi.Infos.Contact.EMail = strings.TrimSpace(s[len("@Contact"):]) rootapi.Infos.Contact.EMail = strings.TrimSpace(s[len("@Contact"):])
} else if strings.HasPrefix(s, "@License") { } else if strings.HasPrefix(s, "@Name") {
rootapi.Infos.License.Name = strings.TrimSpace(s[len("@License"):]) 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, "@LicenseUrl") { } else if strings.HasPrefix(s, "@LicenseUrl") {
rootapi.Infos.License.URL = 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, "@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, "@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
// Analyse controller package
for _, im := range f.Imports { for _, im := range f.Imports {
localName := "" localName := ""
if im.Name != nil { if im.Name != nil {
localName = im.Name.Name localName = im.Name.Name
} }
analisyscontrollerPkg(localName, im.Path.Value) analyseControllerPkg(localName, im.Path.Value)
} }
for _, d := range f.Decls { for _, d := range f.Decls {
switch specDecl := d.(type) { switch specDecl := d.(type) {
@ -107,11 +196,11 @@ func generateDocs(curpath string) {
case *ast.AssignStmt: case *ast.AssignStmt:
for _, l := range stmt.Rhs { for _, l := range stmt.Rhs {
if v, ok := l.(*ast.CallExpr); ok { if v, ok := l.(*ast.CallExpr); ok {
// analisys NewNamespace, it will return version and the subfunction // Analyse NewNamespace, it will return version and the subfunction
if selName := v.Fun.(*ast.SelectorExpr).Sel.String(); selName != "NewNamespace" { if selName := v.Fun.(*ast.SelectorExpr).Sel.String(); selName != "NewNamespace" {
continue continue
} }
version, params := analisysNewNamespace(v) version, params := analyseNewNamespace(v)
if rootapi.BasePath == "" && version != "" { if rootapi.BasePath == "" && version != "" {
rootapi.BasePath = version rootapi.BasePath = version
} }
@ -120,12 +209,12 @@ func generateDocs(curpath string) {
case *ast.CallExpr: case *ast.CallExpr:
controllerName := "" controllerName := ""
if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" { if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" {
s, params := analisysNewNamespace(pp) s, params := analyseNewNamespace(pp)
for _, sp := range params { for _, sp := range params {
switch pp := sp.(type) { switch pp := sp.(type) {
case *ast.CallExpr: case *ast.CallExpr:
if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" { if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" {
controllerName = analisysNSInclude(s, pp) controllerName = analyseNSInclude(s, pp)
if v, ok := controllerComments[controllerName]; ok { if v, ok := controllerComments[controllerName]; ok {
rootapi.Tags = append(rootapi.Tags, swagger.Tag{ rootapi.Tags = append(rootapi.Tags, swagger.Tag{
Name: strings.Trim(s, "/"), Name: strings.Trim(s, "/"),
@ -136,7 +225,7 @@ func generateDocs(curpath string) {
} }
} }
} else if selname == "NSInclude" { } else if selname == "NSInclude" {
controllerName = analisysNSInclude("", pp) controllerName = analyseNSInclude("", pp)
if v, ok := controllerComments[controllerName]; ok { if v, ok := controllerComments[controllerName]; ok {
rootapi.Tags = append(rootapi.Tags, swagger.Tag{ rootapi.Tags = append(rootapi.Tags, swagger.Tag{
Name: controllerName, // if the NSInclude has no prefix, we use the controllername as the tag Name: controllerName, // if the NSInclude has no prefix, we use the controllername as the tag
@ -155,22 +244,26 @@ func generateDocs(curpath string) {
} }
os.Mkdir(path.Join(curpath, "swagger"), 0755) os.Mkdir(path.Join(curpath, "swagger"), 0755)
fd, err := os.Create(path.Join(curpath, "swagger", "swagger.json")) fd, err := os.Create(path.Join(curpath, "swagger", "swagger.json"))
fdyml, err := os.Create(path.Join(curpath, "swagger", "swagger.yml"))
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer fdyml.Close()
defer fd.Close() defer fd.Close()
dt, err := json.MarshalIndent(rootapi, "", " ") dt, err := json.MarshalIndent(rootapi, "", " ")
if err != nil { dtyml, erryml := yaml.Marshal(rootapi)
if err != nil || erryml != nil {
panic(err) panic(err)
} }
_, err = fd.Write(dt) _, err = fd.Write(dt)
if err != nil { _, erryml = fdyml.Write(dtyml)
if err != nil || erryml != nil {
panic(err) panic(err)
} }
} }
// return version and the others params // analyseNewNamespace returns version and the others params
func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) { func analyseNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) {
for i, p := range ce.Args { for i, p := range ce.Args {
if i == 0 { if i == 0 {
switch pp := p.(type) { switch pp := p.(type) {
@ -184,7 +277,7 @@ func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) {
return return
} }
func analisysNSInclude(baseurl string, ce *ast.CallExpr) string { func analyseNSInclude(baseurl string, ce *ast.CallExpr) string {
cname := "" cname := ""
for _, p := range ce.Args { for _, p := range ce.Args {
x := p.(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.SelectorExpr) x := p.(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.SelectorExpr)
@ -232,7 +325,7 @@ func analisysNSInclude(baseurl string, ce *ast.CallExpr) string {
return cname return cname
} }
func analisyscontrollerPkg(localName, pkgpath string) { func analyseControllerPkg(localName, pkgpath string) {
pkgpath = strings.Trim(pkgpath, "\"") pkgpath = strings.Trim(pkgpath, "\"")
if isSystemPackage(pkgpath) { if isSystemPackage(pkgpath) {
return return
@ -248,7 +341,7 @@ func analisyscontrollerPkg(localName, pkgpath string) {
} }
gopath := os.Getenv("GOPATH") gopath := os.Getenv("GOPATH")
if gopath == "" { if gopath == "" {
panic("please set gopath") logger.Fatal("GOPATH environment variable is not set or empty")
} }
pkgRealpath := "" pkgRealpath := ""
@ -266,18 +359,16 @@ func analisyscontrollerPkg(localName, pkgpath string) {
} }
pkgCache[pkgpath] = struct{}{} pkgCache[pkgpath] = struct{}{}
} else { } else {
ColorLog("[ERRO] the %s pkg not exist in gopath\n", pkgpath) logger.Fatalf("Package '%s' does not exist in the GOPATH", pkgpath)
os.Exit(1)
} }
fileSet := token.NewFileSet() fileSet := token.NewFileSet()
astPkgs, err := parser.ParseDir(fileSet, pkgRealpath, func(info os.FileInfo) bool { astPkgs, err := parser.ParseDir(fileSet, pkgRealpath, func(info os.FileInfo) bool {
name := info.Name() name := info.Name()
return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
}, parser.ParseComments) }, parser.ParseComments)
if err != nil { if err != nil {
ColorLog("[ERRO] the %s pkg parser.ParseDir error\n", pkgpath) logger.Fatalf("Error while parsing dir at '%s': %s", pkgpath, err)
os.Exit(1)
} }
for _, pkg := range astPkgs { for _, pkg := range astPkgs {
for _, fl := range pkg.Files { for _, fl := range pkg.Files {
@ -286,7 +377,7 @@ func analisyscontrollerPkg(localName, pkgpath string) {
case *ast.FuncDecl: case *ast.FuncDecl:
if specDecl.Recv != nil && len(specDecl.Recv.List) > 0 { if specDecl.Recv != nil && len(specDecl.Recv.List) > 0 {
if t, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr); ok { if t, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr); ok {
// parse controller method // Parse controller method
parserComments(specDecl.Doc, specDecl.Name.String(), fmt.Sprint(t.X), pkgpath) parserComments(specDecl.Doc, specDecl.Name.String(), fmt.Sprint(t.X), pkgpath)
} }
} }
@ -296,7 +387,7 @@ func analisyscontrollerPkg(localName, pkgpath string) {
switch tp := s.(*ast.TypeSpec).Type.(type) { switch tp := s.(*ast.TypeSpec).Type.(type) {
case *ast.StructType: case *ast.StructType:
_ = tp.Struct _ = tp.Struct
//parse controller definition comments // Parse controller definition comments
if strings.TrimSpace(specDecl.Doc.Text()) != "" { if strings.TrimSpace(specDecl.Doc.Text()) != "" {
controllerComments[pkgpath+s.(*ast.TypeSpec).Name.String()] = specDecl.Doc.Text() controllerComments[pkgpath+s.(*ast.TypeSpec).Name.String()] = specDecl.Doc.Text()
} }
@ -310,10 +401,11 @@ func analisyscontrollerPkg(localName, pkgpath string) {
} }
func isSystemPackage(pkgpath string) bool { func isSystemPackage(pkgpath string) bool {
goroot := runtime.GOROOT() goroot := os.Getenv("GOROOT")
if goroot == "" { if goroot == "" {
panic("goroot is empty, do you install Go right?") logger.Fatalf("GOROOT environment variable is not set or empty")
} }
wg, _ := filepath.EvalSymlinks(filepath.Join(goroot, "src", "pkg", pkgpath)) wg, _ := filepath.EvalSymlinks(filepath.Join(goroot, "src", "pkg", pkgpath))
if utils.FileExists(wg) { if utils.FileExists(wg) {
return true return true
@ -379,8 +471,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
ss = strings.TrimSpace(ss[pos:]) ss = strings.TrimSpace(ss[pos:])
schemaName, pos := peekNextSplitString(ss) schemaName, pos := peekNextSplitString(ss)
if schemaName == "" { if schemaName == "" {
ColorLog("[ERRO][%s.%s] Schema must follow {object} or {array}\n", controllerName, funcName) logger.Fatalf("[%s.%s] Schema must follow {object} or {array}", controllerName, funcName)
os.Exit(-1)
} }
if strings.HasPrefix(schemaName, "[]") { if strings.HasPrefix(schemaName, "[]") {
schemaName = schemaName[2:] schemaName = schemaName[2:]
@ -392,13 +483,13 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
schema.Type = typeFormat[0] schema.Type = typeFormat[0]
schema.Format = typeFormat[1] schema.Format = typeFormat[1]
} else { } else {
cmpath, m, mod, realTypes := getModel(schemaName) m, mod, realTypes := getModel(schemaName)
schema.Ref = "#/definitions/" + m schema.Ref = "#/definitions/" + m
if _, ok := modelsList[pkgpath+controllerName]; !ok { if _, ok := modelsList[pkgpath+controllerName]; !ok {
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0) modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0)
} }
modelsList[pkgpath+controllerName][schemaName] = mod modelsList[pkgpath+controllerName][schemaName] = mod
appendModels(cmpath, pkgpath, controllerName, realTypes) appendModels(pkgpath, controllerName, realTypes)
} }
if isArray { if isArray {
rs.Schema = &swagger.Schema{ rs.Schema = &swagger.Schema{
@ -417,7 +508,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
para := swagger.Parameter{} para := swagger.Parameter{}
p := getparams(strings.TrimSpace(t[len("@Param "):])) p := getparams(strings.TrimSpace(t[len("@Param "):]))
if len(p) < 4 { if len(p) < 4 {
panic(controllerName + "_" + funcName + "'s comments @Param at least should has 4 params") logger.Fatal(controllerName + "_" + funcName + "'s comments @Param should have at least 4 params")
} }
para.Name = p[0] para.Name = p[0]
switch p[1] { switch p[1] {
@ -432,13 +523,13 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
case "body": case "body":
break break
default: default:
ColorLog("[WARN][%s.%s] Unknow param location: %s, Possible values are `query`, `header`, `path`, `formData` or `body`.\n", controllerName, funcName, p[1]) logger.Warnf("[%s.%s] Unknown param location: %s. Possible values are `query`, `header`, `path`, `formData` or `body`.\n", controllerName, funcName, p[1])
} }
para.In = p[1] para.In = p[1]
pp := strings.Split(p[2], ".") pp := strings.Split(p[2], ".")
typ := pp[len(pp)-1] typ := pp[len(pp)-1]
if len(pp) >= 2 { if len(pp) >= 2 {
cmpath, m, mod, realTypes := getModel(p[2]) m, mod, realTypes := getModel(p[2])
para.Schema = &swagger.Schema{ para.Schema = &swagger.Schema{
Ref: "#/definitions/" + m, Ref: "#/definitions/" + m,
} }
@ -446,7 +537,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0) modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0)
} }
modelsList[pkgpath+controllerName][typ] = mod modelsList[pkgpath+controllerName][typ] = mod
appendModels(cmpath, pkgpath, controllerName, realTypes) appendModels(pkgpath, controllerName, realTypes)
} else { } else {
isArray := false isArray := false
paraType := "" paraType := ""
@ -463,7 +554,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
paraType = typeFormat[0] paraType = typeFormat[0]
paraFormat = typeFormat[1] paraFormat = typeFormat[1]
} else { } else {
ColorLog("[WARN][%s.%s] Unknow param type: %s\n", controllerName, funcName, typ) logger.Warnf("[%s.%s] Unknown param type: %s\n", controllerName, funcName, typ)
} }
if isArray { if isArray {
para.Type = "array" para.Type = "array"
@ -476,10 +567,15 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
para.Format = paraFormat para.Format = paraFormat
} }
} }
if len(p) > 4 { switch len(p) {
case 5:
para.Required, _ = strconv.ParseBool(p[3]) para.Required, _ = strconv.ParseBool(p[3])
para.Description = strings.Trim(p[4], `" `) para.Description = strings.Trim(p[4], `" `)
} else { case 6:
para.Default = str2RealType(p[3], para.Type)
para.Required, _ = strconv.ParseBool(p[4])
para.Description = strings.Trim(p[5], `" `)
default:
para.Description = strings.Trim(p[3], `" `) para.Description = strings.Trim(p[3], `" `)
} }
opts.Parameters = append(opts.Parameters, para) opts.Parameters = append(opts.Parameters, para)
@ -565,16 +661,12 @@ func getparams(str string) []string {
var j int var j int
var start bool var start bool
var r []string var r []string
for i, c := range []rune(str) { var quoted int8
if unicode.IsSpace(c) { for _, c := range []rune(str) {
if unicode.IsSpace(c) && quoted == 0 {
if !start { if !start {
continue continue
} else { } else {
if j == 3 {
r = append(r, string(s))
r = append(r, strings.TrimSpace((str[i+1:])))
break
}
start = false start = false
j++ j++
r = append(r, string(s)) r = append(r, string(s))
@ -582,28 +674,24 @@ func getparams(str string) []string {
continue continue
} }
} }
start = true start = true
if c == '"' {
quoted ^= 1
continue
}
s = append(s, c) s = append(s, c)
} }
if len(s) > 0 {
r = append(r, string(s))
}
return r return r
} }
func getModel(str string) (pkgpath, objectname string, m swagger.Schema, realTypes []string) { func getModel(str string) (objectname string, m swagger.Schema, realTypes []string) {
strs := strings.Split(str, ".") strs := strings.Split(str, ".")
objectname = strs[len(strs)-1] objectname = strs[len(strs)-1]
pkgpath = strings.Join(strs[:len(strs)-1], "/") packageName := ""
curpath, _ := os.Getwd()
pkgRealpath := path.Join(curpath, pkgpath)
fileSet := token.NewFileSet()
astPkgs, err := parser.ParseDir(fileSet, pkgRealpath, func(info os.FileInfo) bool {
name := info.Name()
return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
}, parser.ParseComments)
if err != nil {
ColorLog("[ERRO] the model %s parser.ParseDir error\n", str)
os.Exit(1)
}
m.Type = "object" m.Type = "object"
for _, pkg := range astPkgs { for _, pkg := range astPkgs {
for _, fl := range pkg.Files { for _, fl := range pkg.Files {
@ -612,28 +700,29 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Schema, realTyp
if k != objectname { if k != objectname {
continue continue
} }
parseObject(d, k, &m, &realTypes, astPkgs) packageName = pkg.Name
parseObject(d, k, &m, &realTypes, astPkgs, pkg.Name)
} }
} }
} }
} }
if m.Title == "" { if m.Title == "" {
ColorLog("[WARN]can't find the object: %s\n", str) logger.Warnf("Cannot find the object: %s", str)
// TODO remove when all type have been supported // TODO remove when all type have been supported
//os.Exit(1) //os.Exit(1)
} }
if len(rootapi.Definitions) == 0 { if len(rootapi.Definitions) == 0 {
rootapi.Definitions = make(map[string]swagger.Schema) rootapi.Definitions = make(map[string]swagger.Schema)
} }
objectname = packageName + "." + objectname
rootapi.Definitions[objectname] = m rootapi.Definitions[objectname] = m
return return
} }
func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string, astPkgs map[string]*ast.Package) { func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string, astPkgs map[string]*ast.Package, packageName string) {
ts, ok := d.Decl.(*ast.TypeSpec) ts, ok := d.Decl.(*ast.TypeSpec)
if !ok { if !ok {
ColorLog("Unknown type without TypeSec: %v\n", d) logger.Fatalf("Unknown type without TypeSec: %v\n", d)
os.Exit(1)
} }
// TODO support other types, such as `ArrayType`, `MapType`, `InterfaceType` etc... // TODO support other types, such as `ArrayType`, `MapType`, `InterfaceType` etc...
st, ok := ts.Type.(*ast.StructType) st, ok := ts.Type.(*ast.StructType)
@ -644,7 +733,18 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string
if st.Fields.List != nil { if st.Fields.List != nil {
m.Properties = make(map[string]swagger.Propertie) m.Properties = make(map[string]swagger.Propertie)
for _, field := range st.Fields.List { for _, field := range st.Fields.List {
realType := ""
isSlice, realType, sType := typeAnalyser(field) isSlice, realType, sType := typeAnalyser(field)
if (isSlice && isBasicType(realType)) || sType == "object" {
if len(strings.Split(realType, " ")) > 1 {
realType = strings.Replace(realType, " ", ".", -1)
realType = strings.Replace(realType, "&", "", -1)
realType = strings.Replace(realType, "{", "", -1)
realType = strings.Replace(realType, "}", "", -1)
} else {
realType = packageName + "." + realType
}
}
*realTypes = append(*realTypes, realType) *realTypes = append(*realTypes, realType)
mp := swagger.Propertie{} mp := swagger.Propertie{}
if isSlice { if isSlice {
@ -661,12 +761,18 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string
} }
} }
} else { } else {
if isBasicType(realType) { if sType == "object" {
mp.Ref = "#/definitions/" + realType
} else if isBasicType(realType) {
typeFormat := strings.Split(sType, ":") typeFormat := strings.Split(sType, ":")
mp.Type = typeFormat[0] mp.Type = typeFormat[0]
mp.Format = typeFormat[1] mp.Format = typeFormat[1]
} else if sType == "object" { } else if realType == "map" {
mp.Ref = "#/definitions/" + realType typeFormat := strings.Split(sType, ":")
mp.AdditionalProperties = &swagger.Propertie{
Type: typeFormat[0],
Format: typeFormat[1],
}
} }
} }
if field.Names != nil { if field.Names != nil {
@ -681,7 +787,21 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string
} }
var tagValues []string var tagValues []string
stag := reflect.StructTag(strings.Trim(field.Tag.Value, "`")) stag := reflect.StructTag(strings.Trim(field.Tag.Value, "`"))
defaultValue := stag.Get("doc")
if defaultValue != "" {
r, _ := regexp.Compile(`default\((.*)\)`)
if r.MatchString(defaultValue) {
res := r.FindStringSubmatch(defaultValue)
mp.Default = str2RealType(res[1], realType)
} else {
logger.Warnf("Invalid default value: %s", defaultValue)
}
}
tag := stag.Get("json") tag := stag.Get("json")
if tag != "" { if tag != "" {
@ -719,7 +839,7 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string
for _, fl := range pkg.Files { for _, fl := range pkg.Files {
for nameOfObj, obj := range fl.Scope.Objects { for nameOfObj, obj := range fl.Scope.Objects {
if obj.Name == fmt.Sprint(field.Type) { if obj.Name == fmt.Sprint(field.Type) {
parseObject(obj, nameOfObj, m, realTypes, astPkgs) parseObject(obj, nameOfObj, m, realTypes, astPkgs, pkg.Name)
} }
} }
} }
@ -746,7 +866,11 @@ func typeAnalyser(f *ast.Field) (isSlice bool, realType, swaggerType string) {
case *ast.StarExpr: case *ast.StarExpr:
return false, fmt.Sprint(t.X), "object" return false, fmt.Sprint(t.X), "object"
case *ast.MapType: case *ast.MapType:
return false, fmt.Sprint(t.Value), "object" val := fmt.Sprintf("%v", t.Value)
if isBasicType(val) {
return false, "map", basicTypes[val]
}
return false, val, "object"
} }
if k, ok := basicTypes[fmt.Sprint(f.Type)]; ok { if k, ok := basicTypes[fmt.Sprint(f.Type)]; ok {
return false, fmt.Sprint(f.Type), k return false, fmt.Sprint(f.Type), k
@ -761,18 +885,6 @@ func isBasicType(Type string) bool {
return false return false
} }
// refer to builtin.go
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 // regexp get json tag
func grepJSONTag(tag string) string { func grepJSONTag(tag string) string {
r, _ := regexp.Compile(`json:"([^"]*)"`) r, _ := regexp.Compile(`json:"([^"]*)"`)
@ -784,23 +896,16 @@ func grepJSONTag(tag string) string {
} }
// append models // append models
func appendModels(cmpath, pkgpath, controllerName string, realTypes []string) { func appendModels(pkgpath, controllerName string, realTypes []string) {
var p string
if cmpath != "" {
p = strings.Join(strings.Split(cmpath, "/"), ".") + "."
} else {
p = ""
}
for _, realType := range realTypes { for _, realType := range realTypes {
if realType != "" && !isBasicType(strings.TrimLeft(realType, "[]")) && if realType != "" && !isBasicType(strings.TrimLeft(realType, "[]")) &&
!strings.HasPrefix(realType, "map") && !strings.HasPrefix(realType, "&") { !strings.HasPrefix(realType, "map") && !strings.HasPrefix(realType, "&") {
if _, ok := modelsList[pkgpath+controllerName][p+realType]; ok { if _, ok := modelsList[pkgpath+controllerName][realType]; ok {
continue continue
} }
//fmt.Printf(pkgpath + ":" + controllerName + ":" + cmpath + ":" + realType + "\n") _, mod, newRealTypes := getModel(realType)
_, _, mod, newRealTypes := getModel(p + realType) modelsList[pkgpath+controllerName][realType] = mod
modelsList[pkgpath+controllerName][p+realType] = mod appendModels(pkgpath, controllerName, newRealTypes)
appendModels(cmpath, pkgpath, controllerName, newRealTypes)
} }
} }
} }
@ -818,3 +923,28 @@ func urlReplace(src string) string {
} }
return strings.Join(pt, "/") return strings.Join(pt, "/")
} }
func str2RealType(s string, typ string) interface{} {
var err error
var ret interface{}
switch typ {
case "int", "int64", "int32", "int16", "int8":
ret, err = strconv.Atoi(s)
case "bool":
ret, err = strconv.ParseBool(s)
case "float64":
ret, err = strconv.ParseFloat(s, 64)
case "float32":
ret, err = strconv.ParseFloat(s, 32)
default:
return s
}
if err != nil {
logger.Warnf("Invalid default value type '%s': %s", typ, s)
return s
}
return ret
}

View File

@ -38,9 +38,7 @@ func generateHproseAppcode(driver, connStr, level, tables, currpath string) {
case "3": case "3":
mode = OModel | OController | ORouter mode = OModel | OController | ORouter
default: default:
ColorLog("[ERRO] Invalid 'level' option: %s\n", level) logger.Fatal("Invalid 'level' option. Level must be either \"1\", \"2\" or \"3\"")
ColorLog("[HINT] Level must be either 1, 2 or 3\n")
os.Exit(2)
} }
var selectedTables map[string]bool var selectedTables map[string]bool
if tables != "" { if tables != "" {
@ -53,12 +51,9 @@ func generateHproseAppcode(driver, connStr, level, tables, currpath string) {
case "mysql": case "mysql":
case "postgres": case "postgres":
case "sqlite": case "sqlite":
ColorLog("[ERRO] Generating app code from SQLite database is not supported yet.\n") logger.Fatal("Generating app code from SQLite database is not supported yet")
os.Exit(2)
default: default:
ColorLog("[ERRO] Unknown database driver: %s\n", driver) logger.Fatalf("Unknown database driver '%s'. Driver must be one of mysql, postgres or sqlite", driver)
ColorLog("[HINT] Driver must be one of mysql, postgres or sqlite\n")
os.Exit(2)
} }
genHprose(driver, connStr, mode, selectedTables, currpath) genHprose(driver, connStr, mode, selectedTables, currpath)
} }
@ -68,12 +63,11 @@ func generateHproseAppcode(driver, connStr, level, tables, currpath string) {
func genHprose(dbms, connStr string, mode byte, selectedTableNames map[string]bool, currpath string) { func genHprose(dbms, connStr string, mode byte, selectedTableNames map[string]bool, currpath string) {
db, err := sql.Open(dbms, connStr) db, err := sql.Open(dbms, connStr)
if err != nil { if err != nil {
ColorLog("[ERRO] Could not connect to %s database: %s, %s\n", dbms, connStr, err) logger.Fatalf("Could not connect to '%s' database using '%s': %s", dbms, connStr, err)
os.Exit(2)
} }
defer db.Close() defer db.Close()
if trans, ok := dbDriver[dbms]; ok { if trans, ok := dbDriver[dbms]; ok {
ColorLog("[INFO] Analyzing database tables...\n") logger.Info("Analyzing database tables...")
tableNames := trans.GetTableNames(db) tableNames := trans.GetTableNames(db)
tables := getTableObjects(tableNames, db, trans) tables := getTableObjects(tableNames, db, trans)
mvcPath := new(MvcPath) mvcPath := new(MvcPath)
@ -82,8 +76,7 @@ func genHprose(dbms, connStr string, mode byte, selectedTableNames map[string]bo
pkgPath := getPackagePath(currpath) pkgPath := getPackagePath(currpath)
writeHproseSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames) writeHproseSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames)
} else { } else {
ColorLog("[ERRO] Generating app code from %s database is not supported yet.\n", dbms) logger.Fatalf("Generating app code from '%s' database is not supported yet", dbms)
os.Exit(2)
} }
} }
@ -92,7 +85,7 @@ func genHprose(dbms, connStr string, mode byte, selectedTableNames map[string]bo
// Newly geneated files will be inside these folders. // Newly geneated files will be inside these folders.
func writeHproseSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) { func writeHproseSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) {
if (OModel & mode) == OModel { if (OModel & mode) == OModel {
ColorLog("[INFO] Creating model files...\n") logger.Info("Creating model files...")
writeHproseModelFiles(tables, paths.ModelPath, selectedTables) writeHproseModelFiles(tables, paths.ModelPath, selectedTables)
} }
} }
@ -113,21 +106,21 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str
var f *os.File var f *os.File
var err error var err error
if isExist(fpath) { if isExist(fpath) {
ColorLog("[WARN] '%v' already exists. Do you want to overwrite it? [Yes|No] ", fpath) logger.Warnf("'%s' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if askForConfirmation() { if askForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil { if err != nil {
ColorLog("[WARN] %v\n", err) logger.Warnf("%s", err)
continue continue
} }
} else { } else {
ColorLog("[WARN] Skipped create file '%s'\n", fpath) logger.Warnf("Skipped create file '%s'", fpath)
continue continue
} }
} else { } else {
f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666) f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666)
if err != nil { if err != nil {
ColorLog("[WARN] %v\n", err) logger.Warnf("%s", err)
continue continue
} }
} }
@ -150,8 +143,7 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str
fileStr = strings.Replace(fileStr, "{{timePkg}}", timePkg, -1) fileStr = strings.Replace(fileStr, "{{timePkg}}", timePkg, -1)
fileStr = strings.Replace(fileStr, "{{importTimePkg}}", importTimePkg, -1) fileStr = strings.Replace(fileStr, "{{importTimePkg}}", importTimePkg, -1)
if _, err := f.WriteString(fileStr); err != nil { if _, err := f.WriteString(fileStr); err != nil {
ColorLog("[ERRO] Could not write model file to '%s'\n", fpath) logger.Fatalf("Could not write model file to '%s'", fpath)
os.Exit(2)
} }
CloseFile(f) CloseFile(f)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")

View File

@ -51,12 +51,12 @@ func (m mysqlDriver) generateSQLFromFields(fields string) string {
for i, v := range fds { for i, v := range fds {
kv := strings.SplitN(v, ":", 2) kv := strings.SplitN(v, ":", 2)
if len(kv) != 2 { if len(kv) != 2 {
ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n") logger.Error("Fields format is wrong. Should be: key:type,key:type " + v)
return "" return ""
} }
typ, tag := m.getSQLType(kv[1]) typ, tag := m.getSQLType(kv[1])
if typ == "" { if typ == "" {
ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n") logger.Error("Fields format is wrong. Should be: key:type,key:type " + v)
return "" return ""
} }
if i == 0 && strings.ToLower(kv[0]) != "id" { if i == 0 && strings.ToLower(kv[0]) != "id" {
@ -120,16 +120,16 @@ func (m postgresqlDriver) generateSQLFromFields(fields string) string {
for i, v := range fds { for i, v := range fds {
kv := strings.SplitN(v, ":", 2) kv := strings.SplitN(v, ":", 2)
if len(kv) != 2 { if len(kv) != 2 {
ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n") logger.Error("Fields format is wrong. Should be: key:type,key:type " + v)
return "" return ""
} }
typ, tag := m.getSQLType(kv[1]) typ, tag := m.getSQLType(kv[1])
if typ == "" { if typ == "" {
ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n") logger.Error("Fields format is wrong. Should be: key:type,key:type " + v)
return "" return ""
} }
if i == 0 && strings.ToLower(kv[0]) != "id" { if i == 0 && strings.ToLower(kv[0]) != "id" {
sql += "id interger serial primary key," sql += "id serial primary key,"
} }
sql += snakeString(kv[0]) + " " + typ + "," sql += snakeString(kv[0]) + " " + typ + ","
if tag != "" { if tag != "" {
@ -177,7 +177,8 @@ func newDBDriver() DBDriver {
case "postgres": case "postgres":
return postgresqlDriver{} return postgresqlDriver{}
default: default:
panic("driver not supported") logger.Fatal("Driver not supported")
return nil
} }
} }
@ -190,8 +191,7 @@ func generateMigration(mname, upsql, downsql, curpath string) {
if _, err := os.Stat(migrationFilePath); os.IsNotExist(err) { if _, err := os.Stat(migrationFilePath); os.IsNotExist(err) {
// create migrations directory // create migrations directory
if err := os.MkdirAll(migrationFilePath, 0777); err != nil { if err := os.MkdirAll(migrationFilePath, 0777); err != nil {
ColorLog("[ERRO] Could not create migration directory: %s\n", err) logger.Fatalf("Could not create migration directory: %s", err)
os.Exit(2)
} }
} }
// create file // create file
@ -208,8 +208,7 @@ func generateMigration(mname, upsql, downsql, curpath string) {
formatSourceCode(fpath) formatSourceCode(fpath)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
} else { } else {
ColorLog("[ERRO] Could not create migration file: %s\n", err) logger.Fatalf("Could not create migration file: %s", err)
os.Exit(2)
} }
} }

View File

@ -35,19 +35,17 @@ func generateModel(mname, fields, currpath string) {
modelStruct, hastime, err := getStruct(modelName, fields) modelStruct, hastime, err := getStruct(modelName, fields)
if err != nil { if err != nil {
ColorLog("[ERRO] Could not generate the model struct: %s\n", err) logger.Fatalf("Could not generate the model struct: %s", err)
os.Exit(2)
} }
ColorLog("[INFO] Using '%s' as model name\n", modelName) logger.Infof("Using '%s' as model name", modelName)
ColorLog("[INFO] Using '%s' as package name\n", packageName) logger.Infof("Using '%s' as package name", packageName)
fp := path.Join(currpath, "models", p) fp := path.Join(currpath, "models", p)
if _, err := os.Stat(fp); os.IsNotExist(err) { if _, err := os.Stat(fp); os.IsNotExist(err) {
// Create the model's directory // Create the model's directory
if err := os.MkdirAll(fp, 0777); err != nil { if err := os.MkdirAll(fp, 0777); err != nil {
ColorLog("[ERRO] Could not create the model directory: %s\n", err) logger.Fatalf("Could not create the model directory: %s", err)
os.Exit(2)
} }
} }
@ -67,8 +65,7 @@ func generateModel(mname, fields, currpath string) {
formatSourceCode(fpath) formatSourceCode(fpath)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
} else { } else {
ColorLog("[ERRO] Could not create model file: %s\n", err) logger.Fatalf("Could not create model file: %s", err)
os.Exit(2)
} }
} }

View File

@ -3,7 +3,7 @@ package main
import "strings" import "strings"
func generateScaffold(sname, fields, currpath, driver, conn string) { func generateScaffold(sname, fields, currpath, driver, conn string) {
ColorLog("[INFO] Do you want to create a '%v' model? [Yes|No] ", sname) logger.Infof("Do you want to create a '%s' model? [Yes|No] ", sname)
// Generate the model // Generate the model
if askForConfirmation() { if askForConfirmation() {
@ -11,19 +11,19 @@ func generateScaffold(sname, fields, currpath, driver, conn string) {
} }
// Generate the controller // Generate the controller
ColorLog("[INFO] Do you want to create a '%v' controller? [Yes|No] ", sname) logger.Infof("Do you want to create a '%s' controller? [Yes|No] ", sname)
if askForConfirmation() { if askForConfirmation() {
generateController(sname, currpath) generateController(sname, currpath)
} }
// Generate the views // Generate the views
ColorLog("[INFO] Do you want to create views for this '%v' resource? [Yes|No] ", sname) logger.Infof("Do you want to create views for this '%s' resource? [Yes|No] ", sname)
if askForConfirmation() { if askForConfirmation() {
generateView(sname, currpath) generateView(sname, currpath)
} }
// Generate a migration // Generate a migration
ColorLog("[INFO] Do you want to create a '%v' migration and schema for this resource? [Yes|No] ", sname) logger.Infof("Do you want to create a '%s' migration and schema for this resource? [Yes|No] ", sname)
if askForConfirmation() { if askForConfirmation() {
upsql := "" upsql := ""
downsql := "" downsql := ""
@ -40,9 +40,9 @@ func generateScaffold(sname, fields, currpath, driver, conn string) {
} }
// Run the migration // Run the migration
ColorLog("[INFO] Do you want to migrate the database? [Yes|No] ") logger.Infof("Do you want to migrate the database? [Yes|No] ")
if askForConfirmation() { if askForConfirmation() {
migrateUpdate(currpath, 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)) logger.Successf("All done! Don't forget to add beego.Router(\"/%s\" ,&controllers.%sController{}) to routers/route.go\n", sname, strings.Title(sname))
} }

View File

@ -25,13 +25,12 @@ import (
func generateView(viewpath, currpath string) { func generateView(viewpath, currpath string) {
w := NewColorWriter(os.Stdout) w := NewColorWriter(os.Stdout)
ColorLog("[INFO] Generating view...\n") logger.Info("Generating view...")
absViewPath := path.Join(currpath, "views", viewpath) absViewPath := path.Join(currpath, "views", viewpath)
err := os.MkdirAll(absViewPath, os.ModePerm) err := os.MkdirAll(absViewPath, os.ModePerm)
if err != nil { if err != nil {
ColorLog("[ERRO] Could not create '%s' view: %s\n", viewpath, err) logger.Fatalf("Could not create '%s' view: %s", viewpath, err)
os.Exit(2)
} }
cfile := path.Join(absViewPath, "index.tpl") cfile := path.Join(absViewPath, "index.tpl")
@ -40,8 +39,7 @@ func generateView(viewpath, currpath string) {
f.WriteString(cfile) f.WriteString(cfile)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m")
} else { } else {
ColorLog("[ERRO] Could not create view file: %s\n", err) logger.Fatalf("Could not create view file: %s", err)
os.Exit(2)
} }
cfile = path.Join(absViewPath, "show.tpl") cfile = path.Join(absViewPath, "show.tpl")
@ -50,8 +48,7 @@ func generateView(viewpath, currpath string) {
f.WriteString(cfile) f.WriteString(cfile)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m")
} else { } else {
ColorLog("[ERRO] Could not create view file: %s\n", err) logger.Fatalf("Could not create view file: %s", err)
os.Exit(2)
} }
cfile = path.Join(absViewPath, "create.tpl") cfile = path.Join(absViewPath, "create.tpl")
@ -60,8 +57,7 @@ func generateView(viewpath, currpath string) {
f.WriteString(cfile) f.WriteString(cfile)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m")
} else { } else {
ColorLog("[ERRO] Could not create view file: %s\n", err) logger.Fatalf("Could not create view file: %s", err)
os.Exit(2)
} }
cfile = path.Join(absViewPath, "edit.tpl") cfile = path.Join(absViewPath, "edit.tpl")
@ -70,7 +66,6 @@ func generateView(viewpath, currpath string) {
f.WriteString(cfile) f.WriteString(cfile)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m")
} else { } else {
ColorLog("[ERRO] Could not create view file: %s\n", err) logger.Fatalf("Could not create view file: %s", err)
os.Exit(2)
} }
} }

View File

@ -10,7 +10,7 @@
* * * *
* Build rpc application use Hprose base on beego * * Build rpc application use Hprose base on beego *
* * * *
* LastModified: Oct 13, 2014 * * LastModified: Oct 31, 2016 *
* Author: Liu jian <laoliu@lanmv.com> * * Author: Liu jian <laoliu@lanmv.com> *
* * * *
\**********************************************************/ \**********************************************************/
@ -63,16 +63,41 @@ EnableDocs = true
var hproseMaingo = `package main var hproseMaingo = `package main
import ( import (
"fmt"
"reflect"
"{{.Appname}}/models" "{{.Appname}}/models"
"github.com/hprose/hprose-go/hprose" "github.com/hprose/hprose-golang/rpc"
"github.com/astaxie/beego" "github.com/astaxie/beego"
) )
func logInvokeHandler(
name string,
args []reflect.Value,
context rpc.Context,
next rpc.NextInvokeHandler) (results []reflect.Value, err error) {
fmt.Printf("%s(%v) = ", name, args)
results, err = next(name, args, context)
fmt.Printf("%v %v\r\n", results, err)
return
}
func main() { func main() {
service := hprose.NewHttpService() // Create WebSocketServer
// service := rpc.NewWebSocketService()
// Create Http Server
service := rpc.NewHTTPService()
// Use Logger Middleware
service.AddInvokeHandler(logInvokeHandler)
// Publish Functions
service.AddFunction("AddOne", models.AddOne) service.AddFunction("AddOne", models.AddOne)
service.AddFunction("GetOne", models.GetOne) service.AddFunction("GetOne", models.GetOne)
// Start Service
beego.Handler("/", service) beego.Handler("/", service)
beego.Run() beego.Run()
} }
@ -81,8 +106,11 @@ func main() {
var hproseMainconngo = `package main var hproseMainconngo = `package main
import ( import (
"fmt"
"reflect"
"{{.Appname}}/models" "{{.Appname}}/models"
"github.com/hprose/hprose-go/hprose" "github.com/hprose/hprose-golang/rpc"
"github.com/astaxie/beego" "github.com/astaxie/beego"
"github.com/astaxie/beego/orm" "github.com/astaxie/beego/orm"
@ -93,9 +121,30 @@ func init() {
orm.RegisterDataBase("default", "{{.DriverName}}", "{{.conn}}") orm.RegisterDataBase("default", "{{.DriverName}}", "{{.conn}}")
} }
func logInvokeHandler(
name string,
args []reflect.Value,
context rpc.Context,
next rpc.NextInvokeHandler) (results []reflect.Value, err error) {
fmt.Printf("%s(%v) = ", name, args)
results, err = next(name, args, context)
fmt.Printf("%v %v\r\n", results, err)
return
}
func main() { func main() {
service := hprose.NewHttpService() // Create WebSocketServer
// service := rpc.NewWebSocketService()
// Create Http Server
service := rpc.NewHTTPService()
// Use Logger Middleware
service.AddInvokeHandler(logInvokeHandler)
{{HproseFunctionList}} {{HproseFunctionList}}
// Start Service
beego.Handler("/", service) beego.Handler("/", service)
beego.Run() beego.Run()
} }
@ -249,14 +298,13 @@ var hproseAddFunctions = []string{}
func init() { func init() {
cmdHproseapp.Run = createhprose cmdHproseapp.Run = createhprose
cmdHproseapp.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
cmdHproseapp.Flag.Var(&tables, "tables", "specify tables to generate model") cmdHproseapp.Flag.Var(&tables, "tables", "specify tables to generate model")
cmdHproseapp.Flag.Var(&driver, "driver", "database driver: mysql, postgresql, etc.") cmdHproseapp.Flag.Var(&driver, "driver", "database driver: mysql, postgresql, etc.")
cmdHproseapp.Flag.Var(&conn, "conn", "connection string used by the driver to connect to a database instance") cmdHproseapp.Flag.Var(&conn, "conn", "connection string used by the driver to connect to a database instance")
} }
func createhprose(cmd *Command, args []string) int { func createhprose(cmd *Command, args []string) int {
ShowShortVersionBanner()
w := NewColorWriter(os.Stdout) w := NewColorWriter(os.Stdout)
curpath, _ := os.Getwd() curpath, _ := os.Getwd()
@ -265,8 +313,7 @@ func createhprose(cmd *Command, args []string) int {
} }
apppath, packpath, err := checkEnv(args[0]) apppath, packpath, err := checkEnv(args[0])
if err != nil { if err != nil {
fmt.Println(err) logger.Fatalf("%s", err)
os.Exit(2)
} }
if driver == "" { if driver == "" {
driver = "mysql" driver = "mysql"
@ -274,7 +321,7 @@ func createhprose(cmd *Command, args []string) int {
if conn == "" { if conn == "" {
} }
ColorLog("[INFO] Creating Hprose application...\n") logger.Info("Creating Hprose application...")
os.MkdirAll(apppath, 0755) os.MkdirAll(apppath, 0755)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath, "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath, "\x1b[0m")
@ -285,9 +332,9 @@ func createhprose(cmd *Command, args []string) int {
strings.Replace(hproseconf, "{{.Appname}}", args[0], -1)) strings.Replace(hproseconf, "{{.Appname}}", args[0], -1))
if conn != "" { if conn != "" {
ColorLog("[INFO] Using '%s' as 'driver'\n", driver) logger.Infof("Using '%s' as 'driver'", driver)
ColorLog("[INFO] Using '%s' as 'conn'\n", conn) logger.Infof("Using '%s' as 'conn'", conn)
ColorLog("[INFO] Using '%s' as 'tables'\n", tables) logger.Infof("Using '%s' as 'tables'", tables)
generateHproseAppcode(string(driver), string(conn), "1", string(tables), path.Join(curpath, args[0])) generateHproseAppcode(string(driver), string(conn), "1", string(tables), path.Join(curpath, args[0]))
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
maingoContent := strings.Replace(hproseMainconngo, "{{.Appname}}", packpath, -1) maingoContent := strings.Replace(hproseMainconngo, "{{.Appname}}", packpath, -1)
@ -320,6 +367,6 @@ func createhprose(cmd *Command, args []string) int {
WriteToFile(path.Join(apppath, "main.go"), WriteToFile(path.Join(apppath, "main.go"),
strings.Replace(hproseMaingo, "{{.Appname}}", packpath, -1)) strings.Replace(hproseMaingo, "{{.Appname}}", packpath, -1))
} }
ColorLog("[SUCC] New Hprose application successfully created!\n") logger.Success("New Hprose application successfully created!")
return 0 return 0
} }

265
logger.go Normal file
View File

@ -0,0 +1,265 @@
// 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"
"io"
"os"
"path/filepath"
"sync"
"sync/atomic"
"text/template"
)
var errInvalidLogLevel = errors.New("logger: invalid log level")
const (
levelCritical = iota
levelFatal
levelSuccess
levelHint
levelDebug
levelInfo
levelWarn
levelError
)
var (
sequenceNo uint64
instance *BeeLogger
once sync.Once
)
// BeeLogger logs logging records to the specified io.Writer
type BeeLogger struct {
mu sync.Mutex
output io.Writer
}
// LogRecord represents a log record and contains the timestamp when the record
// was created, an increasing id, level and the actual formatted log line.
type LogRecord struct {
ID string
Level string
Message string
Filename string
LineNo int
}
var (
logRecordTemplate *template.Template
debugLogRecordTemplate *template.Template
)
// GetBeeLogger initializes the logger instance with a NewColorWriter output
// and returns a singleton
func GetBeeLogger(w io.Writer) *BeeLogger {
once.Do(func() {
var (
err error
simpleLogFormat = `{{Now "2006/01/02 15:04:05"}} {{.Level}}{{.ID}} {{.Message}}{{EndLine}}`
debugLogFormat = `{{Now "2006/01/02 15:04:05"}} {{.Level}}{{.ID}} {{.Filename}}:{{.LineNo}} {{.Message}}{{EndLine}}`
)
// Initialize and parse logging templates
funcs := template.FuncMap{
"Now": Now,
"EndLine": EndLine,
}
logRecordTemplate, err = template.New("simpleLogFormat").Funcs(funcs).Parse(simpleLogFormat)
MustCheck(err)
debugLogRecordTemplate, err = template.New("debugLogFormat").Funcs(funcs).Parse(debugLogFormat)
MustCheck(err)
instance = &BeeLogger{output: NewColorWriter(w)}
})
return instance
}
// SetOutput sets the logger output destination
func (l *BeeLogger) SetOutput(w io.Writer) {
l.mu.Lock()
defer l.mu.Unlock()
l.output = NewColorWriter(w)
}
func (l *BeeLogger) getLevelTag(level int) string {
switch level {
case levelFatal:
return "FATAL "
case levelSuccess:
return "SUCCESS "
case levelHint:
return "HINT "
case levelDebug:
return "DEBUG "
case levelInfo:
return "INFO "
case levelWarn:
return "WARN "
case levelError:
return "ERROR "
case levelCritical:
return "CRITICAL"
default:
panic(errInvalidLogLevel)
}
}
func (l *BeeLogger) getColorLevel(level int) string {
switch level {
case levelCritical:
return RedBold(l.getLevelTag(level))
case levelFatal:
return RedBold(l.getLevelTag(level))
case levelInfo:
return BlueBold(l.getLevelTag(level))
case levelHint:
return CyanBold(l.getLevelTag(level))
case levelDebug:
return YellowBold(l.getLevelTag(level))
case levelError:
return RedBold(l.getLevelTag(level))
case levelWarn:
return YellowBold(l.getLevelTag(level))
case levelSuccess:
return GreenBold(l.getLevelTag(level))
default:
panic(errInvalidLogLevel)
}
}
// mustLog logs the message according to the specified level and arguments.
// It panics in case of an error.
func (l *BeeLogger) mustLog(level int, message string, args ...interface{}) {
// Acquire the lock
l.mu.Lock()
defer l.mu.Unlock()
// Create the logging record and pass into the output
record := LogRecord{
ID: fmt.Sprintf("%04d", atomic.AddUint64(&sequenceNo, 1)),
Level: l.getColorLevel(level),
Message: fmt.Sprintf(message, args...),
}
err := logRecordTemplate.Execute(l.output, record)
MustCheck(err)
}
// mustLogDebug logs a debug message only if debug mode
// is enabled. i.e. DEBUG_ENABLED="1"
func (l *BeeLogger) mustLogDebug(message string, file string, line int, args ...interface{}) {
if !IsDebugEnabled() {
return
}
// Change the output to Stderr
l.SetOutput(os.Stderr)
// Create the log record
record := LogRecord{
ID: fmt.Sprintf("%04d", atomic.AddUint64(&sequenceNo, 1)),
Level: l.getColorLevel(levelDebug),
Message: fmt.Sprintf(message, args...),
LineNo: line,
Filename: filepath.Base(file),
}
err := debugLogRecordTemplate.Execute(l.output, record)
MustCheck(err)
}
// Debug outputs a debug log message
func (l *BeeLogger) Debug(message string, file string, line int) {
l.mustLogDebug(message, file, line)
}
// Debugf outputs a formatted debug log message
func (l *BeeLogger) Debugf(message string, file string, line int, vars ...interface{}) {
l.mustLogDebug(message, file, line, vars...)
}
// Info outputs an information log message
func (l *BeeLogger) Info(message string) {
l.mustLog(levelInfo, message)
}
// Infof outputs a formatted information log message
func (l *BeeLogger) Infof(message string, vars ...interface{}) {
l.mustLog(levelInfo, message, vars...)
}
// Warn outputs a warning log message
func (l *BeeLogger) Warn(message string) {
l.mustLog(levelWarn, message)
}
// Warnf outputs a formatted warning log message
func (l *BeeLogger) Warnf(message string, vars ...interface{}) {
l.mustLog(levelWarn, message, vars...)
}
// Error outputs an error log message
func (l *BeeLogger) Error(message string) {
l.mustLog(levelError, message)
}
// Errorf outputs a formatted error log message
func (l *BeeLogger) Errorf(message string, vars ...interface{}) {
l.mustLog(levelError, message, vars...)
}
// Fatal outputs a fatal log message and exists
func (l *BeeLogger) Fatal(message string) {
l.mustLog(levelFatal, message)
os.Exit(255)
}
// Fatalf outputs a formatted log message and exists
func (l *BeeLogger) Fatalf(message string, vars ...interface{}) {
l.mustLog(levelFatal, message, vars...)
os.Exit(255)
}
// Success outputs a success log message
func (l *BeeLogger) Success(message string) {
l.mustLog(levelSuccess, message)
}
// Successf outputs a formatted success log message
func (l *BeeLogger) Successf(message string, vars ...interface{}) {
l.mustLog(levelSuccess, message, vars...)
}
// Hint outputs a hint log message
func (l *BeeLogger) Hint(message string) {
l.mustLog(levelHint, message)
}
// Hintf outputs a formatted hint log message
func (l *BeeLogger) Hintf(message string, vars ...interface{}) {
l.mustLog(levelHint, message, vars...)
}
// Critical outputs a critical log message
func (l *BeeLogger) Critical(message string) {
l.mustLog(levelCritical, message)
}
// Criticalf outputs a formatted critical log message
func (l *BeeLogger) Criticalf(message string, vars ...interface{}) {
l.mustLog(levelCritical, message, vars...)
}

View File

@ -16,10 +16,10 @@ package main
import ( import (
"database/sql" "database/sql"
"fmt"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -56,30 +56,31 @@ var mConn docValue
func init() { func init() {
cmdMigrate.Run = runMigration cmdMigrate.Run = runMigration
cmdMigrate.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
cmdMigrate.Flag.Var(&mDriver, "driver", "database driver: mysql, postgres, sqlite, 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") 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 // runMigration is the entry point for starting a migration
func runMigration(cmd *Command, args []string) int { func runMigration(cmd *Command, args []string) int {
ShowShortVersionBanner()
currpath, _ := os.Getwd() currpath, _ := os.Getwd()
gps := GetGOPATHs() gps := GetGOPATHs()
if len(gps) == 0 { if len(gps) == 0 {
ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty") logger.Fatal("GOPATH environment variable is not set or empty")
os.Exit(2)
} }
gopath := gps[0]
Debugf("GOPATH: %s", gopath)
// load config gopath := gps[0]
logger.Debugf("GOPATH: %s", __FILE__(), __LINE__(), gopath)
// Load the configuration
err := loadConfig() err := loadConfig()
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err) logger.Errorf("Failed to load configuration: %s", err)
} }
// getting command line arguments
// Getting command line arguments
if len(args) != 0 { if len(args) != 0 {
cmd.Flag.Parse(args[1:]) cmd.Flag.Parse(args[1:])
} }
@ -95,31 +96,30 @@ func runMigration(cmd *Command, args []string) int {
mConn = "root:@tcp(127.0.0.1:3306)/test" mConn = "root:@tcp(127.0.0.1:3306)/test"
} }
} }
ColorLog("[INFO] Using '%s' as 'driver'\n", mDriver) logger.Infof("Using '%s' as 'driver'", mDriver)
ColorLog("[INFO] Using '%s' as 'conn'\n", mConn) logger.Infof("Using '%s' as 'conn'", mConn)
driverStr, connStr := string(mDriver), string(mConn) driverStr, connStr := string(mDriver), string(mConn)
if len(args) == 0 { if len(args) == 0 {
// run all outstanding migrations // run all outstanding migrations
ColorLog("[INFO] Running all outstanding migrations\n") logger.Info("Running all outstanding migrations")
migrateUpdate(currpath, driverStr, connStr) migrateUpdate(currpath, driverStr, connStr)
} else { } else {
mcmd := args[0] mcmd := args[0]
switch mcmd { switch mcmd {
case "rollback": case "rollback":
ColorLog("[INFO] Rolling back the last migration operation\n") logger.Info("Rolling back the last migration operation")
migrateRollback(currpath, driverStr, connStr) migrateRollback(currpath, driverStr, connStr)
case "reset": case "reset":
ColorLog("[INFO] Reseting all migrations\n") logger.Info("Reseting all migrations")
migrateReset(currpath, driverStr, connStr) migrateReset(currpath, driverStr, connStr)
case "refresh": case "refresh":
ColorLog("[INFO] Refreshing all migrations\n") logger.Info("Refreshing all migrations")
migrateRefresh(currpath, driverStr, connStr) migrateRefresh(currpath, driverStr, connStr)
default: default:
ColorLog("[ERRO] Command is missing\n") logger.Fatal("Command is missing")
os.Exit(2)
} }
} }
ColorLog("[SUCC] Migration successful!\n") logger.Success("Migration successful!")
return 0 return 0
} }
@ -146,16 +146,20 @@ func migrateRefresh(currpath, driver, connStr string) {
// migrate generates source code, build it, and invoke the binary who does the actual migration // migrate generates source code, build it, and invoke the binary who does the actual migration
func migrate(goal, currpath, driver, connStr string) { func migrate(goal, currpath, driver, connStr string) {
dir := path.Join(currpath, "database", "migrations") dir := path.Join(currpath, "database", "migrations")
binary := "m" postfix := ""
if runtime.GOOS == "windows" {
postfix = ".exe"
}
binary := "m" + postfix
source := binary + ".go" source := binary + ".go"
// connect to database
// Connect to database
db, err := sql.Open(driver, connStr) db, err := sql.Open(driver, connStr)
if err != nil { if err != nil {
ColorLog("[ERRO] Could not connect to %s: %s\n", driver, connStr) logger.Fatalf("Could not connect to database using '%s': %s", connStr, err)
ColorLog("[ERRO] Error: %v", err.Error())
os.Exit(2)
} }
defer db.Close() defer db.Close()
checkForSchemaUpdateTable(db, driver) checkForSchemaUpdateTable(db, driver)
latestName, latestTime := getLatestMigration(db, goal) latestName, latestTime := getLatestMigration(db, goal)
writeMigrationSourceFile(dir, source, driver, connStr, latestTime, latestName, goal) writeMigrationSourceFile(dir, source, driver, connStr, latestTime, latestName, goal)
@ -170,50 +174,44 @@ func migrate(goal, currpath, driver, connStr string) {
func checkForSchemaUpdateTable(db *sql.DB, driver string) { func checkForSchemaUpdateTable(db *sql.DB, driver string) {
showTableSQL := showMigrationsTableSQL(driver) showTableSQL := showMigrationsTableSQL(driver)
if rows, err := db.Query(showTableSQL); err != nil { if rows, err := db.Query(showTableSQL); err != nil {
ColorLog("[ERRO] Could not show migrations table: %s\n", err) logger.Fatalf("Could not show migrations table: %s", err)
os.Exit(2)
} else if !rows.Next() { } else if !rows.Next() {
// no migrations table, create anew // No migrations table, create new ones
createTableSQL := createMigrationsTableSQL(driver) createTableSQL := createMigrationsTableSQL(driver)
ColorLog("[INFO] Creating 'migrations' table...\n")
logger.Infof("Creating 'migrations' table...")
if _, err := db.Query(createTableSQL); err != nil { if _, err := db.Query(createTableSQL); err != nil {
ColorLog("[ERRO] Could not create migrations table: %s\n", err) logger.Fatalf("Could not create migrations table: %s", err)
os.Exit(2)
} }
} }
// checking that migrations table schema are expected // Checking that migrations table schema are expected
selectTableSQL := selectMigrationsTableSQL(driver) selectTableSQL := selectMigrationsTableSQL(driver)
if rows, err := db.Query(selectTableSQL); err != nil { if rows, err := db.Query(selectTableSQL); err != nil {
ColorLog("[ERRO] Could not show columns of migrations table: %s\n", err) logger.Fatalf("Could not show columns of migrations table: %s", err)
os.Exit(2)
} else { } else {
for rows.Next() { for rows.Next() {
var fieldBytes, typeBytes, nullBytes, keyBytes, defaultBytes, extraBytes []byte var fieldBytes, typeBytes, nullBytes, keyBytes, defaultBytes, extraBytes []byte
if err := rows.Scan(&fieldBytes, &typeBytes, &nullBytes, &keyBytes, &defaultBytes, &extraBytes); err != nil { if err := rows.Scan(&fieldBytes, &typeBytes, &nullBytes, &keyBytes, &defaultBytes, &extraBytes); err != nil {
ColorLog("[ERRO] Could not read column information: %s\n", err) logger.Fatalf("Could not read column information: %s", err)
os.Exit(2)
} }
fieldStr, typeStr, nullStr, keyStr, defaultStr, extraStr := fieldStr, typeStr, nullStr, keyStr, defaultStr, extraStr :=
string(fieldBytes), string(typeBytes), string(nullBytes), string(keyBytes), string(defaultBytes), string(extraBytes) string(fieldBytes), string(typeBytes), string(nullBytes), string(keyBytes), string(defaultBytes), string(extraBytes)
if fieldStr == "id_migration" { if fieldStr == "id_migration" {
if keyStr != "PRI" || extraStr != "auto_increment" { if keyStr != "PRI" || extraStr != "auto_increment" {
ColorLog("[ERRO] Column migration.id_migration type mismatch: KEY: %s, EXTRA: %s\n", keyStr, extraStr) logger.Hint("Expecting KEY: PRI, EXTRA: auto_increment")
ColorLog("[HINT] Expecting KEY: PRI, EXTRA: auto_increment\n") logger.Fatalf("Column migration.id_migration type mismatch: KEY: %s, EXTRA: %s", keyStr, extraStr)
os.Exit(2)
} }
} else if fieldStr == "name" { } else if fieldStr == "name" {
if !strings.HasPrefix(typeStr, "varchar") || nullStr != "YES" { if !strings.HasPrefix(typeStr, "varchar") || nullStr != "YES" {
ColorLog("[ERRO] Column migration.name type mismatch: TYPE: %s, NULL: %s\n", typeStr, nullStr) logger.Hint("Expecting TYPE: varchar, NULL: YES")
ColorLog("[HINT] Expecting TYPE: varchar, NULL: YES\n") logger.Fatalf("Column migration.name type mismatch: TYPE: %s, NULL: %s", typeStr, nullStr)
os.Exit(2)
} }
} else if fieldStr == "created_at" { } else if fieldStr == "created_at" {
if typeStr != "timestamp" || defaultStr != "CURRENT_TIMESTAMP" { if typeStr != "timestamp" || defaultStr != "CURRENT_TIMESTAMP" {
ColorLog("[ERRO] Column migration.timestamp type mismatch: TYPE: %s, DEFAULT: %s\n", typeStr, defaultStr) logger.Hint("Expecting TYPE: timestamp, DEFAULT: CURRENT_TIMESTAMP")
ColorLog("[HINT] Expecting TYPE: timestamp, DEFAULT: CURRENT_TIMESTAMP\n") logger.Fatalf("Column migration.timestamp type mismatch: TYPE: %s, DEFAULT: %s", typeStr, defaultStr)
os.Exit(2)
} }
} }
} }
@ -257,26 +255,22 @@ func selectMigrationsTableSQL(driver string) string {
func getLatestMigration(db *sql.DB, goal string) (file string, createdAt int64) { 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" sql := "SELECT name FROM migrations where status = 'update' ORDER BY id_migration DESC LIMIT 1"
if rows, err := db.Query(sql); err != nil { if rows, err := db.Query(sql); err != nil {
ColorLog("[ERRO] Could not retrieve migrations: %s\n", err) logger.Fatalf("Could not retrieve migrations: %s", err)
os.Exit(2)
} else { } else {
if rows.Next() { if rows.Next() {
if err := rows.Scan(&file); err != nil { if err := rows.Scan(&file); err != nil {
ColorLog("[ERRO] Could not read migrations in database: %s\n", err) logger.Fatalf("Could not read migrations in database: %s", err)
os.Exit(2)
} }
createdAtStr := file[len(file)-15:] createdAtStr := file[len(file)-15:]
if t, err := time.Parse("20060102_150405", createdAtStr); err != nil { if t, err := time.Parse("20060102_150405", createdAtStr); err != nil {
ColorLog("[ERRO] Could not parse time: %s\n", err) logger.Fatalf("Could not parse time: %s", err)
os.Exit(2)
} else { } else {
createdAt = t.Unix() createdAt = t.Unix()
} }
} else { } else {
// migration table has no 'update' record, no point rolling back // migration table has no 'update' record, no point rolling back
if goal == "rollback" { if goal == "rollback" {
ColorLog("[ERRO] There is nothing to rollback\n") logger.Fatal("There is nothing to rollback")
os.Exit(2)
} }
file, createdAt = "", 0 file, createdAt = "", 0
} }
@ -288,8 +282,7 @@ func getLatestMigration(db *sql.DB, goal string) (file string, createdAt int64)
func writeMigrationSourceFile(dir, source, driver, connStr string, latestTime int64, latestName string, task string) { func writeMigrationSourceFile(dir, source, driver, connStr string, latestTime int64, latestName string, task string) {
changeDir(dir) changeDir(dir)
if f, err := os.OpenFile(source, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err != nil { if f, err := os.OpenFile(source, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err != nil {
ColorLog("[ERRO] Could not create file: %s\n", err) logger.Fatalf("Could not create file: %s", err)
os.Exit(2)
} else { } else {
content := strings.Replace(MigrationMainTPL, "{{DBDriver}}", driver, -1) content := strings.Replace(MigrationMainTPL, "{{DBDriver}}", driver, -1)
content = strings.Replace(content, "{{ConnStr}}", connStr, -1) content = strings.Replace(content, "{{ConnStr}}", connStr, -1)
@ -297,8 +290,7 @@ func writeMigrationSourceFile(dir, source, driver, connStr string, latestTime in
content = strings.Replace(content, "{{LatestName}}", latestName, -1) content = strings.Replace(content, "{{LatestName}}", latestName, -1)
content = strings.Replace(content, "{{Task}}", task, -1) content = strings.Replace(content, "{{Task}}", task, -1)
if _, err := f.WriteString(content); err != nil { if _, err := f.WriteString(content); err != nil {
ColorLog("[ERRO] Could not write to file: %s\n", err) logger.Fatalf("Could not write to file: %s", err)
os.Exit(2)
} }
CloseFile(f) CloseFile(f)
} }
@ -309,7 +301,7 @@ func buildMigrationBinary(dir, binary string) {
changeDir(dir) changeDir(dir)
cmd := exec.Command("go", "build", "-o", binary) cmd := exec.Command("go", "build", "-o", binary)
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
ColorLog("[ERRO] Could not build migration binary: %s\n", err) logger.Errorf("Could not build migration binary: %s", err)
formatShellErrOutput(string(out)) formatShellErrOutput(string(out))
removeTempFile(dir, binary) removeTempFile(dir, binary)
removeTempFile(dir, binary+".go") removeTempFile(dir, binary+".go")
@ -323,7 +315,7 @@ func runMigrationBinary(dir, binary string) {
cmd := exec.Command("./" + binary) cmd := exec.Command("./" + binary)
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
formatShellOutput(string(out)) formatShellOutput(string(out))
ColorLog("[ERRO] Could not run migration binary: %s\n", err) logger.Errorf("Could not run migration binary: %s", err)
removeTempFile(dir, binary) removeTempFile(dir, binary)
removeTempFile(dir, binary+".go") removeTempFile(dir, binary+".go")
os.Exit(2) os.Exit(2)
@ -336,8 +328,7 @@ func runMigrationBinary(dir, binary string) {
// It exits the system when encouter an error // It exits the system when encouter an error
func changeDir(dir string) { func changeDir(dir string) {
if err := os.Chdir(dir); err != nil { if err := os.Chdir(dir); err != nil {
ColorLog("[ERRO] Could not find migration directory: %s\n", err) logger.Fatalf("Could not find migration directory: %s", err)
os.Exit(2)
} }
} }
@ -345,7 +336,7 @@ func changeDir(dir string) {
func removeTempFile(dir, file string) { func removeTempFile(dir, file string) {
changeDir(dir) changeDir(dir)
if err := os.Remove(file); err != nil { if err := os.Remove(file); err != nil {
ColorLog("[WARN] Could not remove temporary file: %s\n", err) logger.Warnf("Could not remove temporary file: %s", err)
} }
} }
@ -353,8 +344,7 @@ func removeTempFile(dir, file string) {
func formatShellErrOutput(o string) { func formatShellErrOutput(o string) {
for _, line := range strings.Split(o, "\n") { for _, line := range strings.Split(o, "\n") {
if line != "" { if line != "" {
ColorLog("[ERRO] -| ") logger.Errorf("|> %s", line)
fmt.Println(line)
} }
} }
} }
@ -363,13 +353,13 @@ func formatShellErrOutput(o string) {
func formatShellOutput(o string) { func formatShellOutput(o string) {
for _, line := range strings.Split(o, "\n") { for _, line := range strings.Split(o, "\n") {
if line != "" { if line != "" {
ColorLog("[INFO] -| ") logger.Infof("|> %s", line)
fmt.Println(line)
} }
} }
} }
const ( const (
// MigrationMainTPL migration main template
MigrationMainTPL = `package main MigrationMainTPL = `package main
import( import(
@ -409,6 +399,7 @@ func main(){
} }
` `
// MYSQLMigrationDDL MySQL migration SQL
MYSQLMigrationDDL = ` MYSQLMigrationDDL = `
CREATE TABLE migrations ( CREATE TABLE migrations (
id_migration int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key', id_migration int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key',
@ -420,7 +411,7 @@ CREATE TABLE migrations (
PRIMARY KEY (id_migration) PRIMARY KEY (id_migration)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ) ENGINE=InnoDB DEFAULT CHARSET=utf8
` `
// POSTGRESMigrationDDL Postgres migration SQL
POSTGRESMigrationDDL = ` POSTGRESMigrationDDL = `
CREATE TYPE migrations_status AS ENUM('update', 'rollback'); CREATE TYPE migrations_status AS ENUM('update', 'rollback');

34
new.go
View File

@ -52,30 +52,30 @@ the following files/directories structure:
func init() { func init() {
cmdNew.Run = createApp cmdNew.Run = createApp
cmdNew.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
} }
func createApp(cmd *Command, args []string) int { func createApp(cmd *Command, args []string) int {
ShowShortVersionBanner()
w := NewColorWriter(os.Stdout) w := NewColorWriter(os.Stdout)
if len(args) != 1 { if len(args) != 1 {
ColorLog("[ERRO] Argument [appname] is missing\n") logger.Fatal("Argument [appname] is missing")
os.Exit(2)
} }
apppath, packpath, err := checkEnv(args[0]) apppath, packpath, err := checkEnv(args[0])
if err != nil { if err != nil {
fmt.Println(err) logger.Fatalf("%s", err)
os.Exit(2)
} }
if isExist(apppath) { if isExist(apppath) {
ColorLog("[ERRO] Path (%s) already exists\n", apppath) logger.Errorf(bold("Application '%s' already exists"), apppath)
ColorLog("[WARN] Do you want to overwrite it? [Yes|No] ") logger.Warn(bold("Do you want to overwrite it? [Yes|No] "))
if !askForConfirmation() { if !askForConfirmation() {
os.Exit(2) os.Exit(2)
} }
} }
ColorLog("[INFO] Creating application...\n") logger.Info("Creating application...")
os.MkdirAll(apppath, 0755) os.MkdirAll(apppath, 0755)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath+string(path.Separator), "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath+string(path.Separator), "\x1b[0m")
@ -117,7 +117,7 @@ func createApp(cmd *Command, args []string) int {
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m") fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "main.go"), strings.Replace(maingo, "{{.Appname}}", packpath, -1)) WriteToFile(path.Join(apppath, "main.go"), strings.Replace(maingo, "{{.Appname}}", packpath, -1))
ColorLog("[SUCC] New application successfully created!\n") logger.Success("New application successfully created!")
return 0 return 0
} }
@ -171,13 +171,13 @@ func init() {
} }
// TestMain is a sample to run an endpoint test // TestBeego is a sample to run an endpoint test
func TestMain(t *testing.T) { func TestBeego(t *testing.T) {
r, _ := http.NewRequest("GET", "/", nil) r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r) beego.BeeApp.Handlers.ServeHTTP(w, r)
beego.Trace("testing", "TestMain", "Code[%d]\n%s", w.Code, w.Body.String()) beego.Trace("testing", "TestBeego", "Code[%d]\n%s", w.Code, w.Body.String())
Convey("Subject: Test Station Endpoint\n", t, func() { Convey("Subject: Test Station Endpoint\n", t, func() {
Convey("Status Code Should Be 200", func() { Convey("Status Code Should Be 200", func() {
@ -302,13 +302,3 @@ var indextpl = `<!DOCTYPE html>
</body> </body>
</html> </html>
` `
// WriteToFile creates a file and writes content to it
func WriteToFile(filename, content string) {
f, err := os.Create(filename)
defer CloseFile(f)
if err != nil {
panic(err)
}
f.WriteString(content)
}

61
pack.go
View File

@ -21,7 +21,6 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
path "path/filepath" path "path/filepath"
@ -102,14 +101,10 @@ func init() {
fs.BoolVar(&verbose, "v", false, "verbose") fs.BoolVar(&verbose, "v", false, "verbose")
cmdPack.Flag = *fs cmdPack.Flag = *fs
cmdPack.Run = packApp cmdPack.Run = packApp
cmdPack.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
w = NewColorWriter(os.Stdout) w = NewColorWriter(os.Stdout)
} }
func exitPrint(con string) {
fmt.Fprintln(os.Stderr, con)
os.Exit(2)
}
type walker interface { type walker interface {
isExclude(string) bool isExclude(string) bool
isEmpty(string) bool isEmpty(string) bool
@ -398,10 +393,10 @@ func (wft *zipWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
func packDirectory(excludePrefix []string, excludeSuffix []string, func packDirectory(excludePrefix []string, excludeSuffix []string,
excludeRegexp []*regexp.Regexp, includePath ...string) (err error) { excludeRegexp []*regexp.Regexp, includePath ...string) (err error) {
ColorLog("Excluding relpath prefix: %s\n", strings.Join(excludePrefix, ":")) logger.Infof("Excluding relpath prefix: %s", strings.Join(excludePrefix, ":"))
ColorLog("Excluding relpath suffix: %s\n", strings.Join(excludeSuffix, ":")) logger.Infof("Excluding relpath suffix: %s", strings.Join(excludeSuffix, ":"))
if len(excludeRegexp) > 0 { if len(excludeRegexp) > 0 {
ColorLog("Excluding filename regex: `%s`\n", strings.Join(excludeR, "`, `")) logger.Infof("Excluding filename regex: `%s`", strings.Join(excludeR, "`, `"))
} }
w, err := os.OpenFile(outputP, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) w, err := os.OpenFile(outputP, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
@ -454,27 +449,7 @@ func packDirectory(excludePrefix []string, excludeSuffix []string,
return return
} }
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()`)
for _, fi := range fis {
if fi.IsDir() == false && strings.HasSuffix(fi.Name(), ".go") {
data, err := ioutil.ReadFile(path.Join(thePath, fi.Name()))
if err != nil {
continue
}
if len(regex.Find(data)) > 0 {
return true
}
}
}
return false
}
func packApp(cmd *Command, args []string) int { func packApp(cmd *Command, args []string) int {
ShowShortVersionBanner()
curPath, _ := os.Getwd() curPath, _ := os.Getwd()
thePath := "" thePath := ""
@ -496,17 +471,13 @@ func packApp(cmd *Command, args []string) int {
thePath, err := path.Abs(appPath) thePath, err := path.Abs(appPath)
if err != nil { if err != nil {
exitPrint(fmt.Sprintf("Wrong app path: %s", thePath)) logger.Fatalf("Wrong application path: %s", thePath)
} }
if stat, err := os.Stat(thePath); os.IsNotExist(err) || stat.IsDir() == false { if stat, err := os.Stat(thePath); os.IsNotExist(err) || stat.IsDir() == false {
exitPrint(fmt.Sprintf("App path does not exist: %s", thePath)) logger.Fatalf("Application path does not exist: %s", thePath)
} }
if isBeegoProject(thePath) == false { logger.Infof("Packaging application on '%s'...", thePath)
exitPrint(fmt.Sprintf("Bee does not support non Beego project"))
}
ColorLog("Packaging application: %s\n", thePath)
appName := path.Base(thePath) appName := path.Base(thePath)
@ -526,7 +497,7 @@ func packApp(cmd *Command, args []string) int {
os.Mkdir(tmpdir, 0700) os.Mkdir(tmpdir, 0700)
if build { if build {
ColorLog("Building application...\n") logger.Info("Building application...")
var envs []string var envs []string
for _, env := range buildEnvs { for _, env := range buildEnvs {
parts := strings.SplitN(env, "=", 2) parts := strings.SplitN(env, "=", 2)
@ -548,7 +519,7 @@ func packApp(cmd *Command, args []string) int {
os.Setenv("GOOS", goos) os.Setenv("GOOS", goos)
os.Setenv("GOARCH", goarch) os.Setenv("GOARCH", goarch)
ColorLog("Env: GOOS=%s GOARCH=%s\n", goos, goarch) logger.Infof("Using: GOOS=%s GOARCH=%s", goos, goarch)
binPath := path.Join(tmpdir, appName) binPath := path.Join(tmpdir, appName)
if goos == "windows" { if goos == "windows" {
@ -571,10 +542,10 @@ func packApp(cmd *Command, args []string) int {
execmd.Dir = thePath execmd.Dir = thePath
err = execmd.Run() err = execmd.Run()
if err != nil { if err != nil {
exitPrint(err.Error()) logger.Fatal(err.Error())
} }
ColorLog("Build successful\n") logger.Success("Build successful!")
} }
switch format { switch format {
@ -592,7 +563,7 @@ func packApp(cmd *Command, args []string) int {
if _, err := os.Stat(outputP); err != nil { if _, err := os.Stat(outputP); err != nil {
err = os.MkdirAll(outputP, 0755) err = os.MkdirAll(outputP, 0755)
if err != nil { if err != nil {
exitPrint(err.Error()) logger.Fatal(err.Error())
} }
} }
@ -614,18 +585,20 @@ func packApp(cmd *Command, args []string) int {
for _, r := range excludeR { for _, r := range excludeR {
if len(r) > 0 { if len(r) > 0 {
if re, err := regexp.Compile(r); err != nil { if re, err := regexp.Compile(r); err != nil {
exitPrint(err.Error()) logger.Fatal(err.Error())
} else { } else {
exr = append(exr, re) exr = append(exr, re)
} }
} }
} }
logger.Infof("Writing to output: %s", outputP)
err = packDirectory(exp, exs, exr, tmpdir, thePath) err = packDirectory(exp, exs, exr, tmpdir, thePath)
if err != nil { if err != nil {
exitPrint(err.Error()) logger.Fatal(err.Error())
} }
ColorLog("Writing to output: `%s`\n", outputP) logger.Success("Application packed!")
return 0 return 0
} }

43
run.go
View File

@ -15,7 +15,6 @@
package main package main
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
path "path/filepath" path "path/filepath"
@ -57,6 +56,7 @@ var (
func init() { func init() {
cmdRun.Run = runApp cmdRun.Run = runApp
cmdRun.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
cmdRun.Flag.Var(&mainFiles, "main", "specify main go files") cmdRun.Flag.Var(&mainFiles, "main", "specify main go files")
cmdRun.Flag.Var(&gendoc, "gendoc", "auto generate the docs") cmdRun.Flag.Var(&gendoc, "gendoc", "auto generate the docs")
cmdRun.Flag.Var(&downdoc, "downdoc", "auto download swagger file when not exist") cmdRun.Flag.Var(&downdoc, "downdoc", "auto download swagger file when not exist")
@ -68,8 +68,6 @@ func init() {
} }
func runApp(cmd *Command, args []string) int { func runApp(cmd *Command, args []string) int {
ShowShortVersionBanner()
if len(args) == 0 || args[0] == "watchall" { if len(args) == 0 || args[0] == "watchall" {
currpath, _ = os.Getwd() currpath, _ = os.Getwd()
@ -77,9 +75,8 @@ func runApp(cmd *Command, args []string) int {
appname = path.Base(currpath) appname = path.Base(currpath)
currentGoPath = _gopath currentGoPath = _gopath
} else { } else {
exitPrint(fmt.Sprintf("Bee does not support non Beego project: %s", currpath)) logger.Fatalf("No application '%s' found in your GOPATH", currpath)
} }
ColorLog("[INFO] Using '%s' as 'appname'\n", appname)
} else { } else {
// Check if passed Bee application path/name exists in the GOPATH(s) // Check if passed Bee application path/name exists in the GOPATH(s)
if found, _gopath, _path := SearchGOPATHs(args[0]); found { if found, _gopath, _path := SearchGOPATHs(args[0]); found {
@ -87,35 +84,35 @@ func runApp(cmd *Command, args []string) int {
currentGoPath = _gopath currentGoPath = _gopath
appname = path.Base(currpath) appname = path.Base(currpath)
} else { } else {
panic(fmt.Sprintf("No Beego application '%s' found in your GOPATH", args[0])) logger.Fatalf("No application '%s' found in your GOPATH", args[0])
} }
ColorLog("[INFO] Using '%s' as 'appname'\n", appname)
if strings.HasSuffix(appname, ".go") && isExist(currpath) { 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) logger.Warnf("The appname is in conflict with file's current path. Do you want to build appname as '%s'", appname)
ColorLog("[INFO] Do you want to overwrite it? [yes|no]] ") logger.Info("Do you want to overwrite it? [yes|no] ")
if !askForConfirmation() { if !askForConfirmation() {
return 0 return 0
} }
} }
} }
Debugf("current path:%s\n", currpath) logger.Infof("Using '%s' as 'appname'", appname)
if runmode == "prod" || runmode == "dev"{ logger.Debugf("Current path: %s", __FILE__(), __LINE__(), currpath)
if runmode == "prod" || runmode == "dev" {
os.Setenv("BEEGO_RUNMODE", runmode) os.Setenv("BEEGO_RUNMODE", runmode)
ColorLog("[INFO] Using '%s' as 'runmode'\n", os.Getenv("BEEGO_RUNMODE")) logger.Infof("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE"))
}else if runmode != ""{ } else if runmode != "" {
os.Setenv("BEEGO_RUNMODE", runmode) os.Setenv("BEEGO_RUNMODE", runmode)
ColorLog("[WARN] Using '%s' as 'runmode'\n", os.Getenv("BEEGO_RUNMODE")) logger.Warnf("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE"))
}else if os.Getenv("BEEGO_RUNMODE") != ""{ } else if os.Getenv("BEEGO_RUNMODE") != "" {
ColorLog("[WARN] Using '%s' as 'runmode'\n", os.Getenv("BEEGO_RUNMODE")) logger.Warnf("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE"))
} }
err := loadConfig() err := loadConfig()
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err) logger.Fatalf("Failed to load configuration: %s", err)
} }
var paths []string var paths []string
@ -143,10 +140,10 @@ func runApp(cmd *Command, args []string) int {
} }
if gendoc == "true" { if gendoc == "true" {
NewWatcher(paths, files, true) NewWatcher(paths, files, true)
Autobuild(files, true) AutoBuild(files, true)
} else { } else {
NewWatcher(paths, files, false) NewWatcher(paths, files, false)
Autobuild(files, false) AutoBuild(files, false)
} }
for { for {
@ -202,16 +199,16 @@ func isExcluded(filePath string) bool {
for _, p := range excludedPaths { for _, p := range excludedPaths {
absP, err := path.Abs(p) absP, err := path.Abs(p)
if err != nil { if err != nil {
ColorLog("[ERROR] Can not get absolute path of [ %s ]\n", p) logger.Errorf("Cannot get absolute path of '%s'", p)
continue continue
} }
absFilePath, err := path.Abs(filePath) absFilePath, err := path.Abs(filePath)
if err != nil { if err != nil {
ColorLog("[ERROR] Can not get absolute path of [ %s ]\n", filePath) logger.Errorf("Cannot get absolute path of '%s'", filePath)
break break
} }
if strings.HasPrefix(absFilePath, absP) { if strings.HasPrefix(absFilePath, absP) {
ColorLog("[INFO] Excluding from watching [ %s ]\n", filePath) logger.Infof("'%s' is not being watched", filePath)
return true return true
} }
} }

View File

@ -17,7 +17,6 @@ import (
"archive/zip" "archive/zip"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"os" "os"
"strings" "strings"
@ -54,6 +53,7 @@ var docport docValue
func init() { func init() {
cmdRundocs.Run = runDocs cmdRundocs.Run = runDocs
cmdRundocs.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
cmdRundocs.Flag.Var(&isDownload, "isDownload", "weather download the Swagger Docs") cmdRundocs.Flag.Var(&isDownload, "isDownload", "weather download the Swagger Docs")
cmdRundocs.Flag.Var(&docport, "docport", "doc server port") cmdRundocs.Flag.Var(&docport, "docport", "doc server port")
} }
@ -63,18 +63,22 @@ func runDocs(cmd *Command, args []string) int {
downloadFromURL(swaggerlink, "swagger.zip") downloadFromURL(swaggerlink, "swagger.zip")
err := unzipAndDelete("swagger.zip") err := unzipAndDelete("swagger.zip")
if err != nil { if err != nil {
fmt.Println("has err exet unzipAndDelete", err) logger.Errorf("Error while unzipping 'swagger.zip' file: %s", err)
} }
} }
if docport == "" { if docport == "" {
docport = "8089" docport = "8089"
} }
if _, err := os.Stat("swagger"); err != nil && os.IsNotExist(err) { if _, err := os.Stat("swagger"); err != nil && os.IsNotExist(err) {
fmt.Println("there's no swagger, please use bee rundocs -isDownload=true downlaod first") logger.Fatal("No Swagger dist found. Run: bee rundocs -isDownload=true")
os.Exit(2) }
logger.Infof("Starting the docs server on: http://127.0.0.1:%s", docport)
err := http.ListenAndServe(":"+string(docport), http.FileServer(http.Dir("swagger")))
if err != nil {
logger.Fatalf("%s", err)
} }
fmt.Println("start the docs server on: http://127.0.0.1:" + docport)
log.Fatal(http.ListenAndServe(":"+string(docport), http.FileServer(http.Dir("swagger"))))
return 0 return 0
} }
@ -85,36 +89,36 @@ func downloadFromURL(url, fileName string) {
} else if fd.Size() == int64(0) { } else if fd.Size() == int64(0) {
down = true down = true
} else { } else {
ColorLog("[%s] Filename %s already exist\n", INFO, fileName) logger.Infof("'%s' already exists", fileName)
return return
} }
if down { if down {
ColorLog("[%s]Downloading %s to %s\n", SUCC, url, fileName) logger.Infof("Downloading '%s' to '%s'...", url, fileName)
output, err := os.Create(fileName) output, err := os.Create(fileName)
if err != nil { if err != nil {
ColorLog("[%s]Error while creating %s: %s\n", ERRO, fileName, err) logger.Errorf("Error while creating '%s': %s", fileName, err)
return return
} }
defer output.Close() defer output.Close()
response, err := http.Get(url) response, err := http.Get(url)
if err != nil { if err != nil {
ColorLog("[%s]Error while downloading %s:%s\n", ERRO, url, err) logger.Errorf("Error while downloading '%s': %s", url, err)
return return
} }
defer response.Body.Close() defer response.Body.Close()
n, err := io.Copy(output, response.Body) n, err := io.Copy(output, response.Body)
if err != nil { if err != nil {
ColorLog("[%s]Error while downloading %s:%s\n", ERRO, url, err) logger.Errorf("Error while downloading '%s': %s", url, err)
return return
} }
ColorLog("[%s] %d bytes downloaded.\n", SUCC, n) logger.Successf("%d bytes downloaded!", n)
} }
} }
func unzipAndDelete(src string) error { func unzipAndDelete(src string) error {
ColorLog("[%s]start to unzip file from %s\n", INFO, src) logger.Infof("Unzipping '%s'...", src)
r, err := zip.OpenReader(src) r, err := zip.OpenReader(src)
if err != nil { if err != nil {
return err return err
@ -146,6 +150,6 @@ func unzipAndDelete(src string) error {
} }
} }
} }
ColorLog("[%s]Start delete src file %s\n", INFO, src) logger.Successf("Done! Deleting '%s'...", src)
return os.RemoveAll(src) return os.RemoveAll(src)
} }

27
test.go
View File

@ -31,6 +31,7 @@ var cmdTest = &Command{
func init() { func init() {
cmdTest.Run = testApp cmdTest.Run = testApp
cmdTest.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
} }
func safePathAppend(arr []string, paths ...string) []string { func safePathAppend(arr []string, paths ...string) []string {
@ -51,19 +52,19 @@ var started = make(chan bool)
func testApp(cmd *Command, args []string) int { func testApp(cmd *Command, args []string) int {
if len(args) != 1 { if len(args) != 1 {
ColorLog("[ERRO] Cannot start running[ %s ]\n", logger.Fatalf("Failed to start: %s", "argument 'appname' is missing")
"argument 'appname' is missing")
os.Exit(2)
} }
crupath, _ := os.Getwd()
Debugf("current path:%s\n", crupath) currpath, _ := os.Getwd()
logger.Debugf("Current path: %s", __FILE__(), __LINE__(), currpath)
err := loadConfig() err := loadConfig()
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err) logger.Fatalf("Failed to load configuration: %s", err)
} }
var paths []string var paths []string
readAppDirectories(crupath, &paths) readAppDirectories(currpath, &paths)
NewWatcher(paths, nil, false) NewWatcher(paths, nil, false)
appname = args[0] appname = args[0]
@ -76,7 +77,7 @@ func testApp(cmd *Command, args []string) int {
} }
func runTest() { func runTest() {
ColorLog("[INFO] Start testing...\n") logger.Info("Start testing...")
time.Sleep(time.Second * 1) time.Sleep(time.Second * 1)
crupwd, _ := os.Getwd() crupwd, _ := os.Getwd()
testDir := path.Join(crupwd, "tests") testDir := path.Join(crupwd, "tests")
@ -88,14 +89,14 @@ func runTest() {
icmd := exec.Command("go", "test") icmd := exec.Command("go", "test")
icmd.Stdout = os.Stdout icmd.Stdout = os.Stdout
icmd.Stderr = os.Stderr icmd.Stderr = os.Stderr
ColorLog("[TRAC] ============== Test Begin ===================\n") logger.Info("============== Test Begin ===================")
err = icmd.Run() err = icmd.Run()
ColorLog("[TRAC] ============== Test End ===================\n") logger.Info("============== Test End =====================")
if err != nil { if err != nil {
ColorLog("[ERRO] ============== Test failed ===================\n") logger.Error("============== Test failed ===================")
ColorLog("[ERRO] %s", err) logger.Errorf("Cause: %s", err)
return return
} }
ColorLog("[SUCC] Test finish\n") logger.Success("Test Completed")
} }

242
util.go
View File

@ -15,14 +15,16 @@
package main package main
import ( import (
"log" "fmt"
"io/ioutil"
"os" "os"
"os/exec"
"path"
"path/filepath" "path/filepath"
"regexp"
"runtime" "runtime"
"strings" "strings"
"time" "time"
"path"
"fmt"
) )
// Go is a basic promise implementation: it wraps calls a function in a goroutine // Go is a basic promise implementation: it wraps calls a function in a goroutine
@ -35,124 +37,14 @@ func Go(f func() error) chan error {
return ch return ch
} }
// if os.env DEBUG set, debug is on // Now returns the current local time in the specified layout
func Debugf(format string, a ...interface{}) { func Now(layout string) string {
if os.Getenv("DEBUG") != "" { return time.Now().Format(layout)
_, file, line, ok := runtime.Caller(1)
if !ok {
file = "<unknown>"
line = -1
} else {
file = filepath.Base(file)
}
fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...)
}
} }
const ( // EndLine returns the a newline escape character
Gray = uint8(iota + 90) func EndLine() string {
Red return "\n"
Green
Yellow
Blue
Magenta
//NRed = uint8(31) // Normal
EndColor = "\033[0m"
INFO = "INFO"
TRAC = "TRAC"
ERRO = "ERRO"
WARN = "WARN"
SUCC = "SUCC"
)
// ColorLog colors log and print to stdout.
// See color rules in function 'ColorLogS'.
func ColorLog(format string, a ...interface{}) {
fmt.Print(ColorLogS(format, a...))
}
// ColorLogS colors log and return colored content.
// Log format: <level> <content [highlight][path]> [ error ].
// Level: TRAC -> blue; ERRO -> red; WARN -> Magenta; SUCC -> green; others -> default.
// Content: default; path: yellow; error -> red.
// Level has to be surrounded by "[" and "]".
// Highlights have to be surrounded by "# " and " #"(space), "#" will be deleted.
// Paths have to be surrounded by "( " and " )"(space).
// Errors have to be surrounded by "[ " and " ]"(space).
// Note: it hasn't support windows yet, contribute is welcome.
func ColorLogS(format string, a ...interface{}) string {
log := fmt.Sprintf(format, a...)
var clog string
if runtime.GOOS != "windows" {
// Level.
i := strings.Index(log, "]")
if log[0] == '[' && i > -1 {
clog += "[" + getColorLevel(log[1:i]) + "]"
}
log = log[i+1:]
// Error.
log = strings.Replace(log, "[ ", fmt.Sprintf("[\033[%dm", Red), -1)
log = strings.Replace(log, " ]", EndColor+"]", -1)
// Path.
log = strings.Replace(log, "( ", fmt.Sprintf("(\033[%dm", Yellow), -1)
log = strings.Replace(log, " )", EndColor+")", -1)
// Highlights.
log = strings.Replace(log, "# ", fmt.Sprintf("\033[%dm", Gray), -1)
log = strings.Replace(log, " #", EndColor, -1)
log = clog + log
} else {
// Level.
i := strings.Index(log, "]")
if log[0] == '[' && i > -1 {
clog += "[" + log[1:i] + "]"
}
log = log[i+1:]
// Error.
log = strings.Replace(log, "[ ", "[", -1)
log = strings.Replace(log, " ]", "]", -1)
// Path.
log = strings.Replace(log, "( ", "(", -1)
log = strings.Replace(log, " )", ")", -1)
// Highlights.
log = strings.Replace(log, "# ", "", -1)
log = strings.Replace(log, " #", "", -1)
log = clog + log
}
return time.Now().Format("2006/01/02 15:04:05 ") + log
}
// getColorLevel returns colored level string by given level.
func getColorLevel(level string) string {
level = strings.ToUpper(level)
switch level {
case INFO:
return fmt.Sprintf("\033[%dm%s\033[0m", Blue, level)
case TRAC:
return fmt.Sprintf("\033[%dm%s\033[0m", Blue, level)
case ERRO:
return fmt.Sprintf("\033[%dm%s\033[0m", Red, level)
case WARN:
return fmt.Sprintf("\033[%dm%s\033[0m", Magenta, level)
case SUCC:
return fmt.Sprintf("\033[%dm%s\033[0m", Green, level)
default:
return level
}
} }
// IsExist returns whether a file or directory exists. // IsExist returns whether a file or directory exists.
@ -174,11 +66,52 @@ func GetGOPATHs() []string {
return paths return paths
} }
// IsBeegoProject checks whether the current path is a Beego application or not
func IsBeegoProject(thePath string) bool {
mainFiles := []string{}
hasBeegoRegex := regexp.MustCompile(`(?s)package main.*?import.*?\(.*?github.com/astaxie/beego".*?\).*func main()`)
c := make(chan error)
// Walk the application path tree to look for main files.
// Main files must satisfy the 'hasBeegoRegex' regular expression.
go func() {
filepath.Walk(thePath, func(fpath string, f os.FileInfo, err error) error {
if err != nil {
return nil
}
// Skip sub-directories
if !f.IsDir() {
var data []byte
data, err = ioutil.ReadFile(fpath)
if err != nil {
c <- err
return nil
}
if len(hasBeegoRegex.Find(data)) > 0 {
mainFiles = append(mainFiles, fpath)
}
}
return nil
})
close(c)
}()
if err := <-c; err != nil {
logger.Fatalf("Unable to walk '%s' tree: %s", thePath, err)
}
if len(mainFiles) > 0 {
return true
}
return false
}
// SearchGOPATHs searchs the user GOPATH(s) for the specified application name.
// It returns a boolean, the application's GOPATH and its full path.
func SearchGOPATHs(app string) (bool, string, string) { func SearchGOPATHs(app string) (bool, string, string) {
gps := GetGOPATHs() gps := GetGOPATHs()
if len(gps) == 0 { if len(gps) == 0 {
ColorLog("[ERRO] Fail to start [ %s ]\n", "GOPATH environment variable is not set or empty") logger.Fatal("GOPATH environment variable is not set or empty")
os.Exit(2)
} }
// Lookup the application inside the user workspace(s) // Lookup the application inside the user workspace(s)
@ -193,9 +126,6 @@ func SearchGOPATHs(app string) (bool, string, string) {
} }
if isExist(currentPath) { if isExist(currentPath) {
if !isBeegoProject(currentPath) {
continue
}
return true, gopath, currentPath return true, gopath, currentPath
} }
} }
@ -211,7 +141,7 @@ func askForConfirmation() bool {
var response string var response string
_, err := fmt.Scanln(&response) _, err := fmt.Scanln(&response)
if err != nil { if err != nil {
log.Fatal(err) logger.Fatalf("%s", err)
} }
okayResponses := []string{"y", "Y", "yes", "Yes", "YES"} okayResponses := []string{"y", "Y", "yes", "Yes", "YES"}
nokayResponses := []string{"n", "N", "no", "No", "NO"} nokayResponses := []string{"n", "N", "no", "No", "NO"}
@ -249,7 +179,7 @@ func snakeString(s string) string {
} }
data = append(data, d) data = append(data, d)
} }
return strings.ToLower(string(data[:len(data)])) return strings.ToLower(string(data[:]))
} }
func camelString(s string) string { func camelString(s string) string {
@ -273,7 +203,25 @@ func camelString(s string) string {
} }
data = append(data, d) data = append(data, d)
} }
return string(data[:len(data)]) return string(data[:])
}
// camelCase converts a _ delimited string to camel case
// e.g. very_important_person => VeryImportantPerson
func camelCase(in string) string {
tokens := strings.Split(in, "_")
for i := range tokens {
tokens[i] = strings.Title(strings.Trim(tokens[i], " "))
}
return strings.Join(tokens, "")
}
// formatSourceCode formats source files
func formatSourceCode(filename string) {
cmd := exec.Command("gofmt", "-w", filename)
if err := cmd.Run(); err != nil {
logger.Warnf("Error while running gofmt: %s", err)
}
} }
// The string flag list, implemented flag.Value interface // The string flag list, implemented flag.Value interface
@ -291,7 +239,45 @@ func (s *strFlags) Set(value string) error {
// CloseFile attempts to close the passed file // CloseFile attempts to close the passed file
// or panics with the actual error // or panics with the actual error
func CloseFile(f *os.File) { func CloseFile(f *os.File) {
if err := f.Close(); err != nil { err := f.Close()
MustCheck(err)
}
// MustCheck panics when the error is not nil
func MustCheck(err error) {
if err != nil {
panic(err) panic(err)
} }
} }
func exitPrint(con string) {
fmt.Fprintln(os.Stderr, con)
os.Exit(2)
}
// WriteToFile creates a file and writes content to it
func WriteToFile(filename, content string) {
f, err := os.Create(filename)
MustCheck(err)
defer CloseFile(f)
_, err = f.WriteString(content)
MustCheck(err)
}
// IsDebugEnabled checks if DEBUG_ENABLED is set or not
func IsDebugEnabled() bool {
debugMode := os.Getenv("DEBUG_ENABLED")
return map[string]bool{"1": true, "0": false}[debugMode]
}
// __FILE__ returns the file name in which the function was invoked
func __FILE__() string {
_, file, _, _ := runtime.Caller(1)
return file
}
// __LINE__ returns the line number at which the function was invoked
func __LINE__() int {
_, _, line, _ := runtime.Caller(1)
return line
}

View File

@ -6,8 +6,10 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"os/exec"
path "path/filepath" path "path/filepath"
"regexp" "regexp"
"strings"
) )
var cmdVersion = &Command{ var cmdVersion = &Command{
@ -76,7 +78,7 @@ func getBeegoVersion() string {
return "" return ""
} }
if gopath == "" { if gopath == "" {
err = fmt.Errorf("You should set GOPATH env variable") err = fmt.Errorf("You need to set GOPATH environment variable")
return "" return ""
} }
wgopath := path.SplitList(gopath) wgopath := path.SplitList(gopath)
@ -88,11 +90,11 @@ func getBeegoVersion() string {
if os.IsNotExist(err) { if os.IsNotExist(err) {
continue continue
} }
ColorLog("[ERRO] Get `beego.go` has error\n") logger.Error("Error while getting stats of 'beego.go'")
} }
fd, err := os.Open(filename) fd, err := os.Open(filename)
if err != nil { if err != nil {
ColorLog("[ERRO] Open `beego.go` has error\n") logger.Error("Error while reading 'beego.go'")
continue continue
} }
reader := bufio.NewReader(fd) reader := bufio.NewReader(fd)
@ -112,5 +114,17 @@ func getBeegoVersion() string {
} }
} }
return "Beego not installed. Please install it first: https://github.com/astaxie/beego" return "Beego is not installed. Please do consider installing it first: https://github.com/astaxie/beego"
}
func getGoVersion() string {
var (
cmdOut []byte
err error
)
if cmdOut, err = exec.Command("go", "version").Output(); err != nil {
logger.Fatalf("There was an error running 'go version' command: %s", err)
}
return strings.Split(string(cmdOut), " ")[2]
} }

View File

@ -16,8 +16,6 @@ package main
import ( import (
"bytes" "bytes"
"fmt"
"github.com/howeyc/fsnotify"
"os" "os"
"os/exec" "os/exec"
"regexp" "regexp"
@ -25,6 +23,8 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/howeyc/fsnotify"
) )
var ( var (
@ -34,11 +34,11 @@ var (
scheduleTime time.Time scheduleTime time.Time
) )
// NewWatcher starts an fsnotify Watcher on the specified paths
func NewWatcher(paths []string, files []string, isgenerate bool) { func NewWatcher(paths []string, files []string, isgenerate bool) {
watcher, err := fsnotify.NewWatcher() watcher, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to create new Watcher[ %s ]\n", err) logger.Fatalf("Failed to create watcher: %s", err)
os.Exit(2)
} }
go func() { go func() {
@ -57,14 +57,14 @@ func NewWatcher(paths []string, files []string, isgenerate bool) {
mt := getFileModTime(e.Name) mt := getFileModTime(e.Name)
if t := eventTime[e.Name]; mt == t { if t := eventTime[e.Name]; mt == t {
ColorLog("[SKIP] # %s #\n", e.String()) logger.Infof(bold("Skipping: ")+"%s", e.String())
isbuild = false isbuild = false
} }
eventTime[e.Name] = mt eventTime[e.Name] = mt
if isbuild { if isbuild {
ColorLog("[EVEN] %s\n", e) logger.Infof("Event fired: %s", e)
go func() { go func() {
// Wait 1s before autobuild util there is no file change. // Wait 1s before autobuild util there is no file change.
scheduleTime = time.Now().Add(1 * time.Second) scheduleTime = time.Now().Add(1 * time.Second)
@ -76,22 +76,21 @@ func NewWatcher(paths []string, files []string, isgenerate bool) {
return return
} }
Autobuild(files, isgenerate) AutoBuild(files, isgenerate)
}() }()
} }
case err := <-watcher.Error: case err := <-watcher.Error:
ColorLog("[WARN] %s\n", err.Error()) // No need to exit here logger.Warnf("Watcher error: %s", err.Error()) // No need to exit here
} }
} }
}() }()
ColorLog("[INFO] Initializing watcher...\n") logger.Info("Initializing watcher...")
for _, path := range paths { for _, path := range paths {
ColorLog("[TRAC] Directory( %s )\n", path) logger.Infof(bold("Watching: ")+"%s", path)
err = watcher.Watch(path) err = watcher.Watch(path)
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to watch directory[ %s ]\n", err) logger.Fatalf("Failed to watch directory: %s", err)
os.Exit(2)
} }
} }
@ -102,25 +101,26 @@ func getFileModTime(path string) int64 {
path = strings.Replace(path, "\\", "/", -1) path = strings.Replace(path, "\\", "/", -1)
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to open file[ %s ]\n", err) logger.Errorf("Failed to open file on '%s': %s", path, err)
return time.Now().Unix() return time.Now().Unix()
} }
defer f.Close() defer f.Close()
fi, err := f.Stat() fi, err := f.Stat()
if err != nil { if err != nil {
ColorLog("[ERRO] Fail to get file information[ %s ]\n", err) logger.Errorf("Failed to get file stats: %s", err)
return time.Now().Unix() return time.Now().Unix()
} }
return fi.ModTime().Unix() return fi.ModTime().Unix()
} }
func Autobuild(files []string, isgenerate bool) { // AutoBuild builds the specified set of files
func AutoBuild(files []string, isgenerate bool) {
state.Lock() state.Lock()
defer state.Unlock() defer state.Unlock()
ColorLog("[INFO] Start building...\n") logger.Info("Start building...")
os.Chdir(currpath) os.Chdir(currpath)
@ -162,7 +162,7 @@ func Autobuild(files []string, isgenerate bool) {
icmd.Stdout = os.Stdout icmd.Stdout = os.Stdout
icmd.Stderr = os.Stderr icmd.Stderr = os.Stderr
icmd.Run() icmd.Run()
ColorLog("============== generate docs ===================\n") logger.Info("============== Generate Docs ===================")
} }
if err == nil { if err == nil {
@ -186,35 +186,38 @@ func Autobuild(files []string, isgenerate bool) {
} }
if err != nil { if err != nil {
ColorLog("[ERRO] ============== Build failed ===================\n") logger.Error("============== Build Failed ===================")
return return
} }
ColorLog("[SUCC] Build was successful\n") logger.Success("Built Successfully!")
Restart(appname) Restart(appname)
} }
// Kill kills the running command process
func Kill() { func Kill() {
defer func() { defer func() {
if e := recover(); e != nil { if e := recover(); e != nil {
fmt.Println("Kill.recover -> ", e) logger.Infof("Kill recover: %s", e)
} }
}() }()
if cmd != nil && cmd.Process != nil { if cmd != nil && cmd.Process != nil {
err := cmd.Process.Kill() err := cmd.Process.Kill()
if err != nil { if err != nil {
fmt.Println("Kill -> ", err) logger.Errorf("Error while killing cmd process: %s", err)
} }
} }
} }
// Restart kills the running command process and starts it again
func Restart(appname string) { func Restart(appname string) {
Debugf("kill running process") logger.Debugf("Kill running process", __FILE__(), __LINE__())
Kill() Kill()
go Start(appname) go Start(appname)
} }
// Start starts the command process
func Start(appname string) { func Start(appname string) {
ColorLog("[INFO] Restarting %s ...\n", appname) logger.Infof("Restarting '%s'...", appname)
if strings.Index(appname, "./") == -1 { if strings.Index(appname, "./") == -1 {
appname = "./" + appname appname = "./" + appname
} }
@ -226,23 +229,22 @@ func Start(appname string) {
cmd.Env = append(os.Environ(), conf.Envs...) cmd.Env = append(os.Environ(), conf.Envs...)
go cmd.Run() go cmd.Run()
ColorLog("[INFO] %s is running...\n", appname) logger.Successf("'%s' is running...", appname)
started <- true started <- true
} }
// Should ignore filenames generated by // shouldIgnoreFile ignores filenames generated by Emacs, Vim or SublimeText.
// Emacs, Vim or SublimeText // It returns true if the file should be ignored, false otherwise.
func shouldIgnoreFile(filename string) bool { func shouldIgnoreFile(filename string) bool {
for _, regex := range ignoredFilesRegExps { for _, regex := range ignoredFilesRegExps {
r, err := regexp.Compile(regex) r, err := regexp.Compile(regex)
if err != nil { if err != nil {
panic("Could not compile the regex: " + regex) logger.Fatalf("Could not compile regular expression: %s", err)
} }
if r.MatchString(filename) { if r.MatchString(filename) {
return true return true
} else {
continue
} }
continue
} }
return false return false
} }