diff --git a/apiapp.go b/apiapp.go index 892719b..4c39c96 100644 --- a/apiapp.go +++ b/apiapp.go @@ -538,19 +538,17 @@ func TestGet(t *testing.T) { func init() { cmdApiapp.Run = createapi + cmdApiapp.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() } cmdApiapp.Flag.Var(&tables, "tables", "specify tables to generate model") 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") } func createapi(cmd *Command, args []string) int { - ShowShortVersionBanner() - w := NewColorWriter(os.Stdout) if len(args) < 1 { - ColorLog("[ERRO] Argument [appname] is missing\n") - os.Exit(2) + logger.Fatal("Argument [appname] is missing") } if len(args) > 1 { @@ -559,8 +557,7 @@ func createapi(cmd *Command, args []string) int { apppath, packpath, err := checkEnv(args[0]) if err != nil { - fmt.Println(err) - os.Exit(2) + logger.Fatalf("%s", err) } if driver == "" { driver = "mysql" @@ -568,7 +565,7 @@ func createapi(cmd *Command, args []string) int { if conn == "" { } - ColorLog("[INFO] Creating API...\n") + logger.Info("Creating API...") os.MkdirAll(apppath, 0755) 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, ), ) - ColorLog("[INFO] Using '%s' as 'driver'\n", driver) - ColorLog("[INFO] Using '%s' as 'conn'\n", conn) - ColorLog("[INFO] Using '%s' as 'tables'\n", tables) + logger.Infof("Using '%s' as 'driver'", driver) + logger.Infof("Using '%s' as 'conn'", conn) + logger.Infof("Using '%s' as 'tables'", tables) generateAppcode(string(driver), string(conn), "3", string(tables), apppath) } else { os.Mkdir(path.Join(apppath, "models"), 0755) @@ -635,15 +632,14 @@ func createapi(cmd *Command, args []string) int { WriteToFile(path.Join(apppath, "main.go"), strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1)) } - ColorLog("[SUCC] New API successfully created!\n") + logger.Success("New API successfully created!") return 0 } func checkEnv(appname string) (apppath, packpath string, err error) { gps := GetGOPATHs() if len(gps) == 0 { - ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty") - os.Exit(2) + logger.Fatal("GOPATH environment variable is not set or empty") } currpath, _ := os.Getwd() 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 // we use the first path gopath := gps[0] - ColorLog("[%s]You current workdir is not a $GOPATH/src, bee will create the application in GOPATH: %s\n", WARN, gopath) - Debugf("GOPATH: %s", gopath) + + logger.Warn("You current workdir is not inside $GOPATH/src.") + logger.Debugf("GOPATH: %s", __FILE__(), __LINE__(), gopath) gosrcpath := path.Join(gopath, "src") apppath = path.Join(gosrcpath, appname) if _, e := os.Stat(apppath); os.IsNotExist(e) == false { 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 } packpath = strings.Join(strings.Split(apppath[len(gosrcpath)+1:], string(path.Separator)), "/") diff --git a/autorouter.go b/autorouter.go index 1273551..259f5da 100644 --- a/autorouter.go +++ b/autorouter.go @@ -66,7 +66,7 @@ func getControllerInfo(path string) (map[string][]string, error) { files := make([]*source, 0, len(fis)) for _, fi := range fis { - // Only load go files. + // Only load Go files if strings.HasSuffix(fi.Name(), ".go") { f, err := os.Open(path + "/" + fi.Name()) if err != nil { @@ -107,7 +107,7 @@ func getControllerInfo(path string) (map[string][]string, error) { return cm, nil } -// A source describles a source code file. +// source represents a source code file. type source struct { name string 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) 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 { pdoc *Package - srcs map[string]*source // Source files. + srcs map[string]*source // Source files 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. type Package struct { ImportPath string - - // Top-level declarations. - Types []*Type + Types []*Type // Top-level declarations } // Type represents structs and interfaces. type Type struct { - Name string // Type name. + Name string // Type name Decl string - Methods []*Func // Exported methods. + Methods []*Func // Exported methods } // Func represents functions @@ -150,7 +148,7 @@ type Func struct { // build generates data from source files. 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) for _, src := range srcs { w.srcs[src.name] = src @@ -158,7 +156,7 @@ func (w *routerWalker) build(srcs []*source) (*Package, error) { w.fset = token.NewFileSet() - // Find the package and associated files. + // Find the package and associated files ctxt := gobuild.Context{ GOOS: runtime.GOOS, GOARCH: runtime.GOARCH, @@ -174,7 +172,7 @@ func (w *routerWalker) build(srcs []*source) (*Package, error) { } 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) if err != nil { if nogo { @@ -272,9 +270,8 @@ func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, e name = strings.TrimPrefix(name, "biogo.") // 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 - // detect when trimming the "go" prefix is appropriate. - + // extra "go" prefix, but not always. + // TODO: examine unresolved ids to detect when trimming the "go" prefix is appropriate. pkg = ast.NewObj(ast.Pkg, name) pkg.Data = ast.NewScope(nil) imports[path] = pkg diff --git a/bale.go b/bale.go index 2bf14f9..e7a9aa1 100644 --- a/bale.go +++ b/bale.go @@ -32,7 +32,7 @@ var cmdBale = &Command{ Long: ` 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. 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() { cmdBale.Run = runBale + cmdBale.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() } } func runBale(cmd *Command, args []string) int { - ShowShortVersionBanner() - err := loadConfig() 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.Mkdir("bale", os.ModePerm) - // Pack and compress data. + // Pack and compress data for _, p := range conf.Bale.Dirs { if !isExist(p) { - ColorLog("[WARN] Skipped directory( %s )\n", p) + logger.Warnf("Skipped directory: %s", p) continue } - ColorLog("[INFO] Packaging directory( %s )\n", p) + logger.Infof("Packaging directory: %s", p) filepath.Walk(p, walkFn) } @@ -74,22 +73,21 @@ func runBale(cmd *Command, args []string) int { fw, err := os.Create("bale.go") if err != nil { - ColorLog("[ERRO] Fail to create file[ %s ]\n", err) - os.Exit(2) + logger.Fatalf("Failed to create file: %s", err) } defer fw.Close() _, err = fw.Write(buf.Bytes()) if err != nil { - ColorLog("[ERRO] Fail to write data[ %s ]\n", err) - os.Exit(2) + logger.Fatalf("Failed to write data: %s", err) } - ColorLog("[SUCC] Baled resources successfully!\n") + logger.Success("Baled resources successfully!") return 0 } const ( + // BaleHeader ... BaleHeader = `package main import( @@ -150,14 +148,13 @@ func walkFn(resPath string, info os.FileInfo, err error) error { return nil } - // Open resource files. + // Open resource files fr, err := os.Open(resPath) if err != nil { - ColorLog("[ERRO] Fail to read file[ %s ]\n", err) - os.Exit(2) + logger.Fatalf("Failed to read file: %s", err) } - // Convert path. + // Convert path resPath = strings.Replace(resPath, "_", "_0_", -1) resPath = strings.Replace(resPath, ".", "_1_", -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) - // Create corresponding Go source files. + // Create corresponding Go source files os.MkdirAll(path.Dir(resPath), os.ModePerm) fw, err := os.Create("bale/" + resPath + ".go") if err != nil { - ColorLog("[ERRO] Fail to create file[ %s ]\n", err) - os.Exit(2) + logger.Fatalf("Failed to create file: %s", err) } defer fw.Close() - // Write header. + // Write header fmt.Fprintf(fw, Header, resPath) - // Copy and compress data. + // Copy and compress data gz := gzip.NewWriter(&ByteWriter{Writer: fw}) io.Copy(gz, fr) gz.Close() @@ -202,6 +198,7 @@ func filterSuffix(name string) bool { } const ( + // Header ... Header = `package bale import( @@ -212,6 +209,7 @@ import( func R%s() []byte { gz, err := gzip.NewReader(bytes.NewBuffer([]byte{` + // Footer ... Footer = ` })) @@ -229,6 +227,7 @@ func R%s() []byte { var newline = []byte{'\n'} +// ByteWriter ... type ByteWriter struct { io.Writer c int @@ -244,12 +243,9 @@ func (w *ByteWriter) Write(p []byte) (n int, err error) { w.Writer.Write(newline) w.c = 0 } - fmt.Fprintf(w.Writer, "0x%02x,", p[n]) w.c++ } - n++ - return } diff --git a/banner.go b/banner.go index af33ef3..b9a4789 100644 --- a/banner.go +++ b/banner.go @@ -6,7 +6,6 @@ import ( "os" "runtime" "text/template" - "time" ) type vars struct { @@ -21,25 +20,17 @@ type vars struct { BeegoVersion string } -// Now returns the current local time in the specified layout -func Now(layout string) string { - return time.Now().Format(layout) -} - // InitBanner loads the banner and prints it to output // All errors are ignored, the application will not // print the banner in case of error. func InitBanner(out io.Writer, in io.Reader) { if in == nil { - ColorLog("[ERRO] The input is nil\n") - os.Exit(2) + logger.Fatal("The input is nil") } banner, err := ioutil.ReadAll(in) if err != nil { - ColorLog("[ERRO] Error trying to read the banner\n") - ColorLog("[HINT] %v\n", err) - os.Exit(2) + logger.Fatalf("Error while trying to read the banner: %s", err) } show(out, string(banner)) @@ -51,13 +42,11 @@ func show(out io.Writer, content string) { Parse(content) if err != nil { - ColorLog("[ERRO] Cannot parse the banner template\n") - ColorLog("[HINT] %v\n", err) - os.Exit(2) + logger.Fatalf("Cannot parse the banner template: %s", err) } err = t.Execute(out, vars{ - runtime.Version(), + getGoVersion(), runtime.GOOS, runtime.GOARCH, runtime.NumCPU(), @@ -67,7 +56,5 @@ func show(out io.Writer, content string) { version, getBeegoVersion(), }) - if err != nil { - panic(err) - } + MustCheck(err) } diff --git a/bee.go b/bee.go index 76c5359..11e14fd 100644 --- a/bee.go +++ b/bee.go @@ -25,13 +25,17 @@ import ( "strings" ) -const version = "1.5.1" +const version = "1.5.2" +// Command is the unit of execution type Command struct { // Run runs the command. // The args are the arguments after the command name. Run func(cmd *Command, args []string) int + // PreRun performs an operation before running the command + PreRun func(cmd *Command, args []string) + // UsageLine is the one-line usage message. // The first word in the line is taken to be the command name. UsageLine string @@ -60,6 +64,7 @@ func (c *Command) Name() string { return name } +// Usage puts out the usage for the command. func (c *Command) Usage() { fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine) fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(string(c.Long))) @@ -88,7 +93,11 @@ var commands = []*Command{ cmdFix, } +var logger = GetBeeLogger(os.Stdout) + func main() { + currentpath, _ := os.Getwd() + flag.Usage = usage flag.Parse() log.SetFlags(0) @@ -112,6 +121,17 @@ func main() { cmd.Flag.Parse(args[1:]) 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)) return } @@ -138,7 +158,6 @@ Additional help topics: {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} Use "bee help [topic]" for more information about that topic. - ` var helpTemplate = `{{if .Runnable}}usage: bee {{.UsageLine}} diff --git a/code.go b/code.go index c341a74..335b021 100644 --- a/code.go +++ b/code.go @@ -109,12 +109,12 @@ func (v *annotationVisitor) Visit(n ast.Node) ast.Visitor { v.ignoreName() ast.Walk(v, n.Type) case *ast.Field: - for _ = range n.Names { + for range n.Names { v.ignoreName() } ast.Walk(v, n.Type) case *ast.ValueSpec: - for _ = range n.Names { + for range n.Names { v.add(AnchorAnnotation, "") } if n.Type != nil { diff --git a/color.go b/color.go index 583d6c6..3896fe9 100644 --- a/color.go +++ b/color.go @@ -14,7 +14,10 @@ package main -import "io" +import ( + "fmt" + "io" +) type outputMode int @@ -49,3 +52,77 @@ func NewModeColorWriter(w io.Writer, mode outputMode) io.Writer { } 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)) +} diff --git a/colorwriter_windows.go b/colorwriter_windows.go index 0a8e88f..7c9e709 100644 --- a/colorwriter_windows.go +++ b/colorwriter_windows.go @@ -419,7 +419,7 @@ func (cw *colorWriter) Write(p []byte) (int, error) { } 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 } diff --git a/conf.go b/conf.go index b26f26b..e02d701 100644 --- a/conf.go +++ b/conf.go @@ -19,10 +19,13 @@ import ( "io/ioutil" "os" + "io" + "path/filepath" + "gopkg.in/yaml.v2" ) -const ConfVer = 0 +const confVer = 0 var defaultConf = `{ "version": 0, @@ -75,42 +78,58 @@ var conf struct { } // loadConfig loads customized configuration. -func loadConfig() error { - foundConf := false - 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) +func loadConfig() (err error) { + err = filepath.Walk(".", func(path string, fileInfo os.FileInfo, err error) error { if err != nil { - return err + return nil } - foundConf = true - } - byml, erryml := ioutil.ReadFile("Beefile") - if erryml == nil { - ColorLog("[INFO] Detected Beefile\n") - err = yaml.Unmarshal(byml, &conf) - if err != nil { - return err + + if fileInfo.IsDir() { + return nil } - foundConf = true - } - if !foundConf { - // Use default. + + if fileInfo.Name() == "bee.json" { + logger.Info("Loading configuration from 'bee.json'...") + 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) if err != nil { - return err + return } } - // Check format version. - if conf.Version != ConfVer { - ColorLog("[WARN] Your bee.json is out-of-date, please update!\n") - ColorLog("[HINT] Compare bee.json under bee source code path and yours\n") + + // No need to return io.EOF error + err = nil + + // 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 { conf.DirStruct.Controllers = "controllers" } @@ -118,7 +137,39 @@ func loadConfig() error { conf.DirStruct.Models = "models" } - // Append watch exts. + // Append watch exts 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 } diff --git a/fix.go b/fix.go index e4adde6..929297c 100644 --- a/fix.go +++ b/fix.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "go/parser" "go/token" "io/ioutil" @@ -8,7 +9,6 @@ import ( "path/filepath" "regexp" "strings" - "fmt" ) var cmdFix = &Command{ @@ -23,16 +23,17 @@ bee fix help to upgrade the application to beego 1.6 func init() { cmdFix.Run = runFix + cmdFix.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() } } func runFix(cmd *Command, args []string) int { - ShowShortVersionBanner() + logger.Info("Upgrading the application...") - ColorLog("[INFO] Upgrading the application...\n") dir, err := os.Getwd() 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 { if info.IsDir() { if strings.HasPrefix(info.Name(), ".") { @@ -49,11 +50,11 @@ func runFix(cmd *Command, args []string) int { err = fixFile(path) fmt.Println("\tfix\t", path) if err != nil { - ColorLog("[ERRO] Could not fix file: %s\n", err) + logger.Errorf("Could not fix file: %s", err) } return err }) - ColorLog("[INFO] Upgrade done!\n") + logger.Success("Upgrade done!") return 0 } @@ -167,18 +168,18 @@ func fixFile(file string) error { } fixed := rp.Replace(string(content)) - // forword the RequestBody from the replace + // Forword the RequestBody from the replace // "Input.Request", "Input.Context.Request", fixed = strings.Replace(fixed, "Input.Context.RequestBody", "Input.RequestBody", -1) - // regexp replace + // Regexp replace pareg := regexp.MustCompile(`(Input.Params\[")(.*)("])`) fixed = pareg.ReplaceAllString(fixed, "Input.Param(\"$2\")") pareg = regexp.MustCompile(`(Input.Data\[\")(.*)(\"\])(\s)(=)(\s)(.*)`) fixed = pareg.ReplaceAllString(fixed, "Input.SetData(\"$2\", $7)") pareg = regexp.MustCompile(`(Input.Data\[\")(.*)(\"\])`) fixed = pareg.ReplaceAllString(fixed, "Input.Data(\"$2\")") - // fix the cache object Put method + // Fix the cache object Put method pareg = regexp.MustCompile(`(\.Put\(\")(.*)(\",)(\s)(.*)(,\s*)([^\*.]*)(\))`) if pareg.MatchString(fixed) && strings.HasSuffix(file, ".go") { fixed = pareg.ReplaceAllString(fixed, ".Put(\"$2\", $5, $7*time.Second)") @@ -199,11 +200,11 @@ func fixFile(file string) error { 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") { fixed = strings.Replace(fixed, "v.Apis", "v.APIs", -1) } - // replace the config file + // Replace the config file if strings.HasSuffix(file, ".conf") { fixed = strings.Replace(fixed, "HttpCertFile", "HTTPSCertFile", -1) fixed = strings.Replace(fixed, "HttpKeyFile", "HTTPSKeyFile", -1) diff --git a/g.go b/g.go index bc4b123..e25bdb1 100644 --- a/g.go +++ b/g.go @@ -69,6 +69,7 @@ var fields docValue func init() { cmdGenerate.Run = generateCode + cmdGenerate.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() } cmdGenerate.Flag.Var(&tables, "tables", "specify tables to generate model") 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") @@ -77,33 +78,30 @@ func init() { } func generateCode(cmd *Command, args []string) int { - ShowShortVersionBanner() - currpath, _ := os.Getwd() if len(args) < 1 { - ColorLog("[ERRO] command is missing\n") - os.Exit(2) + logger.Fatal("Command is missing") } gps := GetGOPATHs() if len(gps) == 0 { - ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty") - os.Exit(2) + logger.Fatal("GOPATH environment variable is not set or empty") } + gopath := gps[0] - Debugf("GOPATH: %s", gopath) + + logger.Debugf("GOPATH: %s", __FILE__(), __LINE__(), gopath) gcmd := args[0] switch gcmd { case "scaffold": if len(args) < 2 { - ColorLog("[ERRO] Wrong number of arguments\n") - ColorLog("[HINT] Usage: bee generate scaffold [scaffoldname] [-fields=\"\"]\n") - os.Exit(2) + logger.Fatal("Wrong number of arguments. Run: bee help generate") } + // Load the configuration err := loadConfig() 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:]) if driver == "" { @@ -119,19 +117,18 @@ func generateCode(cmd *Command, args []string) int { } } if fields == "" { - ColorLog("[ERRO] Wrong number of arguments\n") - ColorLog("[HINT] Usage: bee generate scaffold [scaffoldname] [-fields=\"title:string,body:text\"]\n") - os.Exit(2) + logger.Hint("fields option should not be empty, i.e. -fields=\"title:string,body:text\"") + logger.Fatal("Wrong number of arguments. Run: bee help generate") } sname := args[1] generateScaffold(sname, fields.String(), currpath, driver.String(), conn.String()) case "docs": generateDocs(currpath) case "appcode": - // load config + // Load the configuration err := loadConfig() 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:]) if driver == "" { @@ -153,20 +150,20 @@ func generateCode(cmd *Command, args []string) int { if level == "" { level = "3" } - ColorLog("[INFO] Using '%s' as 'driver'\n", driver) - ColorLog("[INFO] Using '%s' as 'conn'\n", conn) - ColorLog("[INFO] Using '%s' as 'tables'\n", tables) - ColorLog("[INFO] Using '%s' as 'level'\n", level) + logger.Infof("Using '%s' as 'driver'", driver) + logger.Infof("Using '%s' as 'conn'", conn) + logger.Infof("Using '%s' as 'tables'", tables) + logger.Infof("Using '%s' as 'level'", level) generateAppcode(driver.String(), conn.String(), level.String(), tables.String(), currpath) case "migration": if len(args) < 2 { - ColorLog("[ERRO] Wrong number of arguments\n") - ColorLog("[HINT] Usage: bee generate migration [migrationname] [-fields=\"\"]\n") - os.Exit(2) + logger.Fatal("Wrong number of arguments. Run: bee help generate") } cmd.Flag.Parse(args[2:]) mname := args[1] - ColorLog("[INFO] Using '%s' as migration name\n", mname) + + logger.Infof("Using '%s' as migration name", mname) + upsql := "" downsql := "" if fields != "" { @@ -180,21 +177,16 @@ func generateCode(cmd *Command, args []string) int { cname := args[1] generateController(cname, currpath) } else { - ColorLog("[ERRO] Wrong number of arguments\n") - ColorLog("[HINT] Usage: bee generate controller [controllername]\n") - os.Exit(2) + logger.Fatal("Wrong number of arguments. Run: bee help generate") } case "model": if len(args) < 2 { - ColorLog("[ERRO] Wrong number of arguments\n") - ColorLog("[HINT] Usage: bee generate model [modelname] [-fields=\"\"]\n") - os.Exit(2) + logger.Fatal("Wrong number of arguments. Run: bee help generate") } cmd.Flag.Parse(args[2:]) if fields == "" { - ColorLog("[ERRO] Wrong number of arguments\n") - ColorLog("[HINT] Usage: bee generate model [modelname] [-fields=\"title:string,body:text\"]\n") - os.Exit(2) + logger.Hint("fields option should not be empty, i.e. -fields=\"title:string,body:text\"") + logger.Fatal("Wrong number of arguments. Run: bee help generate") } sname := args[1] generateModel(sname, fields.String(), currpath) @@ -203,13 +195,11 @@ func generateCode(cmd *Command, args []string) int { cname := args[1] generateView(cname, currpath) } else { - ColorLog("[ERRO] Wrong number of arguments\n") - ColorLog("[HINT] Usage: bee generate view [viewpath]\n") - os.Exit(2) + logger.Fatal("Wrong number of arguments. Run: bee help generate") } 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 } diff --git a/g_appcode.go b/g_appcode.go index 7733513..9b42abd 100644 --- a/g_appcode.go +++ b/g_appcode.go @@ -18,7 +18,6 @@ import ( "database/sql" "fmt" "os" - "os/exec" "path" "path/filepath" "regexp" @@ -39,7 +38,7 @@ type DbTransformer interface { GetTableNames(conn *sql.DB) []string GetConstraints(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 @@ -265,9 +264,7 @@ func generateAppcode(driver, connStr, level, tables, currpath string) { case "3": mode = OModel | OController | ORouter default: - ColorLog("[ERRO] Invalid 'level' option: %s\n", level) - ColorLog("[HINT] Level must be either 1, 2 or 3\n") - os.Exit(2) + logger.Fatal("Invalid level value. Must be either \"1\", \"2\", or \"3\"") } var selectedTables map[string]bool if tables != "" { @@ -280,12 +277,9 @@ func generateAppcode(driver, connStr, level, tables, currpath string) { case "mysql": case "postgres": case "sqlite": - ColorLog("[ERRO] Generating app code from SQLite database is not supported yet.\n") - os.Exit(2) + logger.Fatal("Generating app code from SQLite database is not supported yet.") default: - ColorLog("[ERRO] Unknown database driver: %s\n", driver) - ColorLog("[HINT] Driver must be one of mysql, postgres or sqlite\n") - os.Exit(2) + logger.Fatal("Unknown database driver. Must be either \"mysql\", \"postgres\" or \"sqlite\"") } 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) { db, err := sql.Open(dbms, connStr) if err != nil { - ColorLog("[ERRO] Could not connect to %s database: %s, %s\n", dbms, connStr, err) - os.Exit(2) + logger.Fatalf("Could not connect to '%s' database using '%s': %s", dbms, connStr, err) } defer db.Close() if trans, ok := dbDriver[dbms]; ok { - ColorLog("[INFO] Analyzing database tables...\n") + logger.Info("Analyzing database tables...") tableNames := trans.GetTableNames(db) tables := getTableObjects(tableNames, db, trans) mvcPath := new(MvcPath) @@ -311,25 +304,21 @@ func gen(dbms, connStr string, mode byte, selectedTableNames map[string]bool, ap pkgPath := getPackagePath(apppath) writeSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames) } else { - ColorLog("[ERRO] Generating app code from %s database is not supported yet.\n", dbms) - os.Exit(2) + logger.Fatalf("Generating app code from '%s' database is not supported yet.", dbms) } } -// 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) { rows, err := db.Query("SHOW TABLES") if err != nil { - ColorLog("[ERRO] Could not show tables\n") - ColorLog("[HINT] Check your connection string\n") - os.Exit(2) + logger.Fatalf("Could not show tables: %s", err) } defer rows.Close() for rows.Next() { var name string if err := rows.Scan(&name); err != nil { - ColorLog("[ERRO] Could not show tables\n") - os.Exit(2) + logger.Fatalf("Could not show tables: %s", err) } tables = append(tables, name) } @@ -358,8 +347,8 @@ func getTableObjects(tableNames []string, db *sql.DB, dbTransformer DbTransforme return } -// getConstraints gets primary key, unique key and foreign keys of a table from information_schema -// and fill in Table struct +// GetConstraints gets primary key, unique key and foreign keys of a table from +// information_schema and fill in the Table struct func (*MysqlDB) GetConstraints(db *sql.DB, table *Table, blackList map[string]bool) { rows, err := db.Query( `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 = ?`, table.Name, table.Name) // u.position_in_unique_constraint, if err != nil { - ColorLog("[ERRO] Could not query INFORMATION_SCHEMA for PK/UK/FK information\n") - os.Exit(2) + logger.Fatal("Could not query INFORMATION_SCHEMA for PK/UK/FK information") } for rows.Next() { var constraintTypeBytes, columnNameBytes, refTableSchemaBytes, refTableNameBytes, refColumnNameBytes, refOrdinalPosBytes []byte 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") - os.Exit(2) + logger.Fatal("Could not read INFORMATION_SCHEMA for PK/UK/FK information") } constraintType, columnName, refTableSchema, refTableName, refColumnName, refOrdinalPos := 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 } else { 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 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 -// and fill in the Column struct +// GetColumns retrieves columns details from +// information_schema and fill in the Column struct func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[string]bool) { // retrieve columns - colDefRows, _ := db.Query( + colDefRows, err := db.Query( `SELECT column_name, data_type, column_type, is_nullable, column_default, extra FROM @@ -418,20 +405,28 @@ func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[strin WHERE table_schema = database() AND table_name = ?`, table.Name) + if err != nil { + logger.Fatalf("Could not query the database: %s", err) + } defer colDefRows.Close() + for colDefRows.Next() { // datatype as bytes so that SQL values can be retrieved var colNameBytes, dataTypeBytes, columnTypeBytes, isNullableBytes, columnDefaultBytes, extraBytes []byte if err := colDefRows.Scan(&colNameBytes, &dataTypeBytes, &columnTypeBytes, &isNullableBytes, &columnDefaultBytes, &extraBytes); err != nil { - ColorLog("[ERRO] Could not query INFORMATION_SCHEMA for column information\n") - os.Exit(2) + logger.Fatal("Could not query INFORMATION_SCHEMA for column information") } colName, dataType, columnType, isNullable, columnDefault, extra := string(colNameBytes), string(dataTypeBytes), string(columnTypeBytes), string(isNullableBytes), string(columnDefaultBytes), string(extraBytes) + // create a column col := new(Column) 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 := new(OrmTag) tag.Column = colName @@ -466,7 +461,10 @@ func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[strin if isSQLSignedIntType(dataType) { sign := extractIntSignness(columnType) 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) { @@ -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 -func (*MysqlDB) GetGoDataType(sqlType string) (goType string) { +func (*MysqlDB) GetGoDataType(sqlType string) (string, error) { var typeMapping = map[string]string{} typeMapping = typeMappingMysql if v, ok := typeMapping[sqlType]; ok { - return v + return v, nil } - ColorLog("[ERRO] data type (%s) not found!\n", sqlType) - os.Exit(2) - return goType + return "", fmt.Errorf("data type '%s' not found", sqlType) } // GetTableNames for PostgreSQL @@ -519,16 +515,14 @@ func (*PostgresDB) GetTableNames(db *sql.DB) (tables []string) { table_type = 'BASE TABLE' AND table_schema NOT IN ('pg_catalog', 'information_schema')`) if err != nil { - ColorLog("[ERRO] Could not show tables: %s\n", err) - ColorLog("[HINT] Check your connection string\n") - os.Exit(2) + logger.Fatalf("Could not show tables: %s", err) } defer rows.Close() + for rows.Next() { var name string if err := rows.Scan(&name); err != nil { - ColorLog("[ERRO] Could not show tables\n") - os.Exit(2) + logger.Fatalf("Could not show tables: %s", err) } tables = append(tables, name) } @@ -558,14 +552,13 @@ func (*PostgresDB) GetConstraints(db *sql.DB, table *Table, blackList map[string AND u.table_name = $2`, table.Name, table.Name) // u.position_in_unique_constraint, if err != nil { - ColorLog("[ERRO] Could not query INFORMATION_SCHEMA for PK/UK/FK information: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not query INFORMATION_SCHEMA for PK/UK/FK information: %s", err) } + for rows.Next() { var constraintTypeBytes, columnNameBytes, refTableSchemaBytes, refTableNameBytes, refColumnNameBytes, refOrdinalPosBytes []byte 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") - os.Exit(2) + logger.Fatalf("Could not read INFORMATION_SCHEMA for PK/UK/FK information: %s", err) } constraintType, columnName, refTableSchema, refTableName, refColumnName, refOrdinalPos := string(constraintTypeBytes), string(columnNameBytes), string(refTableSchemaBytes), @@ -595,7 +588,7 @@ func (*PostgresDB) GetConstraints(db *sql.DB, table *Table, blackList map[string // GetColumns for PostgreSQL func (postgresDB *PostgresDB) GetColumns(db *sql.DB, table *Table, blackList map[string]bool) { // retrieve columns - colDefRows, _ := db.Query( + colDefRows, err := db.Query( `SELECT column_name, 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') AND table_name = $1`, table.Name) + if err != nil { + logger.Fatalf("Could not query INFORMATION_SCHEMA for column information: %s", err) + } defer colDefRows.Close() + for colDefRows.Next() { // datatype as bytes so that SQL values can be retrieved var colNameBytes, dataTypeBytes, columnTypeBytes, isNullableBytes, columnDefaultBytes, extraBytes []byte if err := colDefRows.Scan(&colNameBytes, &dataTypeBytes, &columnTypeBytes, &isNullableBytes, &columnDefaultBytes, &extraBytes); err != nil { - ColorLog("[ERRO] Could not query INFORMATION_SCHEMA for column information\n") - os.Exit(2) + logger.Fatalf("Could not query INFORMATION_SCHEMA for column information: %s", err) } colName, dataType, columnType, isNullable, columnDefault, extra := string(colNameBytes), string(dataTypeBytes), string(columnTypeBytes), string(isNullableBytes), string(columnDefaultBytes), string(extraBytes) - // create a column + // Create a column col := new(Column) 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 := new(OrmTag) 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 -func (*PostgresDB) GetGoDataType(sqlType string) (goType string) { +func (*PostgresDB) GetGoDataType(sqlType string) (string, error) { if v, ok := typeMappingPostgres[sqlType]; ok { - return v + return v, nil } - ColorLog("[ERRO] data type (%s) not found!\n", sqlType) - os.Exit(2) - return goType + return "", fmt.Errorf("data type '%s' not found", sqlType) } // deleteAndRecreatePaths removes several directories completely @@ -717,15 +715,15 @@ func createPaths(mode byte, paths *MvcPath) { // Newly geneated files will be inside these folders. func writeSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) { if (OModel & mode) == OModel { - ColorLog("[INFO] Creating model files...\n") + logger.Info("Creating model files...") writeModelFiles(tables, paths.ModelPath, selectedTables) } if (OController & mode) == OController { - ColorLog("[INFO] Creating controller files...\n") + logger.Info("Creating controller files...") writeControllerFiles(tables, paths.ControllerPath, selectedTables, pkgPath) } if (ORouter & mode) == ORouter { - ColorLog("[INFO] Creating router files...\n") + logger.Info("Creating router files...") 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 err error 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() { f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) if err != nil { - ColorLog("[WARN] %v\n", err) + logger.Warnf("%s", err) continue } } else { - ColorLog("[WARN] Skipped create file '%s'\n", fpath) + logger.Warnf("Skipped create file '%s'", fpath) continue } } else { f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666) if err != nil { - ColorLog("[WARN] %v\n", err) + logger.Warnf("%s", err) 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(fileStr, "{{modelName}}", camelCase(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 := "" 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, "{{importTimePkg}}", importTimePkg, -1) if _, err := f.WriteString(fileStr); err != nil { - ColorLog("[ERRO] Could not write model file to %s\n", fpath) - os.Exit(2) + logger.Fatalf("Could not write model file to '%s': %s", fpath, err) } CloseFile(f) 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) 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 _, selected := selectedTables[tb.Name]; !selected { continue @@ -811,29 +809,28 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri var f *os.File var err error 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() { f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) if err != nil { - ColorLog("[WARN] %v\n", err) + logger.Warnf("%s", err) continue } } else { - ColorLog("[WARN] Skipped create file '%s'\n", fpath) + logger.Warnf("Skipped create file '%s'", fpath) continue } } else { f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666) if err != nil { - ColorLog("[WARN] %v\n", err) + logger.Warnf("%s", err) continue } } fileStr := strings.Replace(CtrlTPL, "{{ctrlName}}", camelCase(tb.Name), -1) fileStr = strings.Replace(fileStr, "{{pkgPath}}", pkgPath, -1) if _, err := f.WriteString(fileStr); err != nil { - ColorLog("[ERRO] Could not write controller file to %s\n", fpath) - os.Exit(2) + logger.Fatalf("Could not write controller file to '%s': %s", fpath, err) } CloseFile(f) 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 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 _, selected := selectedTables[tb.Name]; !selected { continue @@ -856,63 +853,44 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo if tb.Pk == "" { continue } - // add namespaces + // Add namespaces nameSpace := strings.Replace(NamespaceTPL, "{{nameSpace}}", tb.Name, -1) nameSpace = strings.Replace(nameSpace, "{{ctrlName}}", camelCase(tb.Name), -1) nameSpaces = append(nameSpaces, nameSpace) } - // add export controller + // Add export controller fpath := path.Join(rPath, "router.go") routerStr := strings.Replace(RouterTPL, "{{nameSpaces}}", strings.Join(nameSpaces, ""), 1) routerStr = strings.Replace(routerStr, "{{pkgPath}}", pkgPath, 1) var f *os.File var err error if isExist(fpath) { - ColorLog("[WARN] '%v' 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() { f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) if err != nil { - ColorLog("[WARN] %v\n", err) + logger.Warnf("%s", err) return } } else { - ColorLog("[WARN] Skipped create file '%s'\n", fpath) + logger.Warnf("Skipped create file '%s'", fpath) return } } else { f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666) if err != nil { - ColorLog("[WARN] %v\n", err) + logger.Warnf("%s", err) return } } if _, err := f.WriteString(routerStr); err != nil { - ColorLog("[ERRO] Could not write router file to '%s'\n", fpath) - os.Exit(2) + logger.Fatalf("Could not write router file to '%s': %s", fpath, err) } CloseFile(f) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") 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 { 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) { gopath := os.Getenv("GOPATH") - Debugf("gopath:%s", gopath) if gopath == "" { - ColorLog("[ERRO] You should set GOPATH in the env") - os.Exit(2) + logger.Fatal("GOPATH environment variable is not set or empty") } + logger.Debugf("GOPATH: %s", __FILE__(), __LINE__(), gopath) + appsrcpath := "" haspath := false wgopath := filepath.SplitList(gopath) @@ -993,13 +971,11 @@ func getPackagePath(curpath string) (packpath string) { } if !haspath { - ColorLog("[ERRO] Can't generate application code outside of GOPATH '%s'\n", gopath) - os.Exit(2) + logger.Fatalf("Cannot generate application code outside of GOPATH '%s'", gopath) } if curpath == appsrcpath { - ColorLog("[ERRO] Can't generate application code outside of application PATH \n") - os.Exit(2) + logger.Fatal("Cannot generate application code outside of application path") } 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 { // rewrite dot-notation to Object__Attribute 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: var sortFields []string @@ -1168,11 +1148,12 @@ import ( "github.com/astaxie/beego" ) -// oprations for {{ctrlName}} +// {{ctrlName}}Controller oprations for {{ctrlName}} type {{ctrlName}}Controller struct { beego.Controller } +// URLMapping ... func (c *{{ctrlName}}Controller) URLMapping() { c.Mapping("Post", c.Post) c.Mapping("GetOne", c.GetOne) @@ -1181,6 +1162,7 @@ func (c *{{ctrlName}}Controller) URLMapping() { c.Mapping("Delete", c.Delete) } +// Post ... // @Title Post // @Description create {{ctrlName}} // @Param body body models.{{ctrlName}} true "body for {{ctrlName}} content" @@ -1202,7 +1184,8 @@ func (c *{{ctrlName}}Controller) Post() { c.ServeJSON() } -// @Title Get +// GetOne ... +// @Title Get One // @Description get {{ctrlName}} by id // @Param id path string true "The key for staticblock" // @Success 200 {object} models.{{ctrlName}} @@ -1220,6 +1203,7 @@ func (c *{{ctrlName}}Controller) GetOne() { c.ServeJSON() } +// GetAll ... // @Title Get All // @Description get {{ctrlName}} // @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 sortby []string var order []string - var query map[string]string = make(map[string]string) + var query = make(map[string]string) var limit int64 = 10 - var offset int64 = 0 + var offset int64 // fields: col1,col2,entity.col3 if v := c.GetString("fields"); v != "" { @@ -1282,7 +1266,8 @@ func (c *{{ctrlName}}Controller) GetAll() { c.ServeJSON() } -// @Title Update +// Put ... +// @Title Put // @Description update the {{ctrlName}} // @Param id path string true "The id you want to update" // @Param body body models.{{ctrlName}} true "body for {{ctrlName}} content" @@ -1305,6 +1290,7 @@ func (c *{{ctrlName}}Controller) Put() { c.ServeJSON() } +// Delete ... // @Title Delete // @Description delete the {{ctrlName}} // @Param id path string true "The id you want to delete" diff --git a/g_controllers.go b/g_controllers.go index 79ed554..5220210 100644 --- a/g_controllers.go +++ b/g_controllers.go @@ -21,9 +21,6 @@ import ( "strings" ) -// article -// cms/article -// func generateController(cname, currpath string) { w := NewColorWriter(os.Stdout) @@ -36,15 +33,14 @@ func generateController(cname, currpath string) { packageName = p[i+1 : len(p)-1] } - ColorLog("[INFO] Using '%s' as controller name\n", controllerName) - ColorLog("[INFO] Using '%s' as package name\n", packageName) + logger.Infof("Using '%s' as controller name", controllerName) + logger.Infof("Using '%s' as package name", packageName) fp := path.Join(currpath, "controllers", p) if _, err := os.Stat(fp); os.IsNotExist(err) { // Create the controller's directory if err := os.MkdirAll(fp, 0777); err != nil { - ColorLog("[ERRO] Could not create controllers directory: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not create controllers directory: %s", err) } } @@ -56,7 +52,7 @@ func generateController(cname, currpath string) { var content string 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) pkgPath := getPackagePath(currpath) content = strings.Replace(content, "{{pkgPath}}", pkgPath, -1) @@ -71,8 +67,7 @@ func generateController(cname, currpath string) { formatSourceCode(fpath) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") } else { - ColorLog("[ERRO] Could not create controller file: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not create controller file: %s", err) } } @@ -82,11 +77,12 @@ import ( "github.com/astaxie/beego" ) -// operations for {{controllerName}} +// {{controllerName}}Controller operations for {{controllerName}} type {{controllerName}}Controller struct { beego.Controller } +// URLMapping ... func (c *{{controllerName}}Controller) URLMapping() { c.Mapping("Post", c.Post) c.Mapping("GetOne", c.GetOne) @@ -95,6 +91,7 @@ func (c *{{controllerName}}Controller) URLMapping() { c.Mapping("Delete", c.Delete) } +// Post ... // @Title Create // @Description create {{controllerName}} // @Param body body models.{{controllerName}} true "body for {{controllerName}} content" @@ -105,6 +102,7 @@ func (c *{{controllerName}}Controller) Post() { } +// GetOne ... // @Title GetOne // @Description get {{controllerName}} by id // @Param id path string true "The key for staticblock" @@ -115,6 +113,7 @@ func (c *{{controllerName}}Controller) GetOne() { } +// GetAll ... // @Title GetAll // @Description get {{controllerName}} // @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}} // @Param id path string true "The id you want to update" // @Param body body models.{{controllerName}} true "body for {{controllerName}} content" @@ -141,6 +141,7 @@ func (c *{{controllerName}}Controller) Put() { } +// Delete ... // @Title Delete // @Description delete the {{controllerName}} // @Param id path string true "The id you want to delete" @@ -164,11 +165,12 @@ import ( "github.com/astaxie/beego" ) -// oprations for {{controllerName}} +// {{controllerName}}Controller oprations for {{controllerName}} type {{controllerName}}Controller struct { beego.Controller } +// URLMapping ... func (c *{{controllerName}}Controller) URLMapping() { c.Mapping("Post", c.Post) c.Mapping("GetOne", c.GetOne) @@ -177,6 +179,7 @@ func (c *{{controllerName}}Controller) URLMapping() { c.Mapping("Delete", c.Delete) } +// Post ... // @Title Post // @Description create {{controllerName}} // @Param body body models.{{controllerName}} true "body for {{controllerName}} content" @@ -195,7 +198,8 @@ func (c *{{controllerName}}Controller) Post() { c.ServeJSON() } -// @Title Get +// GetOne ... +// @Title Get One // @Description get {{controllerName}} by id // @Param id path string true "The key for staticblock" // @Success 200 {object} models.{{controllerName}} @@ -213,6 +217,7 @@ func (c *{{controllerName}}Controller) GetOne() { c.ServeJSON() } +// GetAll ... // @Title Get All // @Description get {{controllerName}} // @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 sortby []string var order []string - var query map[string]string = make(map[string]string) + var query = make(map[string]string) var limit int64 = 10 - var offset int64 = 0 + var offset int64 // fields: col1,col2,entity.col3 if v := c.GetString("fields"); v != "" { @@ -275,7 +280,8 @@ func (c *{{controllerName}}Controller) GetAll() { c.ServeJSON() } -// @Title Update +// Put ... +// @Title Put // @Description update the {{controllerName}} // @Param id path string true "The id you want to update" // @Param body body models.{{controllerName}} true "body for {{controllerName}} content" @@ -295,6 +301,7 @@ func (c *{{controllerName}}Controller) Put() { c.ServeJSON() } +// Delete ... // @Title Delete // @Description delete the {{controllerName}} // @Param id path string true "The id you want to delete" diff --git a/g_docs.go b/g_docs.go index 83bca73..ca9a877 100644 --- a/g_docs.go +++ b/g_docs.go @@ -26,11 +26,12 @@ import ( "path/filepath" "reflect" "regexp" - "runtime" "strconv" "strings" "unicode" + "gopkg.in/yaml.v2" + "github.com/astaxie/beego/swagger" "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 modelsList map[string]map[string]swagger.Schema 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() { pkgCache = make(map[string]struct{}) @@ -55,21 +80,68 @@ func init() { importlist = make(map[string]string) controllerList = make(map[string]map[string]*swagger.Item) 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) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, path.Join(curpath, "routers", "router.go"), nil, parser.ParseComments) - if err != nil { - ColorLog("[ERRO] parse router.go error\n") - os.Exit(2) + logger.Fatalf("Error while parsing router.go: %s", err) } rootapi.Infos = swagger.Information{} rootapi.SwaggerVersion = "2.0" - //analysis API comments + + // Analyse API comments if f.Comments != nil { for _, c := range f.Comments { for _, s := range strings.Split(c.Text(), "\n") { @@ -83,21 +155,38 @@ func generateDocs(curpath string) { rootapi.Infos.TermsOfService = strings.TrimSpace(s[len("@TermsOfServiceUrl"):]) } else if strings.HasPrefix(s, "@Contact") { rootapi.Infos.Contact.EMail = strings.TrimSpace(s[len("@Contact"):]) - } else if strings.HasPrefix(s, "@License") { - rootapi.Infos.License.Name = strings.TrimSpace(s[len("@License"):]) + } else if strings.HasPrefix(s, "@Name") { + rootapi.Infos.Contact.Name = strings.TrimSpace(s[len("@Name"):]) + } else if strings.HasPrefix(s, "@URL") { + rootapi.Infos.Contact.URL = strings.TrimSpace(s[len("@URL"):]) } else if strings.HasPrefix(s, "@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 { localName := "" if im.Name != nil { localName = im.Name.Name } - analisyscontrollerPkg(localName, im.Path.Value) + analyseControllerPkg(localName, im.Path.Value) } for _, d := range f.Decls { switch specDecl := d.(type) { @@ -107,11 +196,11 @@ func generateDocs(curpath string) { case *ast.AssignStmt: for _, l := range stmt.Rhs { 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" { continue } - version, params := analisysNewNamespace(v) + version, params := analyseNewNamespace(v) if rootapi.BasePath == "" && version != "" { rootapi.BasePath = version } @@ -120,12 +209,12 @@ func generateDocs(curpath string) { case *ast.CallExpr: controllerName := "" if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" { - s, params := analisysNewNamespace(pp) + s, params := analyseNewNamespace(pp) for _, sp := range params { switch pp := sp.(type) { case *ast.CallExpr: if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" { - controllerName = analisysNSInclude(s, pp) + controllerName = analyseNSInclude(s, pp) if v, ok := controllerComments[controllerName]; ok { rootapi.Tags = append(rootapi.Tags, swagger.Tag{ Name: strings.Trim(s, "/"), @@ -136,7 +225,7 @@ func generateDocs(curpath string) { } } } else if selname == "NSInclude" { - controllerName = analisysNSInclude("", pp) + controllerName = analyseNSInclude("", pp) if v, ok := controllerComments[controllerName]; ok { rootapi.Tags = append(rootapi.Tags, swagger.Tag{ Name: controllerName, // if the NSInclude has no prefix, we use the controllername as the tag @@ -155,22 +244,26 @@ func generateDocs(curpath string) { } os.Mkdir(path.Join(curpath, "swagger"), 0755) fd, err := os.Create(path.Join(curpath, "swagger", "swagger.json")) + fdyml, err := os.Create(path.Join(curpath, "swagger", "swagger.yml")) if err != nil { panic(err) } + defer fdyml.Close() defer fd.Close() dt, err := json.MarshalIndent(rootapi, "", " ") - if err != nil { + dtyml, erryml := yaml.Marshal(rootapi) + if err != nil || erryml != nil { panic(err) } _, err = fd.Write(dt) - if err != nil { + _, erryml = fdyml.Write(dtyml) + if err != nil || erryml != nil { panic(err) } } -// return version and the others params -func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) { +// analyseNewNamespace returns version and the others params +func analyseNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) { for i, p := range ce.Args { if i == 0 { switch pp := p.(type) { @@ -184,7 +277,7 @@ func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) { return } -func analisysNSInclude(baseurl string, ce *ast.CallExpr) string { +func analyseNSInclude(baseurl string, ce *ast.CallExpr) string { cname := "" for _, p := range ce.Args { x := p.(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.SelectorExpr) @@ -232,7 +325,7 @@ func analisysNSInclude(baseurl string, ce *ast.CallExpr) string { return cname } -func analisyscontrollerPkg(localName, pkgpath string) { +func analyseControllerPkg(localName, pkgpath string) { pkgpath = strings.Trim(pkgpath, "\"") if isSystemPackage(pkgpath) { return @@ -248,7 +341,7 @@ func analisyscontrollerPkg(localName, pkgpath string) { } gopath := os.Getenv("GOPATH") if gopath == "" { - panic("please set gopath") + logger.Fatal("GOPATH environment variable is not set or empty") } pkgRealpath := "" @@ -266,18 +359,16 @@ func analisyscontrollerPkg(localName, pkgpath string) { } pkgCache[pkgpath] = struct{}{} } else { - ColorLog("[ERRO] the %s pkg not exist in gopath\n", pkgpath) - os.Exit(1) + logger.Fatalf("Package '%s' does not exist in the GOPATH", 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 %s pkg parser.ParseDir error\n", pkgpath) - os.Exit(1) + logger.Fatalf("Error while parsing dir at '%s': %s", pkgpath, err) } for _, pkg := range astPkgs { for _, fl := range pkg.Files { @@ -286,7 +377,7 @@ func analisyscontrollerPkg(localName, pkgpath string) { case *ast.FuncDecl: if specDecl.Recv != nil && len(specDecl.Recv.List) > 0 { if t, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr); ok { - // parse controller method + // Parse controller method 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) { case *ast.StructType: _ = tp.Struct - //parse controller definition comments + // Parse controller definition comments if strings.TrimSpace(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 { - goroot := runtime.GOROOT() + goroot := os.Getenv("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)) if utils.FileExists(wg) { return true @@ -379,8 +471,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat ss = strings.TrimSpace(ss[pos:]) schemaName, pos := peekNextSplitString(ss) if schemaName == "" { - ColorLog("[ERRO][%s.%s] Schema must follow {object} or {array}\n", controllerName, funcName) - os.Exit(-1) + logger.Fatalf("[%s.%s] Schema must follow {object} or {array}", controllerName, funcName) } if strings.HasPrefix(schemaName, "[]") { schemaName = schemaName[2:] @@ -392,13 +483,13 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat schema.Type = typeFormat[0] schema.Format = typeFormat[1] } else { - cmpath, m, mod, realTypes := getModel(schemaName) + m, mod, realTypes := getModel(schemaName) schema.Ref = "#/definitions/" + m if _, ok := modelsList[pkgpath+controllerName]; !ok { modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0) } modelsList[pkgpath+controllerName][schemaName] = mod - appendModels(cmpath, pkgpath, controllerName, realTypes) + appendModels(pkgpath, controllerName, realTypes) } if isArray { rs.Schema = &swagger.Schema{ @@ -417,7 +508,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat para := swagger.Parameter{} p := getparams(strings.TrimSpace(t[len("@Param "):])) 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] switch p[1] { @@ -432,13 +523,13 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat case "body": break default: - ColorLog("[WARN][%s.%s] Unknow param location: %s, Possible values are `query`, `header`, `path`, `formData` or `body`.\n", controllerName, funcName, p[1]) + 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] pp := strings.Split(p[2], ".") typ := pp[len(pp)-1] if len(pp) >= 2 { - cmpath, m, mod, realTypes := getModel(p[2]) + m, mod, realTypes := getModel(p[2]) para.Schema = &swagger.Schema{ 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][typ] = mod - appendModels(cmpath, pkgpath, controllerName, realTypes) + appendModels(pkgpath, controllerName, realTypes) } else { isArray := false paraType := "" @@ -463,7 +554,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat paraType = typeFormat[0] paraFormat = typeFormat[1] } 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 { para.Type = "array" @@ -476,10 +567,15 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat para.Format = paraFormat } } - if len(p) > 4 { + switch len(p) { + case 5: para.Required, _ = strconv.ParseBool(p[3]) 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], `" `) } opts.Parameters = append(opts.Parameters, para) @@ -565,16 +661,12 @@ func getparams(str string) []string { var j int var start bool var r []string - for i, c := range []rune(str) { - if unicode.IsSpace(c) { + var quoted int8 + for _, c := range []rune(str) { + if unicode.IsSpace(c) && quoted == 0 { if !start { continue } else { - if j == 3 { - r = append(r, string(s)) - r = append(r, strings.TrimSpace((str[i+1:]))) - break - } start = false j++ r = append(r, string(s)) @@ -582,28 +674,24 @@ func getparams(str string) []string { continue } } + start = true + if c == '"' { + quoted ^= 1 + continue + } s = append(s, c) } + if len(s) > 0 { + r = append(r, string(s)) + } 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, ".") objectname = strs[len(strs)-1] - pkgpath = strings.Join(strs[:len(strs)-1], "/") - 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) - } + packageName := "" m.Type = "object" for _, pkg := range astPkgs { for _, fl := range pkg.Files { @@ -612,28 +700,29 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Schema, realTyp if k != objectname { continue } - parseObject(d, k, &m, &realTypes, astPkgs) + packageName = pkg.Name + parseObject(d, k, &m, &realTypes, astPkgs, pkg.Name) } } } } 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 //os.Exit(1) } if len(rootapi.Definitions) == 0 { rootapi.Definitions = make(map[string]swagger.Schema) } + objectname = packageName + "." + objectname rootapi.Definitions[objectname] = m 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) if !ok { - ColorLog("Unknown type without TypeSec: %v\n", d) - os.Exit(1) + logger.Fatalf("Unknown type without TypeSec: %v\n", d) } // TODO support other types, such as `ArrayType`, `MapType`, `InterfaceType` etc... 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 { m.Properties = make(map[string]swagger.Propertie) for _, field := range st.Fields.List { + realType := "" 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) mp := swagger.Propertie{} if isSlice { @@ -661,12 +761,18 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string } } } else { - if isBasicType(realType) { + if sType == "object" { + mp.Ref = "#/definitions/" + realType + } else if isBasicType(realType) { typeFormat := strings.Split(sType, ":") mp.Type = typeFormat[0] mp.Format = typeFormat[1] - } else if sType == "object" { - mp.Ref = "#/definitions/" + realType + } else if realType == "map" { + typeFormat := strings.Split(sType, ":") + mp.AdditionalProperties = &swagger.Propertie{ + Type: typeFormat[0], + Format: typeFormat[1], + } } } if field.Names != nil { @@ -681,7 +787,21 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string } var tagValues []string + 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") if tag != "" { @@ -719,7 +839,7 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string for _, fl := range pkg.Files { for nameOfObj, obj := range fl.Scope.Objects { if obj.Name == fmt.Sprint(field.Type) { - parseObject(obj, nameOfObj, m, realTypes, astPkgs) + 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: return false, fmt.Sprint(t.X), "object" 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 { return false, fmt.Sprint(f.Type), k @@ -761,18 +885,6 @@ func isBasicType(Type string) bool { 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 func grepJSONTag(tag string) string { r, _ := regexp.Compile(`json:"([^"]*)"`) @@ -784,23 +896,16 @@ func grepJSONTag(tag string) string { } // append models -func appendModels(cmpath, pkgpath, controllerName string, realTypes []string) { - var p string - if cmpath != "" { - p = strings.Join(strings.Split(cmpath, "/"), ".") + "." - } else { - p = "" - } +func appendModels(pkgpath, controllerName string, realTypes []string) { for _, realType := range realTypes { if realType != "" && !isBasicType(strings.TrimLeft(realType, "[]")) && !strings.HasPrefix(realType, "map") && !strings.HasPrefix(realType, "&") { - if _, ok := modelsList[pkgpath+controllerName][p+realType]; ok { + if _, ok := modelsList[pkgpath+controllerName][realType]; ok { continue } - //fmt.Printf(pkgpath + ":" + controllerName + ":" + cmpath + ":" + realType + "\n") - _, _, mod, newRealTypes := getModel(p + realType) - modelsList[pkgpath+controllerName][p+realType] = mod - appendModels(cmpath, pkgpath, controllerName, newRealTypes) + _, mod, newRealTypes := getModel(realType) + modelsList[pkgpath+controllerName][realType] = mod + appendModels(pkgpath, controllerName, newRealTypes) } } } @@ -818,3 +923,28 @@ func urlReplace(src string) string { } 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 +} diff --git a/g_hproseappcode.go b/g_hproseappcode.go index f42fa52..a132f64 100644 --- a/g_hproseappcode.go +++ b/g_hproseappcode.go @@ -38,9 +38,7 @@ func generateHproseAppcode(driver, connStr, level, tables, currpath string) { case "3": mode = OModel | OController | ORouter default: - ColorLog("[ERRO] Invalid 'level' option: %s\n", level) - ColorLog("[HINT] Level must be either 1, 2 or 3\n") - os.Exit(2) + logger.Fatal("Invalid 'level' option. Level must be either \"1\", \"2\" or \"3\"") } var selectedTables map[string]bool if tables != "" { @@ -53,12 +51,9 @@ func generateHproseAppcode(driver, connStr, level, tables, currpath string) { case "mysql": case "postgres": case "sqlite": - ColorLog("[ERRO] Generating app code from SQLite database is not supported yet.\n") - os.Exit(2) + logger.Fatal("Generating app code from SQLite database is not supported yet") default: - ColorLog("[ERRO] Unknown database driver: %s\n", driver) - ColorLog("[HINT] Driver must be one of mysql, postgres or sqlite\n") - os.Exit(2) + logger.Fatalf("Unknown database driver '%s'. Driver must be one of mysql, postgres or sqlite", driver) } 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) { db, err := sql.Open(dbms, connStr) if err != nil { - ColorLog("[ERRO] Could not connect to %s database: %s, %s\n", dbms, connStr, err) - os.Exit(2) + logger.Fatalf("Could not connect to '%s' database using '%s': %s", dbms, connStr, err) } defer db.Close() if trans, ok := dbDriver[dbms]; ok { - ColorLog("[INFO] Analyzing database tables...\n") + logger.Info("Analyzing database tables...") tableNames := trans.GetTableNames(db) tables := getTableObjects(tableNames, db, trans) mvcPath := new(MvcPath) @@ -82,8 +76,7 @@ func genHprose(dbms, connStr string, mode byte, selectedTableNames map[string]bo pkgPath := getPackagePath(currpath) writeHproseSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames) } else { - ColorLog("[ERRO] Generating app code from %s database is not supported yet.\n", dbms) - os.Exit(2) + logger.Fatalf("Generating app code from '%s' database is not supported yet", dbms) } } @@ -92,7 +85,7 @@ func genHprose(dbms, connStr string, mode byte, selectedTableNames map[string]bo // Newly geneated files will be inside these folders. func writeHproseSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) { if (OModel & mode) == OModel { - ColorLog("[INFO] Creating model files...\n") + logger.Info("Creating model files...") writeHproseModelFiles(tables, paths.ModelPath, selectedTables) } } @@ -113,21 +106,21 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str var f *os.File var err error 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() { f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) if err != nil { - ColorLog("[WARN] %v\n", err) + logger.Warnf("%s", err) continue } } else { - ColorLog("[WARN] Skipped create file '%s'\n", fpath) + logger.Warnf("Skipped create file '%s'", fpath) continue } } else { f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666) if err != nil { - ColorLog("[WARN] %v\n", err) + logger.Warnf("%s", err) 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, "{{importTimePkg}}", importTimePkg, -1) if _, err := f.WriteString(fileStr); err != nil { - ColorLog("[ERRO] Could not write model file to '%s'\n", fpath) - os.Exit(2) + logger.Fatalf("Could not write model file to '%s'", fpath) } CloseFile(f) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") diff --git a/g_migration.go b/g_migration.go index c0485ef..8ab3f1f 100644 --- a/g_migration.go +++ b/g_migration.go @@ -51,12 +51,12 @@ func (m mysqlDriver) generateSQLFromFields(fields string) string { for i, v := range fds { kv := strings.SplitN(v, ":", 2) if len(kv) != 2 { - ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n") + logger.Error("Fields format is wrong. Should be: key:type,key:type " + v) return "" } typ, tag := m.getSQLType(kv[1]) 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 "" } if i == 0 && strings.ToLower(kv[0]) != "id" { @@ -120,16 +120,16 @@ func (m postgresqlDriver) generateSQLFromFields(fields string) string { for i, v := range fds { kv := strings.SplitN(v, ":", 2) if len(kv) != 2 { - ColorLog("[ERRO] Fields format is wrong. Should be: key:type,key:type " + v + "\n") + logger.Error("Fields format is wrong. Should be: key:type,key:type " + v) return "" } typ, tag := m.getSQLType(kv[1]) 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 "" } if i == 0 && strings.ToLower(kv[0]) != "id" { - sql += "id interger serial primary key," + sql += "id serial primary key," } sql += snakeString(kv[0]) + " " + typ + "," if tag != "" { @@ -177,7 +177,8 @@ func newDBDriver() DBDriver { case "postgres": return postgresqlDriver{} 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) { // create migrations directory if err := os.MkdirAll(migrationFilePath, 0777); err != nil { - ColorLog("[ERRO] Could not create migration directory: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not create migration directory: %s", err) } } // create file @@ -208,8 +208,7 @@ func generateMigration(mname, upsql, downsql, curpath string) { formatSourceCode(fpath) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") } else { - ColorLog("[ERRO] Could not create migration file: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not create migration file: %s", err) } } diff --git a/g_model.go b/g_model.go index 7956034..81b203b 100644 --- a/g_model.go +++ b/g_model.go @@ -35,19 +35,17 @@ func generateModel(mname, fields, currpath string) { modelStruct, hastime, err := getStruct(modelName, fields) if err != nil { - ColorLog("[ERRO] Could not generate the model struct: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not generate the model struct: %s", err) } - ColorLog("[INFO] Using '%s' as model name\n", modelName) - ColorLog("[INFO] Using '%s' as package name\n", packageName) + logger.Infof("Using '%s' as model name", modelName) + logger.Infof("Using '%s' as package name", packageName) fp := path.Join(currpath, "models", p) if _, err := os.Stat(fp); os.IsNotExist(err) { // Create the model's directory if err := os.MkdirAll(fp, 0777); err != nil { - ColorLog("[ERRO] Could not create the model directory: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not create the model directory: %s", err) } } @@ -67,8 +65,7 @@ func generateModel(mname, fields, currpath string) { formatSourceCode(fpath) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m") } else { - ColorLog("[ERRO] Could not create model file: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not create model file: %s", err) } } diff --git a/g_scaffold.go b/g_scaffold.go index cccda16..dd43425 100644 --- a/g_scaffold.go +++ b/g_scaffold.go @@ -3,7 +3,7 @@ package main import "strings" 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 if askForConfirmation() { @@ -11,19 +11,19 @@ func generateScaffold(sname, fields, currpath, driver, conn string) { } // 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() { generateController(sname, currpath) } // 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() { generateView(sname, currpath) } // 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() { upsql := "" downsql := "" @@ -40,9 +40,9 @@ func generateScaffold(sname, fields, currpath, driver, conn string) { } // 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() { 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)) } diff --git a/g_views.go b/g_views.go index c078223..aa7815e 100644 --- a/g_views.go +++ b/g_views.go @@ -25,13 +25,12 @@ import ( func generateView(viewpath, currpath string) { w := NewColorWriter(os.Stdout) - ColorLog("[INFO] Generating view...\n") + logger.Info("Generating view...") absViewPath := path.Join(currpath, "views", viewpath) err := os.MkdirAll(absViewPath, os.ModePerm) if err != nil { - ColorLog("[ERRO] Could not create '%s' view: %s\n", viewpath, err) - os.Exit(2) + logger.Fatalf("Could not create '%s' view: %s", viewpath, err) } cfile := path.Join(absViewPath, "index.tpl") @@ -40,8 +39,7 @@ func generateView(viewpath, currpath string) { f.WriteString(cfile) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m") } else { - ColorLog("[ERRO] Could not create view file: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not create view file: %s", err) } cfile = path.Join(absViewPath, "show.tpl") @@ -50,8 +48,7 @@ func generateView(viewpath, currpath string) { f.WriteString(cfile) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m") } else { - ColorLog("[ERRO] Could not create view file: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not create view file: %s", err) } cfile = path.Join(absViewPath, "create.tpl") @@ -60,8 +57,7 @@ func generateView(viewpath, currpath string) { f.WriteString(cfile) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m") } else { - ColorLog("[ERRO] Could not create view file: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not create view file: %s", err) } cfile = path.Join(absViewPath, "edit.tpl") @@ -70,7 +66,6 @@ func generateView(viewpath, currpath string) { f.WriteString(cfile) fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m") } else { - ColorLog("[ERRO] Could not create view file: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not create view file: %s", err) } } diff --git a/hproseapp.go b/hproseapp.go index e6f7e1c..6b2cf6f 100644 --- a/hproseapp.go +++ b/hproseapp.go @@ -10,7 +10,7 @@ * * * Build rpc application use Hprose base on beego * * * - * LastModified: Oct 13, 2014 * + * LastModified: Oct 31, 2016 * * Author: Liu jian * * * \**********************************************************/ @@ -63,16 +63,41 @@ EnableDocs = true var hproseMaingo = `package main import ( + "fmt" + "reflect" + "{{.Appname}}/models" - "github.com/hprose/hprose-go/hprose" + "github.com/hprose/hprose-golang/rpc" "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() { - 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("GetOne", models.GetOne) + + // Start Service beego.Handler("/", service) beego.Run() } @@ -81,8 +106,11 @@ func main() { var hproseMainconngo = `package main import ( + "fmt" + "reflect" + "{{.Appname}}/models" - "github.com/hprose/hprose-go/hprose" + "github.com/hprose/hprose-golang/rpc" "github.com/astaxie/beego" "github.com/astaxie/beego/orm" @@ -93,9 +121,30 @@ func init() { 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() { - service := hprose.NewHttpService() + // Create WebSocketServer + // service := rpc.NewWebSocketService() + + // Create Http Server + service := rpc.NewHTTPService() + + // Use Logger Middleware + service.AddInvokeHandler(logInvokeHandler) + {{HproseFunctionList}} + + // Start Service beego.Handler("/", service) beego.Run() } @@ -249,14 +298,13 @@ var hproseAddFunctions = []string{} func init() { cmdHproseapp.Run = createhprose + cmdHproseapp.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() } cmdHproseapp.Flag.Var(&tables, "tables", "specify tables to generate model") 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") } func createhprose(cmd *Command, args []string) int { - ShowShortVersionBanner() - w := NewColorWriter(os.Stdout) curpath, _ := os.Getwd() @@ -265,8 +313,7 @@ func createhprose(cmd *Command, args []string) int { } apppath, packpath, err := checkEnv(args[0]) if err != nil { - fmt.Println(err) - os.Exit(2) + logger.Fatalf("%s", err) } if driver == "" { driver = "mysql" @@ -274,7 +321,7 @@ func createhprose(cmd *Command, args []string) int { if conn == "" { } - ColorLog("[INFO] Creating Hprose application...\n") + logger.Info("Creating Hprose application...") os.MkdirAll(apppath, 0755) 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)) if conn != "" { - ColorLog("[INFO] Using '%s' as 'driver'\n", driver) - ColorLog("[INFO] Using '%s' as 'conn'\n", conn) - ColorLog("[INFO] Using '%s' as 'tables'\n", tables) + logger.Infof("Using '%s' as 'driver'", driver) + logger.Infof("Using '%s' as 'conn'", conn) + logger.Infof("Using '%s' as 'tables'", tables) 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") maingoContent := strings.Replace(hproseMainconngo, "{{.Appname}}", packpath, -1) @@ -320,6 +367,6 @@ func createhprose(cmd *Command, args []string) int { WriteToFile(path.Join(apppath, "main.go"), strings.Replace(hproseMaingo, "{{.Appname}}", packpath, -1)) } - ColorLog("[SUCC] New Hprose application successfully created!\n") + logger.Success("New Hprose application successfully created!") return 0 } diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..e9e081f --- /dev/null +++ b/logger.go @@ -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...) +} diff --git a/migrate.go b/migrate.go index a32cc8e..3d89991 100644 --- a/migrate.go +++ b/migrate.go @@ -16,10 +16,10 @@ package main import ( "database/sql" - "fmt" "os" "os/exec" "path" + "runtime" "strconv" "strings" "time" @@ -56,30 +56,31 @@ var mConn docValue func init() { 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(&mConn, "conn", "connection string used by the driver to connect to a database instance") } // runMigration is the entry point for starting a migration func runMigration(cmd *Command, args []string) int { - ShowShortVersionBanner() - currpath, _ := os.Getwd() gps := GetGOPATHs() if len(gps) == 0 { - ColorLog("[ERRO] Fail to start[ %s ]\n", "GOPATH environment variable is not set or empty") - os.Exit(2) + logger.Fatal("GOPATH environment variable is not set or empty") } - gopath := gps[0] - Debugf("GOPATH: %s", gopath) - // load config + gopath := gps[0] + + logger.Debugf("GOPATH: %s", __FILE__(), __LINE__(), gopath) + + // Load the configuration err := loadConfig() 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 { 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" } } - ColorLog("[INFO] Using '%s' as 'driver'\n", mDriver) - ColorLog("[INFO] Using '%s' as 'conn'\n", mConn) + logger.Infof("Using '%s' as 'driver'", mDriver) + logger.Infof("Using '%s' as 'conn'", mConn) driverStr, connStr := string(mDriver), string(mConn) if len(args) == 0 { // run all outstanding migrations - ColorLog("[INFO] Running all outstanding migrations\n") + logger.Info("Running all outstanding migrations") migrateUpdate(currpath, driverStr, connStr) } else { mcmd := args[0] switch mcmd { case "rollback": - ColorLog("[INFO] Rolling back the last migration operation\n") + logger.Info("Rolling back the last migration operation") migrateRollback(currpath, driverStr, connStr) case "reset": - ColorLog("[INFO] Reseting all migrations\n") + logger.Info("Reseting all migrations") migrateReset(currpath, driverStr, connStr) case "refresh": - ColorLog("[INFO] Refreshing all migrations\n") + logger.Info("Refreshing all migrations") migrateRefresh(currpath, driverStr, connStr) default: - ColorLog("[ERRO] Command is missing\n") - os.Exit(2) + logger.Fatal("Command is missing") } } - ColorLog("[SUCC] Migration successful!\n") + logger.Success("Migration successful!") 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 func migrate(goal, currpath, driver, connStr string) { dir := path.Join(currpath, "database", "migrations") - binary := "m" + postfix := "" + if runtime.GOOS == "windows" { + postfix = ".exe" + } + binary := "m" + postfix source := binary + ".go" - // connect to database + + // Connect to database db, err := sql.Open(driver, connStr) if err != nil { - ColorLog("[ERRO] Could not connect to %s: %s\n", driver, connStr) - ColorLog("[ERRO] Error: %v", err.Error()) - os.Exit(2) + logger.Fatalf("Could not connect to database using '%s': %s", connStr, err) } defer db.Close() + checkForSchemaUpdateTable(db, driver) latestName, latestTime := getLatestMigration(db, goal) writeMigrationSourceFile(dir, source, driver, connStr, latestTime, latestName, goal) @@ -170,50 +174,44 @@ func migrate(goal, currpath, driver, connStr string) { func checkForSchemaUpdateTable(db *sql.DB, driver string) { showTableSQL := showMigrationsTableSQL(driver) if rows, err := db.Query(showTableSQL); err != nil { - ColorLog("[ERRO] Could not show migrations table: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not show migrations table: %s", err) } else if !rows.Next() { - // no migrations table, create anew + // No migrations table, create new ones createTableSQL := createMigrationsTableSQL(driver) - ColorLog("[INFO] Creating 'migrations' table...\n") + + logger.Infof("Creating 'migrations' table...") + if _, err := db.Query(createTableSQL); err != nil { - ColorLog("[ERRO] Could not create migrations table: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not create migrations table: %s", err) } } - // checking that migrations table schema are expected + // Checking that migrations table schema are expected selectTableSQL := selectMigrationsTableSQL(driver) if rows, err := db.Query(selectTableSQL); err != nil { - ColorLog("[ERRO] Could not show columns of migrations table: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not show columns of migrations table: %s", err) } else { for rows.Next() { var fieldBytes, typeBytes, nullBytes, keyBytes, defaultBytes, extraBytes []byte if err := rows.Scan(&fieldBytes, &typeBytes, &nullBytes, &keyBytes, &defaultBytes, &extraBytes); err != nil { - ColorLog("[ERRO] Could not read column information: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not read column information: %s", err) } fieldStr, typeStr, nullStr, keyStr, defaultStr, extraStr := string(fieldBytes), string(typeBytes), string(nullBytes), string(keyBytes), string(defaultBytes), string(extraBytes) if fieldStr == "id_migration" { if keyStr != "PRI" || extraStr != "auto_increment" { - ColorLog("[ERRO] Column migration.id_migration type mismatch: KEY: %s, EXTRA: %s\n", keyStr, extraStr) - ColorLog("[HINT] Expecting KEY: PRI, EXTRA: auto_increment\n") - os.Exit(2) + logger.Hint("Expecting KEY: PRI, EXTRA: auto_increment") + logger.Fatalf("Column migration.id_migration type mismatch: KEY: %s, EXTRA: %s", keyStr, extraStr) } } else if fieldStr == "name" { if !strings.HasPrefix(typeStr, "varchar") || nullStr != "YES" { - ColorLog("[ERRO] Column migration.name type mismatch: TYPE: %s, NULL: %s\n", typeStr, nullStr) - ColorLog("[HINT] Expecting TYPE: varchar, NULL: YES\n") - os.Exit(2) + logger.Hint("Expecting TYPE: varchar, NULL: YES") + logger.Fatalf("Column migration.name type mismatch: TYPE: %s, NULL: %s", typeStr, nullStr) } - } else if fieldStr == "created_at" { if typeStr != "timestamp" || defaultStr != "CURRENT_TIMESTAMP" { - ColorLog("[ERRO] Column migration.timestamp type mismatch: TYPE: %s, DEFAULT: %s\n", typeStr, defaultStr) - ColorLog("[HINT] Expecting TYPE: timestamp, DEFAULT: CURRENT_TIMESTAMP\n") - os.Exit(2) + logger.Hint("Expecting TYPE: timestamp, DEFAULT: CURRENT_TIMESTAMP") + logger.Fatalf("Column migration.timestamp type mismatch: TYPE: %s, DEFAULT: %s", typeStr, defaultStr) } } } @@ -257,26 +255,22 @@ func selectMigrationsTableSQL(driver string) string { func getLatestMigration(db *sql.DB, goal string) (file string, createdAt int64) { sql := "SELECT name FROM migrations where status = 'update' ORDER BY id_migration DESC LIMIT 1" if rows, err := db.Query(sql); err != nil { - ColorLog("[ERRO] Could not retrieve migrations: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not retrieve migrations: %s", err) } else { if rows.Next() { if err := rows.Scan(&file); err != nil { - ColorLog("[ERRO] Could not read migrations in database: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not read migrations in database: %s", err) } createdAtStr := file[len(file)-15:] if t, err := time.Parse("20060102_150405", createdAtStr); err != nil { - ColorLog("[ERRO] Could not parse time: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not parse time: %s", err) } else { createdAt = t.Unix() } } else { // migration table has no 'update' record, no point rolling back if goal == "rollback" { - ColorLog("[ERRO] There is nothing to rollback\n") - os.Exit(2) + logger.Fatal("There is nothing to rollback") } 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) { changeDir(dir) 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) - os.Exit(2) + logger.Fatalf("Could not create file: %s", err) } else { content := strings.Replace(MigrationMainTPL, "{{DBDriver}}", driver, -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, "{{Task}}", task, -1) if _, err := f.WriteString(content); err != nil { - ColorLog("[ERRO] Could not write to file: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not write to file: %s", err) } CloseFile(f) } @@ -309,7 +301,7 @@ func buildMigrationBinary(dir, binary string) { changeDir(dir) cmd := exec.Command("go", "build", "-o", binary) 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)) removeTempFile(dir, binary) removeTempFile(dir, binary+".go") @@ -323,7 +315,7 @@ func runMigrationBinary(dir, binary string) { cmd := exec.Command("./" + binary) if out, err := cmd.CombinedOutput(); err != nil { 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+".go") os.Exit(2) @@ -336,8 +328,7 @@ func runMigrationBinary(dir, binary string) { // It exits the system when encouter an error func changeDir(dir string) { if err := os.Chdir(dir); err != nil { - ColorLog("[ERRO] Could not find migration directory: %s\n", err) - os.Exit(2) + logger.Fatalf("Could not find migration directory: %s", err) } } @@ -345,7 +336,7 @@ func changeDir(dir string) { func removeTempFile(dir, file string) { changeDir(dir) 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) { for _, line := range strings.Split(o, "\n") { if line != "" { - ColorLog("[ERRO] -| ") - fmt.Println(line) + logger.Errorf("|> %s", line) } } } @@ -363,13 +353,13 @@ func formatShellErrOutput(o string) { func formatShellOutput(o string) { for _, line := range strings.Split(o, "\n") { if line != "" { - ColorLog("[INFO] -| ") - fmt.Println(line) + logger.Infof("|> %s", line) } } } const ( + // MigrationMainTPL migration main template MigrationMainTPL = `package main import( @@ -409,6 +399,7 @@ func main(){ } ` + // MYSQLMigrationDDL MySQL migration SQL MYSQLMigrationDDL = ` CREATE TABLE migrations ( id_migration int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key', @@ -420,7 +411,7 @@ CREATE TABLE migrations ( PRIMARY KEY (id_migration) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ` - + // POSTGRESMigrationDDL Postgres migration SQL POSTGRESMigrationDDL = ` CREATE TYPE migrations_status AS ENUM('update', 'rollback'); diff --git a/new.go b/new.go index e0ba92f..b8be305 100644 --- a/new.go +++ b/new.go @@ -52,30 +52,30 @@ the following files/directories structure: func init() { cmdNew.Run = createApp + cmdNew.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() } } func createApp(cmd *Command, args []string) int { - ShowShortVersionBanner() w := NewColorWriter(os.Stdout) + if len(args) != 1 { - ColorLog("[ERRO] Argument [appname] is missing\n") - os.Exit(2) + logger.Fatal("Argument [appname] is missing") } + apppath, packpath, err := checkEnv(args[0]) if err != nil { - fmt.Println(err) - os.Exit(2) + logger.Fatalf("%s", err) } if isExist(apppath) { - ColorLog("[ERRO] Path (%s) already exists\n", apppath) - ColorLog("[WARN] Do you want to overwrite it? [Yes|No] ") + logger.Errorf(bold("Application '%s' already exists"), apppath) + logger.Warn(bold("Do you want to overwrite it? [Yes|No] ")) if !askForConfirmation() { os.Exit(2) } } - ColorLog("[INFO] Creating application...\n") + logger.Info("Creating application...") 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") @@ -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") 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 } @@ -171,13 +171,13 @@ func init() { } -// TestMain is a sample to run an endpoint test -func TestMain(t *testing.T) { +// TestBeego is a sample to run an endpoint test +func TestBeego(t *testing.T) { r, _ := http.NewRequest("GET", "/", nil) w := httptest.NewRecorder() 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("Status Code Should Be 200", func() { @@ -302,13 +302,3 @@ var indextpl = ` ` - -// 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) -} diff --git a/pack.go b/pack.go index bdf3581..4e16086 100644 --- a/pack.go +++ b/pack.go @@ -21,7 +21,6 @@ import ( "flag" "fmt" "io" - "io/ioutil" "os" "os/exec" path "path/filepath" @@ -102,14 +101,10 @@ func init() { fs.BoolVar(&verbose, "v", false, "verbose") cmdPack.Flag = *fs cmdPack.Run = packApp + cmdPack.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() } w = NewColorWriter(os.Stdout) } -func exitPrint(con string) { - fmt.Fprintln(os.Stderr, con) - os.Exit(2) -} - type walker interface { isExclude(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, excludeRegexp []*regexp.Regexp, includePath ...string) (err error) { - ColorLog("Excluding relpath prefix: %s\n", strings.Join(excludePrefix, ":")) - ColorLog("Excluding relpath suffix: %s\n", strings.Join(excludeSuffix, ":")) + logger.Infof("Excluding relpath prefix: %s", strings.Join(excludePrefix, ":")) + logger.Infof("Excluding relpath suffix: %s", strings.Join(excludeSuffix, ":")) 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) @@ -454,27 +449,7 @@ func packDirectory(excludePrefix []string, excludeSuffix []string, 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 { - ShowShortVersionBanner() - curPath, _ := os.Getwd() thePath := "" @@ -496,17 +471,13 @@ func packApp(cmd *Command, args []string) int { thePath, err := path.Abs(appPath) 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 { - exitPrint(fmt.Sprintf("App path does not exist: %s", thePath)) + logger.Fatalf("Application path does not exist: %s", thePath) } - if isBeegoProject(thePath) == false { - exitPrint(fmt.Sprintf("Bee does not support non Beego project")) - } - - ColorLog("Packaging application: %s\n", thePath) + logger.Infof("Packaging application on '%s'...", thePath) appName := path.Base(thePath) @@ -526,7 +497,7 @@ func packApp(cmd *Command, args []string) int { os.Mkdir(tmpdir, 0700) if build { - ColorLog("Building application...\n") + logger.Info("Building application...") var envs []string for _, env := range buildEnvs { parts := strings.SplitN(env, "=", 2) @@ -548,7 +519,7 @@ func packApp(cmd *Command, args []string) int { os.Setenv("GOOS", goos) 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) if goos == "windows" { @@ -571,10 +542,10 @@ func packApp(cmd *Command, args []string) int { execmd.Dir = thePath err = execmd.Run() if err != nil { - exitPrint(err.Error()) + logger.Fatal(err.Error()) } - ColorLog("Build successful\n") + logger.Success("Build successful!") } switch format { @@ -592,7 +563,7 @@ func packApp(cmd *Command, args []string) int { if _, err := os.Stat(outputP); err != nil { err = os.MkdirAll(outputP, 0755) 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 { if len(r) > 0 { if re, err := regexp.Compile(r); err != nil { - exitPrint(err.Error()) + logger.Fatal(err.Error()) } else { exr = append(exr, re) } } } + logger.Infof("Writing to output: %s", outputP) + err = packDirectory(exp, exs, exr, tmpdir, thePath) if err != nil { - exitPrint(err.Error()) + logger.Fatal(err.Error()) } - ColorLog("Writing to output: `%s`\n", outputP) + logger.Success("Application packed!") return 0 } diff --git a/run.go b/run.go index 8aab6b5..b7526e2 100644 --- a/run.go +++ b/run.go @@ -15,7 +15,6 @@ package main import ( - "fmt" "io/ioutil" "os" path "path/filepath" @@ -57,6 +56,7 @@ var ( func init() { cmdRun.Run = runApp + cmdRun.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() } cmdRun.Flag.Var(&mainFiles, "main", "specify main go files") cmdRun.Flag.Var(&gendoc, "gendoc", "auto generate the docs") cmdRun.Flag.Var(&downdoc, "downdoc", "auto download swagger file when not exist") @@ -68,8 +68,6 @@ func init() { } func runApp(cmd *Command, args []string) int { - ShowShortVersionBanner() - if len(args) == 0 || args[0] == "watchall" { currpath, _ = os.Getwd() @@ -77,9 +75,8 @@ func runApp(cmd *Command, args []string) int { appname = path.Base(currpath) currentGoPath = _gopath } 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 { // Check if passed Bee application path/name exists in the GOPATH(s) if found, _gopath, _path := SearchGOPATHs(args[0]); found { @@ -87,35 +84,35 @@ func runApp(cmd *Command, args []string) int { currentGoPath = _gopath appname = path.Base(currpath) } 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) { - ColorLog("[WARN] The appname is in conflict with currpath's file, do you want to build appname as %s\n", appname) - ColorLog("[INFO] Do you want to overwrite it? [yes|no]] ") + logger.Warnf("The appname is in conflict with file's current path. Do you want to build appname as '%s'", appname) + logger.Info("Do you want to overwrite it? [yes|no] ") if !askForConfirmation() { 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) - ColorLog("[INFO] Using '%s' as 'runmode'\n", os.Getenv("BEEGO_RUNMODE")) - }else if runmode != ""{ + logger.Infof("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE")) + } else if runmode != "" { os.Setenv("BEEGO_RUNMODE", runmode) - ColorLog("[WARN] Using '%s' as 'runmode'\n", os.Getenv("BEEGO_RUNMODE")) - }else if os.Getenv("BEEGO_RUNMODE") != ""{ - ColorLog("[WARN] Using '%s' as 'runmode'\n", os.Getenv("BEEGO_RUNMODE")) + logger.Warnf("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE")) + } else if os.Getenv("BEEGO_RUNMODE") != "" { + logger.Warnf("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE")) } err := loadConfig() if err != nil { - ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err) + logger.Fatalf("Failed to load configuration: %s", err) } var paths []string @@ -143,10 +140,10 @@ func runApp(cmd *Command, args []string) int { } if gendoc == "true" { NewWatcher(paths, files, true) - Autobuild(files, true) + AutoBuild(files, true) } else { NewWatcher(paths, files, false) - Autobuild(files, false) + AutoBuild(files, false) } for { @@ -202,16 +199,16 @@ func isExcluded(filePath string) bool { for _, p := range excludedPaths { absP, err := path.Abs(p) if err != nil { - ColorLog("[ERROR] Can not get absolute path of [ %s ]\n", p) + logger.Errorf("Cannot get absolute path of '%s'", p) continue } absFilePath, err := path.Abs(filePath) if err != nil { - ColorLog("[ERROR] Can not get absolute path of [ %s ]\n", filePath) + logger.Errorf("Cannot get absolute path of '%s'", filePath) break } if strings.HasPrefix(absFilePath, absP) { - ColorLog("[INFO] Excluding from watching [ %s ]\n", filePath) + logger.Infof("'%s' is not being watched", filePath) return true } } diff --git a/rundocs.go b/rundocs.go index 35eef7e..645d391 100644 --- a/rundocs.go +++ b/rundocs.go @@ -17,7 +17,6 @@ import ( "archive/zip" "fmt" "io" - "log" "net/http" "os" "strings" @@ -54,6 +53,7 @@ var docport docValue func init() { cmdRundocs.Run = runDocs + cmdRundocs.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() } cmdRundocs.Flag.Var(&isDownload, "isDownload", "weather download the Swagger Docs") cmdRundocs.Flag.Var(&docport, "docport", "doc server port") } @@ -63,18 +63,22 @@ func runDocs(cmd *Command, args []string) int { downloadFromURL(swaggerlink, "swagger.zip") err := unzipAndDelete("swagger.zip") if err != nil { - fmt.Println("has err exet unzipAndDelete", err) + logger.Errorf("Error while unzipping 'swagger.zip' file: %s", err) } } if docport == "" { docport = "8089" } if _, err := os.Stat("swagger"); err != nil && os.IsNotExist(err) { - fmt.Println("there's no swagger, please use bee rundocs -isDownload=true downlaod first") - os.Exit(2) + logger.Fatal("No Swagger dist found. Run: bee rundocs -isDownload=true") + } + + 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 } @@ -85,36 +89,36 @@ func downloadFromURL(url, fileName string) { } else if fd.Size() == int64(0) { down = true } else { - ColorLog("[%s] Filename %s already exist\n", INFO, fileName) + logger.Infof("'%s' already exists", fileName) return } 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) if err != nil { - ColorLog("[%s]Error while creating %s: %s\n", ERRO, fileName, err) + logger.Errorf("Error while creating '%s': %s", fileName, err) return } defer output.Close() response, err := http.Get(url) if err != nil { - ColorLog("[%s]Error while downloading %s:%s\n", ERRO, url, err) + logger.Errorf("Error while downloading '%s': %s", url, err) return } defer response.Body.Close() n, err := io.Copy(output, response.Body) if err != nil { - ColorLog("[%s]Error while downloading %s:%s\n", ERRO, url, err) + logger.Errorf("Error while downloading '%s': %s", url, err) return } - ColorLog("[%s] %d bytes downloaded.\n", SUCC, n) + logger.Successf("%d bytes downloaded!", n) } } 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) if err != nil { 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) } diff --git a/test.go b/test.go index de5116d..f8be62b 100644 --- a/test.go +++ b/test.go @@ -31,6 +31,7 @@ var cmdTest = &Command{ func init() { cmdTest.Run = testApp + cmdTest.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() } } func safePathAppend(arr []string, paths ...string) []string { @@ -51,19 +52,19 @@ var started = make(chan bool) func testApp(cmd *Command, args []string) int { if len(args) != 1 { - ColorLog("[ERRO] Cannot start running[ %s ]\n", - "argument 'appname' is missing") - os.Exit(2) + logger.Fatalf("Failed to start: %s", "argument 'appname' is missing") } - crupath, _ := os.Getwd() - Debugf("current path:%s\n", crupath) + + currpath, _ := os.Getwd() + + logger.Debugf("Current path: %s", __FILE__(), __LINE__(), currpath) err := loadConfig() if err != nil { - ColorLog("[ERRO] Fail to parse bee.json[ %s ]\n", err) + logger.Fatalf("Failed to load configuration: %s", err) } var paths []string - readAppDirectories(crupath, &paths) + readAppDirectories(currpath, &paths) NewWatcher(paths, nil, false) appname = args[0] @@ -76,7 +77,7 @@ func testApp(cmd *Command, args []string) int { } func runTest() { - ColorLog("[INFO] Start testing...\n") + logger.Info("Start testing...") time.Sleep(time.Second * 1) crupwd, _ := os.Getwd() testDir := path.Join(crupwd, "tests") @@ -88,14 +89,14 @@ func runTest() { icmd := exec.Command("go", "test") icmd.Stdout = os.Stdout icmd.Stderr = os.Stderr - ColorLog("[TRAC] ============== Test Begin ===================\n") + logger.Info("============== Test Begin ===================") err = icmd.Run() - ColorLog("[TRAC] ============== Test End ===================\n") + logger.Info("============== Test End =====================") if err != nil { - ColorLog("[ERRO] ============== Test failed ===================\n") - ColorLog("[ERRO] %s", err) + logger.Error("============== Test failed ===================") + logger.Errorf("Cause: %s", err) return } - ColorLog("[SUCC] Test finish\n") + logger.Success("Test Completed") } diff --git a/util.go b/util.go index fd2e1ce..a13ade2 100644 --- a/util.go +++ b/util.go @@ -15,14 +15,16 @@ package main import ( - "log" + "fmt" + "io/ioutil" "os" + "os/exec" + "path" "path/filepath" + "regexp" "runtime" "strings" "time" - "path" - "fmt" ) // 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 } -// if os.env DEBUG set, debug is on -func Debugf(format string, a ...interface{}) { - if os.Getenv("DEBUG") != "" { - _, file, line, ok := runtime.Caller(1) - if !ok { - file = "" - line = -1 - } else { - file = filepath.Base(file) - } - fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...) - } +// Now returns the current local time in the specified layout +func Now(layout string) string { + return time.Now().Format(layout) } -const ( - Gray = uint8(iota + 90) - Red - 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: [ 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 - } +// EndLine returns the a newline escape character +func EndLine() string { + return "\n" } // IsExist returns whether a file or directory exists. @@ -174,11 +66,52 @@ func GetGOPATHs() []string { 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) { gps := GetGOPATHs() if len(gps) == 0 { - ColorLog("[ERRO] Fail to start [ %s ]\n", "GOPATH environment variable is not set or empty") - os.Exit(2) + logger.Fatal("GOPATH environment variable is not set or empty") } // Lookup the application inside the user workspace(s) @@ -193,9 +126,6 @@ func SearchGOPATHs(app string) (bool, string, string) { } if isExist(currentPath) { - if !isBeegoProject(currentPath) { - continue - } return true, gopath, currentPath } } @@ -211,7 +141,7 @@ func askForConfirmation() bool { var response string _, err := fmt.Scanln(&response) if err != nil { - log.Fatal(err) + logger.Fatalf("%s", err) } okayResponses := []string{"y", "Y", "yes", "Yes", "YES"} nokayResponses := []string{"n", "N", "no", "No", "NO"} @@ -249,7 +179,7 @@ func snakeString(s string) string { } data = append(data, d) } - return strings.ToLower(string(data[:len(data)])) + return strings.ToLower(string(data[:])) } func camelString(s string) string { @@ -273,7 +203,25 @@ func camelString(s string) string { } 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 @@ -291,7 +239,45 @@ func (s *strFlags) Set(value string) error { // CloseFile attempts to close the passed file // or panics with the actual error 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) } } + +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 +} diff --git a/version.go b/version.go index d9442aa..60dbfd7 100644 --- a/version.go +++ b/version.go @@ -6,8 +6,10 @@ import ( "fmt" "io" "os" + "os/exec" path "path/filepath" "regexp" + "strings" ) var cmdVersion = &Command{ @@ -76,7 +78,7 @@ func getBeegoVersion() string { return "" } if gopath == "" { - err = fmt.Errorf("You should set GOPATH env variable") + err = fmt.Errorf("You need to set GOPATH environment variable") return "" } wgopath := path.SplitList(gopath) @@ -88,11 +90,11 @@ func getBeegoVersion() string { if os.IsNotExist(err) { continue } - ColorLog("[ERRO] Get `beego.go` has error\n") + logger.Error("Error while getting stats of 'beego.go'") } fd, err := os.Open(filename) if err != nil { - ColorLog("[ERRO] Open `beego.go` has error\n") + logger.Error("Error while reading 'beego.go'") continue } 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] } diff --git a/watch.go b/watch.go index efde48a..a030c3b 100644 --- a/watch.go +++ b/watch.go @@ -16,8 +16,6 @@ package main import ( "bytes" - "fmt" - "github.com/howeyc/fsnotify" "os" "os/exec" "regexp" @@ -25,6 +23,8 @@ import ( "strings" "sync" "time" + + "github.com/howeyc/fsnotify" ) var ( @@ -34,11 +34,11 @@ var ( scheduleTime time.Time ) +// NewWatcher starts an fsnotify Watcher on the specified paths func NewWatcher(paths []string, files []string, isgenerate bool) { watcher, err := fsnotify.NewWatcher() if err != nil { - ColorLog("[ERRO] Fail to create new Watcher[ %s ]\n", err) - os.Exit(2) + logger.Fatalf("Failed to create watcher: %s", err) } go func() { @@ -57,14 +57,14 @@ func NewWatcher(paths []string, files []string, isgenerate bool) { mt := getFileModTime(e.Name) if t := eventTime[e.Name]; mt == t { - ColorLog("[SKIP] # %s #\n", e.String()) + logger.Infof(bold("Skipping: ")+"%s", e.String()) isbuild = false } eventTime[e.Name] = mt if isbuild { - ColorLog("[EVEN] %s\n", e) + logger.Infof("Event fired: %s", e) go func() { // Wait 1s before autobuild util there is no file change. scheduleTime = time.Now().Add(1 * time.Second) @@ -76,22 +76,21 @@ func NewWatcher(paths []string, files []string, isgenerate bool) { return } - Autobuild(files, isgenerate) + AutoBuild(files, isgenerate) }() } 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 { - ColorLog("[TRAC] Directory( %s )\n", path) + logger.Infof(bold("Watching: ")+"%s", path) err = watcher.Watch(path) if err != nil { - ColorLog("[ERRO] Fail to watch directory[ %s ]\n", err) - os.Exit(2) + logger.Fatalf("Failed to watch directory: %s", err) } } @@ -102,25 +101,26 @@ func getFileModTime(path string) int64 { path = strings.Replace(path, "\\", "/", -1) f, err := os.Open(path) 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() } defer f.Close() fi, err := f.Stat() 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 fi.ModTime().Unix() } -func Autobuild(files []string, isgenerate bool) { +// AutoBuild builds the specified set of files +func AutoBuild(files []string, isgenerate bool) { state.Lock() defer state.Unlock() - ColorLog("[INFO] Start building...\n") + logger.Info("Start building...") os.Chdir(currpath) @@ -162,7 +162,7 @@ func Autobuild(files []string, isgenerate bool) { icmd.Stdout = os.Stdout icmd.Stderr = os.Stderr icmd.Run() - ColorLog("============== generate docs ===================\n") + logger.Info("============== Generate Docs ===================") } if err == nil { @@ -186,35 +186,38 @@ func Autobuild(files []string, isgenerate bool) { } if err != nil { - ColorLog("[ERRO] ============== Build failed ===================\n") + logger.Error("============== Build Failed ===================") return } - ColorLog("[SUCC] Build was successful\n") + logger.Success("Built Successfully!") Restart(appname) } +// Kill kills the running command process func Kill() { defer func() { if e := recover(); e != nil { - fmt.Println("Kill.recover -> ", e) + logger.Infof("Kill recover: %s", e) } }() if cmd != nil && cmd.Process != nil { err := cmd.Process.Kill() 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) { - Debugf("kill running process") + logger.Debugf("Kill running process", __FILE__(), __LINE__()) Kill() go Start(appname) } +// Start starts the command process func Start(appname string) { - ColorLog("[INFO] Restarting %s ...\n", appname) + logger.Infof("Restarting '%s'...", appname) if strings.Index(appname, "./") == -1 { appname = "./" + appname } @@ -226,23 +229,22 @@ func Start(appname string) { cmd.Env = append(os.Environ(), conf.Envs...) go cmd.Run() - ColorLog("[INFO] %s is running...\n", appname) + logger.Successf("'%s' is running...", appname) started <- true } -// Should ignore filenames generated by -// Emacs, Vim or SublimeText +// shouldIgnoreFile ignores filenames generated by Emacs, Vim or SublimeText. +// It returns true if the file should be ignored, false otherwise. func shouldIgnoreFile(filename string) bool { for _, regex := range ignoredFilesRegExps { r, err := regexp.Compile(regex) if err != nil { - panic("Could not compile the regex: " + regex) + logger.Fatalf("Could not compile regular expression: %s", err) } if r.MatchString(filename) { return true - } else { - continue } + continue } return false }