mirror of
https://github.com/beego/bee.git
synced 2024-12-22 15:10:49 +00:00
commit
2ff99d4ea7
116
apiapp.go
116
apiapp.go
@ -24,39 +24,34 @@ import (
|
||||
var cmdApiapp = &Command{
|
||||
// CustomFlags: true,
|
||||
UsageLine: "api [appname]",
|
||||
Short: "create an API beego application",
|
||||
Short: "Creates a Beego API application",
|
||||
Long: `
|
||||
Create an API beego application.
|
||||
The command 'api' creates a Beego API application.
|
||||
|
||||
bee api [appname] [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test]
|
||||
-tables: a list of table names separated by ',' (default is empty, indicating all tables)
|
||||
-driver: [mysql | postgres | sqlite] (default: mysql)
|
||||
-conn: the connection string used by the driver, the default is ''
|
||||
e.g. for mysql: root:@tcp(127.0.0.1:3306)/test
|
||||
e.g. for postgres: postgres://postgres:postgres@127.0.0.1:5432/postgres
|
||||
{{"Example:"|bold}}
|
||||
$ bee api [appname] [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test]
|
||||
|
||||
If 'conn' argument is empty, bee api creates an example API application,
|
||||
when 'conn' argument is provided, bee api generates an API application based
|
||||
on the existing database.
|
||||
If 'conn' argument is empty, the command will generate an example API application. Otherwise the command
|
||||
will connect to your database and generate models based on the existing tables.
|
||||
|
||||
The command 'api' creates a folder named [appname] and inside the folder deploy
|
||||
the following files/directories structure:
|
||||
|
||||
├── conf
|
||||
│ └── app.conf
|
||||
├── controllers
|
||||
│ └── object.go
|
||||
│ └── user.go
|
||||
├── routers
|
||||
│ └── router.go
|
||||
├── tests
|
||||
│ └── default_test.go
|
||||
├── main.go
|
||||
└── models
|
||||
└── object.go
|
||||
└── user.go
|
||||
The command 'api' creates a folder named [appname] with the following structure:
|
||||
|
||||
├── main.go
|
||||
├── {{"conf"|foldername}}
|
||||
│ └── app.conf
|
||||
├── {{"controllers"|foldername}}
|
||||
│ └── object.go
|
||||
│ └── user.go
|
||||
├── {{"routers"|foldername}}
|
||||
│ └── router.go
|
||||
├── {{"tests"|foldername}}
|
||||
│ └── default_test.go
|
||||
└── {{"models"|foldername}}
|
||||
└── object.go
|
||||
└── user.go
|
||||
`,
|
||||
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
|
||||
Run: createapi,
|
||||
}
|
||||
|
||||
var apiconf = `appname = {{.Appname}}
|
||||
@ -537,20 +532,16 @@ func TestGet(t *testing.T) {
|
||||
`
|
||||
|
||||
func init() {
|
||||
cmdApiapp.Run = createapi
|
||||
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")
|
||||
cmdApiapp.Flag.Var(&tables, "tables", "List of table names separated by a comma.")
|
||||
cmdApiapp.Flag.Var(&driver, "driver", "Database driver. Either mysql, postgres or sqlite.")
|
||||
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)
|
||||
output := cmd.Out()
|
||||
|
||||
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 +550,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,22 +558,22 @@ 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")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath, "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "conf"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf"), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "controllers"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers"), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "tests"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests"), "\x1b[0m")
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "conf", "app.conf"),
|
||||
strings.Replace(apiconf, "{{.Appname}}", path.Base(args[0]), -1))
|
||||
|
||||
if conn != "" {
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
|
||||
maingoContent := strings.Replace(apiMainconngo, "{{.Appname}}", packpath, -1)
|
||||
maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(driver), -1)
|
||||
if driver == "mysql" {
|
||||
@ -599,51 +589,50 @@ 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)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models"), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "routers"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers")+string(path.Separator), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers")+string(path.Separator), "\x1b[0m")
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "object.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "object.go"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "controllers", "object.go"),
|
||||
strings.Replace(apiControllers, "{{.Appname}}", packpath, -1))
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "user.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "user.go"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "controllers", "user.go"),
|
||||
strings.Replace(apiControllers2, "{{.Appname}}", packpath, -1))
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests", "default_test.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests", "default_test.go"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "tests", "default_test.go"),
|
||||
strings.Replace(apiTests, "{{.Appname}}", packpath, -1))
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers", "router.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers", "router.go"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "routers", "router.go"),
|
||||
strings.Replace(apirouter, "{{.Appname}}", packpath, -1))
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "object.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "object.go"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "models", "object.go"), apiModels)
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "user.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "user.go"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "models", "user.go"), apiModels2)
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "main.go"),
|
||||
strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1))
|
||||
}
|
||||
ColorLog("[SUCC] New API successfully created!\n")
|
||||
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 +647,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)), "/")
|
||||
|
@ -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
|
||||
|
61
bale.go
61
bale.go
@ -28,41 +28,35 @@ import (
|
||||
|
||||
var cmdBale = &Command{
|
||||
UsageLine: "bale",
|
||||
Short: "packs non-Go files to Go source files",
|
||||
Long: `
|
||||
Bale command compress all the static files in to a single binary file.
|
||||
Short: "Transforms non-Go files to Go source files",
|
||||
Long: `Bale command compress all the static files in to a single binary file.
|
||||
|
||||
This is usefull 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.
|
||||
This is mainly used for zealots who are requiring 100% Go code.
|
||||
This is useful to not have to carry static files including js, css, images and
|
||||
views when deploying a Web application.
|
||||
|
||||
It will auto-generate an unpack function to the main package then run it during the runtime.
|
||||
This is mainly used for zealots who are requiring 100% Go code.
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdBale.Run = runBale
|
||||
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
|
||||
Run: runBale,
|
||||
}
|
||||
|
||||
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 +68,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 +143,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 +160,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 +193,7 @@ func filterSuffix(name string) bool {
|
||||
}
|
||||
|
||||
const (
|
||||
// Header ...
|
||||
Header = `package bale
|
||||
|
||||
import(
|
||||
@ -212,6 +204,7 @@ import(
|
||||
|
||||
func R%s() []byte {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer([]byte{`
|
||||
// Footer ...
|
||||
Footer = `
|
||||
}))
|
||||
|
||||
@ -229,6 +222,7 @@ func R%s() []byte {
|
||||
|
||||
var newline = []byte{'\n'}
|
||||
|
||||
// ByteWriter ...
|
||||
type ByteWriter struct {
|
||||
io.Writer
|
||||
c int
|
||||
@ -244,12 +238,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
|
||||
}
|
||||
|
23
banner.go
23
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)
|
||||
}
|
||||
|
144
bee.go
144
bee.go
@ -18,29 +18,33 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const version = "1.5.2"
|
||||
const version = "1.6.0"
|
||||
|
||||
// Command is the unit of execution
|
||||
type Command struct {
|
||||
// Run runs the command.
|
||||
// The args are the arguments after the command name.
|
||||
Run func(cmd *Command, args []string) int
|
||||
|
||||
// PreRun performs an operation before running the command
|
||||
PreRun func(cmd *Command, args []string)
|
||||
|
||||
// UsageLine is the one-line usage message.
|
||||
// The first word in the line is taken to be the command name.
|
||||
UsageLine string
|
||||
|
||||
// Short is the short description shown in the 'go help' output.
|
||||
Short template.HTML
|
||||
Short string
|
||||
|
||||
// Long is the long message shown in the 'go help <this-command>' output.
|
||||
Long template.HTML
|
||||
Long string
|
||||
|
||||
// Flag is a set of flags specific to this command.
|
||||
Flag flag.FlagSet
|
||||
@ -48,6 +52,9 @@ type Command struct {
|
||||
// CustomFlags indicates that the command will do its own
|
||||
// flag parsing.
|
||||
CustomFlags bool
|
||||
|
||||
// output out writer if set in SetOutput(w)
|
||||
output *io.Writer
|
||||
}
|
||||
|
||||
// Name returns the command's name: the first word in the usage line.
|
||||
@ -60,9 +67,24 @@ func (c *Command) Name() string {
|
||||
return name
|
||||
}
|
||||
|
||||
// SetOutput sets the destination for usage and error messages.
|
||||
// If output is nil, os.Stderr is used.
|
||||
func (c *Command) SetOutput(output io.Writer) {
|
||||
c.output = &output
|
||||
}
|
||||
|
||||
// Out returns the out writer of the current command.
|
||||
// If cmd.output is nil, os.Stderr is used.
|
||||
func (c *Command) Out() io.Writer {
|
||||
if c.output != nil {
|
||||
return *c.output
|
||||
}
|
||||
return NewColorWriter(os.Stderr)
|
||||
}
|
||||
|
||||
// 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)))
|
||||
tmpl(cmdUsage, c)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
@ -72,7 +94,25 @@ func (c *Command) Runnable() bool {
|
||||
return c.Run != nil
|
||||
}
|
||||
|
||||
var commands = []*Command{
|
||||
func (c *Command) Options() map[string]string {
|
||||
options := make(map[string]string)
|
||||
c.Flag.VisitAll(func(f *flag.Flag) {
|
||||
defaultVal := f.DefValue
|
||||
if len(defaultVal) > 0 {
|
||||
if strings.Contains(defaultVal, ":") {
|
||||
// Truncate the flag's default value by appending '...' at the end
|
||||
options[f.Name+"="+strings.Split(defaultVal, ":")[0]+":..."] = f.Usage
|
||||
} else {
|
||||
options[f.Name+"="+defaultVal] = f.Usage
|
||||
}
|
||||
} else {
|
||||
options[f.Name] = f.Usage
|
||||
}
|
||||
})
|
||||
return options
|
||||
}
|
||||
|
||||
var availableCommands = []*Command{
|
||||
cmdNew,
|
||||
cmdRun,
|
||||
cmdPack,
|
||||
@ -88,12 +128,17 @@ var commands = []*Command{
|
||||
cmdFix,
|
||||
}
|
||||
|
||||
var logger = GetBeeLogger(os.Stdout)
|
||||
|
||||
func main() {
|
||||
currentpath, _ := os.Getwd()
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
log.SetFlags(0)
|
||||
|
||||
args := flag.Args()
|
||||
|
||||
if len(args) < 1 {
|
||||
usage()
|
||||
}
|
||||
@ -103,7 +148,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
for _, cmd := range commands {
|
||||
for _, cmd := range availableCommands {
|
||||
if cmd.Name() == args[0] && cmd.Run != nil {
|
||||
cmd.Flag.Usage = func() { cmd.Usage() }
|
||||
if cmd.CustomFlags {
|
||||
@ -112,77 +157,92 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "bee: unknown subcommand %q\nRun 'bee help' for usage.\n", args[0])
|
||||
os.Exit(2)
|
||||
printErrorAndExit("Unknown subcommand")
|
||||
}
|
||||
|
||||
var usageTemplate = `Bee is a tool for managing beego framework.
|
||||
var usageTemplate = `Bee is a Fast and Flexible tool for managing your Beego Web Application.
|
||||
|
||||
Usage:
|
||||
{{"USAGE" | headline}}
|
||||
{{"bee command [arguments]" | bold}}
|
||||
|
||||
bee command [arguments]
|
||||
|
||||
The commands are:
|
||||
{{"AVAILABLE COMMANDS" | headline}}
|
||||
{{range .}}{{if .Runnable}}
|
||||
{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
|
||||
{{.Name | printf "%-11s" | bold}} {{.Short}}{{end}}{{end}}
|
||||
|
||||
Use "bee help [command]" for more information about a command.
|
||||
Use {{"bee help [command]" | bold}} for more information about a command.
|
||||
|
||||
Additional help topics:
|
||||
{{"ADDITIONAL HELP TOPICS" | headline}}
|
||||
{{range .}}{{if not .Runnable}}
|
||||
{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
|
||||
|
||||
Use "bee help [topic]" for more information about that topic.
|
||||
|
||||
Use {{"bee help [topic]" | bold}} for more information about that topic.
|
||||
`
|
||||
|
||||
var helpTemplate = `{{if .Runnable}}usage: bee {{.UsageLine}}
|
||||
|
||||
{{end}}{{.Long | trim}}
|
||||
var helpTemplate = `{{"USAGE" | headline}}
|
||||
{{.UsageLine | printf "bee %s" | bold}}
|
||||
{{if .Options}}{{endline}}{{"OPTIONS" | headline}}{{range $k,$v := .Options}}
|
||||
{{$k | printf "-%-12s" | bold}} {{$v}}{{end}}{{endline}}{{end}}
|
||||
{{"DESCRIPTION" | headline}}
|
||||
{{tmpltostr .Long . | trim}}
|
||||
`
|
||||
|
||||
var errorTemplate = `bee: %s.
|
||||
Use {{"bee help" | bold}} for more information.
|
||||
`
|
||||
|
||||
var cmdUsage = `Use {{printf "bee help %s" .Name | bold}} for more information.{{endline}}`
|
||||
|
||||
func usage() {
|
||||
tmpl(os.Stdout, usageTemplate, commands)
|
||||
tmpl(usageTemplate, availableCommands)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func tmpl(w io.Writer, text string, data interface{}) {
|
||||
t := template.New("top")
|
||||
t.Funcs(template.FuncMap{"trim": func(s template.HTML) template.HTML {
|
||||
return template.HTML(strings.TrimSpace(string(s)))
|
||||
}})
|
||||
func tmpl(text string, data interface{}) {
|
||||
output := NewColorWriter(os.Stderr)
|
||||
|
||||
t := template.New("usage").Funcs(BeeFuncMap())
|
||||
template.Must(t.Parse(text))
|
||||
if err := t.Execute(w, data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err := t.Execute(output, data)
|
||||
MustCheck(err)
|
||||
}
|
||||
|
||||
func help(args []string) {
|
||||
if len(args) == 0 {
|
||||
usage()
|
||||
// not exit 2: succeeded at 'go help'.
|
||||
return
|
||||
}
|
||||
if len(args) != 1 {
|
||||
fmt.Fprintf(os.Stdout, "usage: bee help command\n\nToo many arguments given.\n")
|
||||
os.Exit(2) // failed at 'bee help'
|
||||
printErrorAndExit("Too many arguments")
|
||||
}
|
||||
|
||||
arg := args[0]
|
||||
|
||||
for _, cmd := range commands {
|
||||
for _, cmd := range availableCommands {
|
||||
if cmd.Name() == arg {
|
||||
tmpl(os.Stdout, helpTemplate, cmd)
|
||||
// not exit 2: succeeded at 'go help cmd'.
|
||||
tmpl(helpTemplate, cmd)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Unknown help topic %#q. Run 'bee help'.\n", arg)
|
||||
os.Exit(2) // failed at 'bee help cmd'
|
||||
printErrorAndExit("Unknown help topic")
|
||||
}
|
||||
|
||||
func printErrorAndExit(message string) {
|
||||
tmpl(fmt.Sprintf(errorTemplate, message), nil)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
4
code.go
4
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 {
|
||||
|
99
color.go
99
color.go
@ -14,7 +14,10 @@
|
||||
|
||||
package main
|
||||
|
||||
import "io"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type outputMode int
|
||||
|
||||
@ -49,3 +52,97 @@ 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)
|
||||
}
|
||||
|
||||
// Black returns a black string
|
||||
func Black(message string) string {
|
||||
return fmt.Sprintf("\x1b[30m%s\x1b[0m", message)
|
||||
}
|
||||
|
||||
// White returns a white string
|
||||
func White(message string) string {
|
||||
return fmt.Sprintf("\x1b[37m%s\x1b[0m", 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)
|
||||
}
|
||||
|
||||
// BlackBold returns a black bold string
|
||||
func BlackBold(message string) string {
|
||||
return fmt.Sprintf("\x1b[30m%s\x1b[0m", bold(message))
|
||||
}
|
||||
|
||||
// WhiteBold returns a white bold string
|
||||
func WhiteBold(message string) string {
|
||||
return fmt.Sprintf("\x1b[37m%s\x1b[0m", bold(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))
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
109
conf.go
109
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
|
||||
}
|
||||
|
35
fix.go
35
fix.go
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
@ -8,31 +9,33 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var cmdFix = &Command{
|
||||
UsageLine: "fix",
|
||||
Short: "fix the beego application to make it compatible with beego 1.6",
|
||||
Long: `
|
||||
As from beego1.6, there's some incompatible code with the old version.
|
||||
Short: "Fixes your application by making it compatible with newer versions of Beego",
|
||||
Long: `As of {{"Beego 1.6"|bold}}, there are some backward compatibility issues.
|
||||
|
||||
bee fix help to upgrade the application to beego 1.6
|
||||
The command 'fix' will try to solve those issues by upgrading your code base
|
||||
to be compatible with Beego version 1.6+.
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdFix.Run = runFix
|
||||
cmdFix.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
|
||||
}
|
||||
|
||||
func runFix(cmd *Command, args []string) int {
|
||||
ShowShortVersionBanner()
|
||||
output := cmd.Out()
|
||||
|
||||
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(), ".") {
|
||||
@ -47,13 +50,13 @@ func runFix(cmd *Command, args []string) int {
|
||||
return nil
|
||||
}
|
||||
err = fixFile(path)
|
||||
fmt.Println("\tfix\t", path)
|
||||
fmt.Fprintf(output, GreenBold("\tfix\t")+"%s\n", 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 +170,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 +202,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)
|
||||
|
135
g.go
135
g.go
@ -20,45 +20,42 @@ import (
|
||||
)
|
||||
|
||||
var cmdGenerate = &Command{
|
||||
UsageLine: "generate [Command]",
|
||||
Short: "source code generator",
|
||||
Long: `
|
||||
bee generate scaffold [scaffoldname] [-fields=""] [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
The generate scaffold command will do a number of things for you.
|
||||
-fields: a list of table fields. Format: field:type, ...
|
||||
-driver: [mysql | postgres | sqlite], the default is mysql
|
||||
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test
|
||||
example: bee generate scaffold post -fields="title:string,body:text"
|
||||
UsageLine: "generate [command]",
|
||||
Short: "Source code generator",
|
||||
Long: `▶ {{"To scaffold out your entire application:"|bold}}
|
||||
|
||||
bee generate model [modelname] [-fields=""]
|
||||
generate RESTFul model based on fields
|
||||
-fields: a list of table fields. Format: field:type, ...
|
||||
$ bee generate scaffold [scaffoldname] [-fields="title:string,body:text"] [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
|
||||
bee generate controller [controllerfile]
|
||||
generate RESTful controllers
|
||||
▶ {{"To generate a Model based on fields:"|bold}}
|
||||
|
||||
bee generate view [viewpath]
|
||||
generate CRUD view in viewpath
|
||||
$ bee generate model [modelname] [-fields="name:type"]
|
||||
|
||||
bee generate migration [migrationfile] [-fields=""]
|
||||
generate migration file for making database schema update
|
||||
-fields: a list of table fields. Format: field:type, ...
|
||||
▶ {{"To generate a controller:"|bold}}
|
||||
|
||||
bee generate docs
|
||||
generate swagger doc file
|
||||
$ bee generate controller [controllerfile]
|
||||
|
||||
bee generate test [routerfile]
|
||||
generate testcase
|
||||
▶ {{"To generate a CRUD view:"|bold}}
|
||||
|
||||
bee generate appcode [-tables=""] [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"] [-level=3]
|
||||
generate appcode based on an existing database
|
||||
-tables: a list of table names separated by ',', default is empty, indicating all tables
|
||||
-driver: [mysql | postgres | sqlite], the default is mysql
|
||||
-conn: the connection string used by the driver.
|
||||
default for mysql: root:@tcp(127.0.0.1:3306)/test
|
||||
default for postgres: postgres://postgres:postgres@127.0.0.1:5432/postgres
|
||||
-level: [1 | 2 | 3], 1 = models; 2 = models,controllers; 3 = models,controllers,router
|
||||
$ bee generate view [viewpath]
|
||||
|
||||
▶ {{"To generate a migration file for making database schema updates:"|bold}}
|
||||
|
||||
$ bee generate migration [migrationfile] [-fields="name:type"]
|
||||
|
||||
▶ {{"To generate swagger doc file:"|bold}}
|
||||
|
||||
$ bee generate docs
|
||||
|
||||
▶ {{"To generate a test case:"|bold}}
|
||||
|
||||
$ bee generate test [routerfile]
|
||||
|
||||
▶ {{"To generate appcode based on an existing database:"|bold}}
|
||||
|
||||
$ bee generate appcode [-tables=""] [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"] [-level=3]
|
||||
`,
|
||||
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
|
||||
Run: generateCode,
|
||||
}
|
||||
|
||||
var driver docValue
|
||||
@ -68,42 +65,38 @@ var tables docValue
|
||||
var fields docValue
|
||||
|
||||
func init() {
|
||||
cmdGenerate.Run = generateCode
|
||||
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")
|
||||
cmdGenerate.Flag.Var(&level, "level", "1 = models only; 2 = models and controllers; 3 = models, controllers and routers")
|
||||
cmdGenerate.Flag.Var(&fields, "fields", "specify the fields want to generate.")
|
||||
cmdGenerate.Flag.Var(&tables, "tables", "List of table names separated by a comma.")
|
||||
cmdGenerate.Flag.Var(&driver, "driver", "Database driver. Either mysql, postgres or sqlite.")
|
||||
cmdGenerate.Flag.Var(&conn, "conn", "Connection string used by the driver to connect to a database instance.")
|
||||
cmdGenerate.Flag.Var(&level, "level", "Either 1, 2 or 3. i.e. 1=models; 2=models and controllers; 3=models, controllers and routers.")
|
||||
cmdGenerate.Flag.Var(&fields, "fields", "List of table fields.")
|
||||
}
|
||||
|
||||
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 +112,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 +145,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 +172,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 +190,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
|
||||
}
|
||||
|
202
g_appcode.go
202
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 <null> 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 <null> 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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
286
g_docs.go
286
g_docs.go
@ -26,7 +26,6 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
@ -50,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{})
|
||||
@ -57,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") {
|
||||
@ -89,18 +159,18 @@ func generateDocs(curpath string) {
|
||||
rootapi.Infos.Contact.Name = strings.TrimSpace(s[len("@Name"):])
|
||||
} else if strings.HasPrefix(s, "@URL") {
|
||||
rootapi.Infos.Contact.URL = strings.TrimSpace(s[len("@URL"):])
|
||||
} else if strings.HasPrefix(s, "@License") {
|
||||
if rootapi.Infos.License == nil {
|
||||
rootapi.Infos.License = &swagger.License{Name: strings.TrimSpace(s[len("@License"):])}
|
||||
} else {
|
||||
rootapi.Infos.License.Name = strings.TrimSpace(s[len("@License"):])
|
||||
}
|
||||
} else if strings.HasPrefix(s, "@LicenseUrl") {
|
||||
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") {
|
||||
@ -109,13 +179,14 @@ func generateDocs(curpath string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
@ -125,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
|
||||
}
|
||||
@ -138,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, "/"),
|
||||
@ -154,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
|
||||
@ -191,8 +262,8 @@ func generateDocs(curpath string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@ -206,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)
|
||||
@ -254,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
|
||||
@ -270,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 := ""
|
||||
|
||||
@ -288,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 {
|
||||
@ -308,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)
|
||||
}
|
||||
}
|
||||
@ -318,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()
|
||||
}
|
||||
@ -332,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
|
||||
@ -401,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:]
|
||||
@ -414,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{
|
||||
@ -439,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] {
|
||||
@ -454,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,
|
||||
}
|
||||
@ -468,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 := ""
|
||||
@ -485,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"
|
||||
@ -498,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)
|
||||
@ -587,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))
|
||||
@ -604,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 {
|
||||
@ -634,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)
|
||||
@ -666,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 {
|
||||
@ -709,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 != "" {
|
||||
@ -747,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -793,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:"([^"]*)"`)
|
||||
@ -816,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -850,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
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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,12 +120,12 @@ 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" {
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
13
g_model.go
13
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
17
g_views.go
17
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)
|
||||
}
|
||||
}
|
||||
|
158
hproseapp.go
158
hproseapp.go
@ -1,19 +1,16 @@
|
||||
/**********************************************************\
|
||||
| |
|
||||
| hprose |
|
||||
| |
|
||||
| Official WebSite: http://www.hprose.com/ |
|
||||
| http://www.hprose.org/ |
|
||||
| |
|
||||
\**********************************************************/
|
||||
/**********************************************************\
|
||||
* *
|
||||
* Build rpc application use Hprose base on beego *
|
||||
* *
|
||||
* LastModified: Oct 13, 2014 *
|
||||
* Author: Liu jian <laoliu@lanmv.com> *
|
||||
* *
|
||||
\**********************************************************/
|
||||
// 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
|
||||
|
||||
@ -27,30 +24,28 @@ import (
|
||||
var cmdHproseapp = &Command{
|
||||
// CustomFlags: true,
|
||||
UsageLine: "hprose [appname]",
|
||||
Short: "create an rpc application use hprose base on beego framework",
|
||||
Short: "Creates an RPC application based on Hprose and Beego frameworks",
|
||||
Long: `
|
||||
create an rpc application use hprose base on beego framework
|
||||
The command 'hprose' creates an RPC application based on both Beego and Hprose (http://hprose.com/).
|
||||
|
||||
bee hprose [appname] [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test]
|
||||
-tables: a list of table names separated by ',', default is empty, indicating all tables
|
||||
-driver: [mysql | postgres | sqlite], the default is mysql
|
||||
-conn: the connection string used by the driver, the default is ''
|
||||
e.g. for mysql: root:@tcp(127.0.0.1:3306)/test
|
||||
e.g. for postgres: postgres://postgres:postgres@127.0.0.1:5432/postgres
|
||||
{{"To scaffold out your application, use:"|bold}}
|
||||
|
||||
if conn is empty will create a example rpc application. otherwise generate rpc application use hprose based on an existing database.
|
||||
$ bee hprose [appname] [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test]
|
||||
|
||||
In the current path, will create a folder named [appname]
|
||||
If 'conn' is empty, the command will generate a sample application. Otherwise the command
|
||||
will connect to your database and generate models based on the existing tables.
|
||||
|
||||
In the appname folder has the follow struct:
|
||||
The command 'hprose' creates a folder named [appname] with the following structure:
|
||||
|
||||
├── conf
|
||||
│ └── app.conf
|
||||
├── main.go
|
||||
└── models
|
||||
└── object.go
|
||||
└── user.go
|
||||
├── main.go
|
||||
├── {{"conf"|foldername}}
|
||||
│ └── app.conf
|
||||
└── {{"models"|foldername}}
|
||||
└── object.go
|
||||
└── user.go
|
||||
`,
|
||||
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
|
||||
Run: createhprose,
|
||||
}
|
||||
|
||||
var hproseconf = `appname = {{.Appname}}
|
||||
@ -63,16 +58,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 +101,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 +116,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()
|
||||
}
|
||||
@ -248,16 +292,13 @@ func DeleteUser(uid string) {
|
||||
var hproseAddFunctions = []string{}
|
||||
|
||||
func init() {
|
||||
cmdHproseapp.Run = createhprose
|
||||
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")
|
||||
cmdHproseapp.Flag.Var(&tables, "tables", "List of table names separated by a comma.")
|
||||
cmdHproseapp.Flag.Var(&driver, "driver", "Database driver. Either mysql, postgres or sqlite.")
|
||||
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)
|
||||
output := cmd.Out()
|
||||
|
||||
curpath, _ := os.Getwd()
|
||||
if len(args) > 1 {
|
||||
@ -265,8 +306,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,22 +314,22 @@ 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")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath, "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "conf"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf"), "\x1b[0m")
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "conf", "app.conf"),
|
||||
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")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
|
||||
maingoContent := strings.Replace(hproseMainconngo, "{{.Appname}}", packpath, -1)
|
||||
maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(driver), -1)
|
||||
maingoContent = strings.Replace(maingoContent, "{{HproseFunctionList}}", strings.Join(hproseAddFunctions, ""), -1)
|
||||
@ -308,18 +348,18 @@ func createhprose(cmd *Command, args []string) int {
|
||||
)
|
||||
} else {
|
||||
os.Mkdir(path.Join(apppath, "models"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models"), "\x1b[0m")
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "object.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "object.go"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "models", "object.go"), apiModels)
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "user.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "user.go"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "models", "user.go"), apiModels2)
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "main.go"),
|
||||
strings.Replace(hproseMaingo, "{{.Appname}}", packpath, -1))
|
||||
}
|
||||
ColorLog("[SUCC] New Hprose application successfully created!\n")
|
||||
logger.Success("New Hprose application successfully created!")
|
||||
return 0
|
||||
}
|
||||
|
265
logger.go
Normal file
265
logger.go
Normal file
@ -0,0 +1,265 @@
|
||||
// Copyright 2013 bee authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var errInvalidLogLevel = errors.New("logger: invalid log level")
|
||||
|
||||
const (
|
||||
levelCritical = iota
|
||||
levelFatal
|
||||
levelSuccess
|
||||
levelHint
|
||||
levelDebug
|
||||
levelInfo
|
||||
levelWarn
|
||||
levelError
|
||||
)
|
||||
|
||||
var (
|
||||
sequenceNo uint64
|
||||
instance *BeeLogger
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// BeeLogger logs logging records to the specified io.Writer
|
||||
type BeeLogger struct {
|
||||
mu sync.Mutex
|
||||
output io.Writer
|
||||
}
|
||||
|
||||
// LogRecord represents a log record and contains the timestamp when the record
|
||||
// was created, an increasing id, level and the actual formatted log line.
|
||||
type LogRecord struct {
|
||||
ID string
|
||||
Level string
|
||||
Message string
|
||||
Filename string
|
||||
LineNo int
|
||||
}
|
||||
|
||||
var (
|
||||
logRecordTemplate *template.Template
|
||||
debugLogRecordTemplate *template.Template
|
||||
)
|
||||
|
||||
// GetBeeLogger initializes the logger instance with a NewColorWriter output
|
||||
// and returns a singleton
|
||||
func GetBeeLogger(w io.Writer) *BeeLogger {
|
||||
once.Do(func() {
|
||||
var (
|
||||
err error
|
||||
simpleLogFormat = `{{Now "2006/01/02 15:04:05"}} {{.Level}} ▶ {{.ID}} {{.Message}}{{EndLine}}`
|
||||
debugLogFormat = `{{Now "2006/01/02 15:04:05"}} {{.Level}} ▶ {{.ID}} {{.Filename}}:{{.LineNo}} {{.Message}}{{EndLine}}`
|
||||
)
|
||||
|
||||
// Initialize and parse logging templates
|
||||
funcs := template.FuncMap{
|
||||
"Now": Now,
|
||||
"EndLine": EndLine,
|
||||
}
|
||||
logRecordTemplate, err = template.New("simpleLogFormat").Funcs(funcs).Parse(simpleLogFormat)
|
||||
MustCheck(err)
|
||||
debugLogRecordTemplate, err = template.New("debugLogFormat").Funcs(funcs).Parse(debugLogFormat)
|
||||
MustCheck(err)
|
||||
|
||||
instance = &BeeLogger{output: NewColorWriter(w)}
|
||||
})
|
||||
return instance
|
||||
}
|
||||
|
||||
// SetOutput sets the logger output destination
|
||||
func (l *BeeLogger) SetOutput(w io.Writer) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.output = NewColorWriter(w)
|
||||
}
|
||||
|
||||
func (l *BeeLogger) getLevelTag(level int) string {
|
||||
switch level {
|
||||
case levelFatal:
|
||||
return "FATAL "
|
||||
case levelSuccess:
|
||||
return "SUCCESS "
|
||||
case levelHint:
|
||||
return "HINT "
|
||||
case levelDebug:
|
||||
return "DEBUG "
|
||||
case levelInfo:
|
||||
return "INFO "
|
||||
case levelWarn:
|
||||
return "WARN "
|
||||
case levelError:
|
||||
return "ERROR "
|
||||
case levelCritical:
|
||||
return "CRITICAL"
|
||||
default:
|
||||
panic(errInvalidLogLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *BeeLogger) getColorLevel(level int) string {
|
||||
switch level {
|
||||
case levelCritical:
|
||||
return RedBold(l.getLevelTag(level))
|
||||
case levelFatal:
|
||||
return RedBold(l.getLevelTag(level))
|
||||
case levelInfo:
|
||||
return BlueBold(l.getLevelTag(level))
|
||||
case levelHint:
|
||||
return CyanBold(l.getLevelTag(level))
|
||||
case levelDebug:
|
||||
return YellowBold(l.getLevelTag(level))
|
||||
case levelError:
|
||||
return RedBold(l.getLevelTag(level))
|
||||
case levelWarn:
|
||||
return YellowBold(l.getLevelTag(level))
|
||||
case levelSuccess:
|
||||
return GreenBold(l.getLevelTag(level))
|
||||
default:
|
||||
panic(errInvalidLogLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// mustLog logs the message according to the specified level and arguments.
|
||||
// It panics in case of an error.
|
||||
func (l *BeeLogger) mustLog(level int, message string, args ...interface{}) {
|
||||
// Acquire the lock
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
// Create the logging record and pass into the output
|
||||
record := LogRecord{
|
||||
ID: fmt.Sprintf("%04d", atomic.AddUint64(&sequenceNo, 1)),
|
||||
Level: l.getColorLevel(level),
|
||||
Message: fmt.Sprintf(message, args...),
|
||||
}
|
||||
|
||||
err := logRecordTemplate.Execute(l.output, record)
|
||||
MustCheck(err)
|
||||
}
|
||||
|
||||
// mustLogDebug logs a debug message only if debug mode
|
||||
// is enabled. i.e. DEBUG_ENABLED="1"
|
||||
func (l *BeeLogger) mustLogDebug(message string, file string, line int, args ...interface{}) {
|
||||
if !IsDebugEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
// Change the output to Stderr
|
||||
l.SetOutput(os.Stderr)
|
||||
|
||||
// Create the log record
|
||||
record := LogRecord{
|
||||
ID: fmt.Sprintf("%04d", atomic.AddUint64(&sequenceNo, 1)),
|
||||
Level: l.getColorLevel(levelDebug),
|
||||
Message: fmt.Sprintf(message, args...),
|
||||
LineNo: line,
|
||||
Filename: filepath.Base(file),
|
||||
}
|
||||
err := debugLogRecordTemplate.Execute(l.output, record)
|
||||
MustCheck(err)
|
||||
}
|
||||
|
||||
// Debug outputs a debug log message
|
||||
func (l *BeeLogger) Debug(message string, file string, line int) {
|
||||
l.mustLogDebug(message, file, line)
|
||||
}
|
||||
|
||||
// Debugf outputs a formatted debug log message
|
||||
func (l *BeeLogger) Debugf(message string, file string, line int, vars ...interface{}) {
|
||||
l.mustLogDebug(message, file, line, vars...)
|
||||
}
|
||||
|
||||
// Info outputs an information log message
|
||||
func (l *BeeLogger) Info(message string) {
|
||||
l.mustLog(levelInfo, message)
|
||||
}
|
||||
|
||||
// Infof outputs a formatted information log message
|
||||
func (l *BeeLogger) Infof(message string, vars ...interface{}) {
|
||||
l.mustLog(levelInfo, message, vars...)
|
||||
}
|
||||
|
||||
// Warn outputs a warning log message
|
||||
func (l *BeeLogger) Warn(message string) {
|
||||
l.mustLog(levelWarn, message)
|
||||
}
|
||||
|
||||
// Warnf outputs a formatted warning log message
|
||||
func (l *BeeLogger) Warnf(message string, vars ...interface{}) {
|
||||
l.mustLog(levelWarn, message, vars...)
|
||||
}
|
||||
|
||||
// Error outputs an error log message
|
||||
func (l *BeeLogger) Error(message string) {
|
||||
l.mustLog(levelError, message)
|
||||
}
|
||||
|
||||
// Errorf outputs a formatted error log message
|
||||
func (l *BeeLogger) Errorf(message string, vars ...interface{}) {
|
||||
l.mustLog(levelError, message, vars...)
|
||||
}
|
||||
|
||||
// Fatal outputs a fatal log message and exists
|
||||
func (l *BeeLogger) Fatal(message string) {
|
||||
l.mustLog(levelFatal, message)
|
||||
os.Exit(255)
|
||||
}
|
||||
|
||||
// Fatalf outputs a formatted log message and exists
|
||||
func (l *BeeLogger) Fatalf(message string, vars ...interface{}) {
|
||||
l.mustLog(levelFatal, message, vars...)
|
||||
os.Exit(255)
|
||||
}
|
||||
|
||||
// Success outputs a success log message
|
||||
func (l *BeeLogger) Success(message string) {
|
||||
l.mustLog(levelSuccess, message)
|
||||
}
|
||||
|
||||
// Successf outputs a formatted success log message
|
||||
func (l *BeeLogger) Successf(message string, vars ...interface{}) {
|
||||
l.mustLog(levelSuccess, message, vars...)
|
||||
}
|
||||
|
||||
// Hint outputs a hint log message
|
||||
func (l *BeeLogger) Hint(message string) {
|
||||
l.mustLog(levelHint, message)
|
||||
}
|
||||
|
||||
// Hintf outputs a formatted hint log message
|
||||
func (l *BeeLogger) Hintf(message string, vars ...interface{}) {
|
||||
l.mustLog(levelHint, message, vars...)
|
||||
}
|
||||
|
||||
// Critical outputs a critical log message
|
||||
func (l *BeeLogger) Critical(message string) {
|
||||
l.mustLog(levelCritical, message)
|
||||
}
|
||||
|
||||
// Criticalf outputs a formatted critical log message
|
||||
func (l *BeeLogger) Criticalf(message string, vars ...interface{}) {
|
||||
l.mustLog(levelCritical, message, vars...)
|
||||
}
|
161
migrate.go
161
migrate.go
@ -16,71 +16,68 @@ package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var cmdMigrate = &Command{
|
||||
UsageLine: "migrate [Command]",
|
||||
Short: "run database migrations",
|
||||
Long: `
|
||||
bee migrate [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
run all outstanding migrations
|
||||
-driver: [mysql | postgres | sqlite] (default: mysql)
|
||||
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test
|
||||
Short: "Runs database migrations",
|
||||
Long: `The command 'migrate' allows you to run database migrations to keep it up-to-date.
|
||||
|
||||
bee migrate rollback [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
rollback the last migration operation
|
||||
-driver: [mysql | postgres | sqlite] (default: mysql)
|
||||
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test
|
||||
▶ {{"To run all the migrations:"|bold}}
|
||||
|
||||
bee migrate reset [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
rollback all migrations
|
||||
-driver: [mysql | postgres | sqlite] (default: mysql)
|
||||
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test
|
||||
$ bee migrate [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
|
||||
bee migrate refresh [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
rollback all migrations and run them all again
|
||||
-driver: [mysql | postgres | sqlite] (default: mysql)
|
||||
-conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test
|
||||
▶ {{"To rollback the last migration:"|bold}}
|
||||
|
||||
$ bee migrate rollback [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
|
||||
▶ {{"To do a reset, which will rollback all the migrations:"|bold}}
|
||||
|
||||
$ bee migrate reset [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
|
||||
▶ {{"To update your schema:"|bold}}
|
||||
|
||||
$ bee migrate refresh [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
|
||||
`,
|
||||
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
|
||||
Run: runMigration,
|
||||
}
|
||||
|
||||
var mDriver docValue
|
||||
var mConn docValue
|
||||
|
||||
func init() {
|
||||
cmdMigrate.Run = runMigration
|
||||
cmdMigrate.Flag.Var(&mDriver, "driver", "database driver: mysql, postgres, sqlite, etc.")
|
||||
cmdMigrate.Flag.Var(&mConn, "conn", "connection string used by the driver to connect to a database instance")
|
||||
cmdMigrate.Flag.Var(&mDriver, "driver", "Database driver. Either mysql, postgres or sqlite.")
|
||||
cmdMigrate.Flag.Var(&mConn, "conn", "Connection string used by the driver to connect to a database instance.")
|
||||
}
|
||||
|
||||
// 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:])
|
||||
}
|
||||
@ -96,31 +93,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,21 +142,21 @@ 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")
|
||||
dir := path.Join(currpath, "database", "migrations")
|
||||
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)
|
||||
@ -175,50 +171,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -262,26 +252,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
|
||||
}
|
||||
@ -293,8 +279,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)
|
||||
@ -302,8 +287,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)
|
||||
}
|
||||
@ -314,7 +298,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")
|
||||
@ -328,7 +312,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)
|
||||
@ -341,8 +325,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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -350,7 +333,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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,8 +341,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -368,13 +350,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(
|
||||
@ -414,6 +396,7 @@ func main(){
|
||||
}
|
||||
|
||||
`
|
||||
// MYSQLMigrationDDL MySQL migration SQL
|
||||
MYSQLMigrationDDL = `
|
||||
CREATE TABLE migrations (
|
||||
id_migration int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key',
|
||||
@ -425,7 +408,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');
|
||||
|
||||
|
111
new.go
111
new.go
@ -23,101 +23,96 @@ import (
|
||||
|
||||
var cmdNew = &Command{
|
||||
UsageLine: "new [appname]",
|
||||
Short: "Create a Beego application",
|
||||
Short: "Creates a Beego application",
|
||||
Long: `
|
||||
Creates a Beego application for the given app name in the current directory.
|
||||
|
||||
The command 'new' creates a folder named [appname] and inside the folder deploy
|
||||
the following files/directories structure:
|
||||
The command 'new' creates a folder named [appname] and generates the following structure:
|
||||
|
||||
|- main.go
|
||||
|- conf
|
||||
|- app.conf
|
||||
|- controllers
|
||||
|- default.go
|
||||
|- models
|
||||
|- routers
|
||||
|- router.go
|
||||
|- tests
|
||||
|- default_test.go
|
||||
|- static
|
||||
|- js
|
||||
|- css
|
||||
|- img
|
||||
|- views
|
||||
index.tpl
|
||||
├── main.go
|
||||
├── {{"conf"|foldername}}
|
||||
│ └── app.conf
|
||||
├── {{"controllers"|foldername}}
|
||||
│ └── default.go
|
||||
├── {{"models"|foldername}}
|
||||
├── {{"routers"|foldername}}
|
||||
│ └── router.go
|
||||
├── {{"tests"|foldername}}
|
||||
│ └── default_test.go
|
||||
├── {{"static"|foldername}}
|
||||
│ └── {{"js"|foldername}}
|
||||
│ └── {{"css"|foldername}}
|
||||
│ └── {{"img"|foldername}}
|
||||
└── {{"views"|foldername}}
|
||||
└── index.tpl
|
||||
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdNew.Run = createApp
|
||||
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
|
||||
Run: createApp,
|
||||
}
|
||||
|
||||
func createApp(cmd *Command, args []string) int {
|
||||
ShowShortVersionBanner()
|
||||
w := NewColorWriter(os.Stdout)
|
||||
output := cmd.Out()
|
||||
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")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath+string(path.Separator), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "conf"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf")+string(path.Separator), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf")+string(path.Separator), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "controllers"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers")+string(path.Separator), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers")+string(path.Separator), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "models"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models")+string(path.Separator), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models")+string(path.Separator), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "routers"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers")+string(path.Separator), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers")+string(path.Separator), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "tests"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests")+string(path.Separator), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests")+string(path.Separator), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "static"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static")+string(path.Separator), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static")+string(path.Separator), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "static", "js"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "js")+string(path.Separator), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "js")+string(path.Separator), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "static", "css"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "css")+string(path.Separator), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "css")+string(path.Separator), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "static", "img"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "img")+string(path.Separator), "\x1b[0m")
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "views")+string(path.Separator), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "img")+string(path.Separator), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "views")+string(path.Separator), "\x1b[0m")
|
||||
os.Mkdir(path.Join(apppath, "views"), 0755)
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "conf", "app.conf"), strings.Replace(appconf, "{{.Appname}}", path.Base(args[0]), -1))
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "default.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "default.go"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "controllers", "default.go"), controllers)
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "views", "index.tpl"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "views", "index.tpl"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "views", "index.tpl"), indextpl)
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers", "router.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers", "router.go"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "routers", "router.go"), strings.Replace(router, "{{.Appname}}", packpath, -1))
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests", "default_test.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests", "default_test.go"), "\x1b[0m")
|
||||
WriteToFile(path.Join(apppath, "tests", "default_test.go"), strings.Replace(test, "{{.Appname}}", packpath, -1))
|
||||
|
||||
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
|
||||
fmt.Fprintf(output, "\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 +166,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 +297,3 @@ var indextpl = `<!DOCTYPE html>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
// WriteToFile creates a file and writes content to it
|
||||
func WriteToFile(filename, content string) {
|
||||
f, err := os.Create(filename)
|
||||
defer CloseFile(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.WriteString(content)
|
||||
}
|
||||
|
133
pack.go
133
pack.go
@ -21,7 +21,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
path "path/filepath"
|
||||
@ -37,26 +36,15 @@ import (
|
||||
var cmdPack = &Command{
|
||||
CustomFlags: true,
|
||||
UsageLine: "pack",
|
||||
Short: "Compress a beego project into a single file",
|
||||
Long: `
|
||||
Pack is used to compress a beego project into a single file.
|
||||
This eases the deployment by extracting the zip file to a server.
|
||||
Short: "Compresses a Beego application into a single file",
|
||||
Long: `Pack is used to compress Beego applications into a tarball/zip file.
|
||||
This eases the deployment by directly extracting the file to a server.
|
||||
|
||||
-p app path (default is the current path).
|
||||
-b build specify platform app (default: true).
|
||||
-ba additional args of go build
|
||||
-be=[] additional ENV Variables of go build. eg: GOARCH=arm
|
||||
-o compressed file output dir. default use current path
|
||||
-f="" format: tar.gz, zip (default: tar.gz)
|
||||
-exp="" relpath exclude prefix (default: .). use : as separator
|
||||
-exs="" relpath exclude suffix (default: .go:.DS_Store:.tmp). use : as separator
|
||||
all path use : as separator
|
||||
-exr=[] file/directory name exclude by Regexp (default: ^).
|
||||
-fs=false follow symlink (default: false).
|
||||
-ss=false skip symlink (default: false)
|
||||
default embed symlink into compressed file
|
||||
-v=false verbose
|
||||
{{"Example:"|bold}}
|
||||
$ bee pack -v -ba="-ldflags '-s -w'"
|
||||
`,
|
||||
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
|
||||
Run: packApp,
|
||||
}
|
||||
|
||||
var (
|
||||
@ -72,7 +60,6 @@ var (
|
||||
buildEnvs ListOpts
|
||||
verbose bool
|
||||
format string
|
||||
w io.Writer
|
||||
)
|
||||
|
||||
type ListOpts []string
|
||||
@ -88,26 +75,19 @@ func (opts *ListOpts) Set(value string) error {
|
||||
|
||||
func init() {
|
||||
fs := flag.NewFlagSet("pack", flag.ContinueOnError)
|
||||
fs.StringVar(&appPath, "p", "", "app path. default is current path")
|
||||
fs.BoolVar(&build, "b", true, "build specify platform app")
|
||||
fs.StringVar(&buildArgs, "ba", "", "additional args of go build")
|
||||
fs.Var(&buildEnvs, "be", "additional ENV Variables of go build. eg: GOARCH=arm")
|
||||
fs.StringVar(&outputP, "o", "", "compressed file output dir. default use current path")
|
||||
fs.StringVar(&format, "f", "tar.gz", "format. [ tar.gz / zip ]")
|
||||
fs.StringVar(&excludeP, "exp", ".", "path exclude prefix. use : as separator")
|
||||
fs.StringVar(&excludeS, "exs", ".go:.DS_Store:.tmp", "path exclude suffix. use : as separator")
|
||||
fs.Var(&excludeR, "exr", "filename exclude by Regexp")
|
||||
fs.BoolVar(&fsym, "fs", false, "follow symlink")
|
||||
fs.BoolVar(&ssym, "ss", false, "skip symlink")
|
||||
fs.BoolVar(&verbose, "v", false, "verbose")
|
||||
fs.StringVar(&appPath, "p", "", "Set the application path. Defaults to the current path.")
|
||||
fs.BoolVar(&build, "b", true, "Tell the command to do a build for the current platform. Defaults to true.")
|
||||
fs.StringVar(&buildArgs, "ba", "", "Specify additional args for Go build.")
|
||||
fs.Var(&buildEnvs, "be", "Specify additional env variables for Go build. e.g. GOARCH=arm.")
|
||||
fs.StringVar(&outputP, "o", "", "Set the compressed file output path. Defaults to the current path.")
|
||||
fs.StringVar(&format, "f", "tar.gz", "Set file format. Either tar.gz or zip. Defaults to tar.gz.")
|
||||
fs.StringVar(&excludeP, "exp", ".", "Set prefixes of paths to be excluded. Uses a column (:) as separator.")
|
||||
fs.StringVar(&excludeS, "exs", ".go:.DS_Store:.tmp", "Set suffixes of paths to be excluded. Uses a column (:) as separator.")
|
||||
fs.Var(&excludeR, "exr", "Set a regular expression of files to be excluded.")
|
||||
fs.BoolVar(&fsym, "fs", false, "Tell the command to follow symlinks. Defaults to false.")
|
||||
fs.BoolVar(&ssym, "ss", false, "Tell the command to skip symlinks. Defaults to false.")
|
||||
fs.BoolVar(&verbose, "v", false, "Be more verbose during the operation. Defaults to false.")
|
||||
cmdPack.Flag = *fs
|
||||
cmdPack.Run = packApp
|
||||
w = NewColorWriter(os.Stdout)
|
||||
}
|
||||
|
||||
func exitPrint(con string) {
|
||||
fmt.Fprintln(os.Stderr, con)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
type walker interface {
|
||||
@ -132,6 +112,7 @@ type walkFileTree struct {
|
||||
excludeRegexp []*regexp.Regexp
|
||||
excludeSuffix []string
|
||||
allfiles map[string]bool
|
||||
output *io.Writer
|
||||
}
|
||||
|
||||
func (wft *walkFileTree) setPrefix(prefix string) {
|
||||
@ -244,7 +225,7 @@ func (wft *walkFileTree) walkLeaf(fpath string, fi os.FileInfo, err error) error
|
||||
|
||||
if added, err := wft.wak.compress(name, fpath, fi); added {
|
||||
if verbose {
|
||||
fmt.Fprintf(w, "\t%s%scompressed%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", name, "\x1b[0m")
|
||||
fmt.Fprintf(*wft.output, "\t%s%scompressed%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", name, "\x1b[0m")
|
||||
}
|
||||
wft.allfiles[name] = true
|
||||
return err
|
||||
@ -395,13 +376,13 @@ func (wft *zipWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func packDirectory(excludePrefix []string, excludeSuffix []string,
|
||||
func packDirectory(output io.Writer, 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)
|
||||
@ -413,6 +394,7 @@ func packDirectory(excludePrefix []string, excludeSuffix []string,
|
||||
|
||||
if format == "zip" {
|
||||
walk := new(zipWalk)
|
||||
walk.output = &output
|
||||
zw := zip.NewWriter(w)
|
||||
defer func() {
|
||||
zw.Close()
|
||||
@ -426,6 +408,7 @@ func packDirectory(excludePrefix []string, excludeSuffix []string,
|
||||
wft = walk
|
||||
} else {
|
||||
walk := new(tarWalk)
|
||||
walk.output = &output
|
||||
cw := gzip.NewWriter(w)
|
||||
tw := tar.NewWriter(cw)
|
||||
|
||||
@ -454,27 +437,8 @@ 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()
|
||||
|
||||
output := cmd.Out()
|
||||
curPath, _ := os.Getwd()
|
||||
thePath := ""
|
||||
|
||||
@ -488,7 +452,7 @@ func packApp(cmd *Command, args []string) int {
|
||||
nArgs = append(nArgs, a)
|
||||
}
|
||||
}
|
||||
cmdPack.Flag.Parse(nArgs)
|
||||
cmd.Flag.Parse(nArgs)
|
||||
|
||||
if path.IsAbs(appPath) == false {
|
||||
appPath = path.Join(curPath, appPath)
|
||||
@ -496,17 +460,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)
|
||||
|
||||
@ -524,9 +484,16 @@ func packApp(cmd *Command, args []string) int {
|
||||
tmpdir := path.Join(os.TempDir(), "beePack-"+str)
|
||||
|
||||
os.Mkdir(tmpdir, 0700)
|
||||
defer func() {
|
||||
// Remove the tmpdir once bee pack is done
|
||||
err := os.RemoveAll(tmpdir)
|
||||
if err != nil {
|
||||
logger.Error("Failed to remove the generated temp dir")
|
||||
}
|
||||
}()
|
||||
|
||||
if build {
|
||||
ColorLog("Building application...\n")
|
||||
logger.Info("Building application...")
|
||||
var envs []string
|
||||
for _, env := range buildEnvs {
|
||||
parts := strings.SplitN(env, "=", 2)
|
||||
@ -548,7 +515,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" {
|
||||
@ -561,7 +528,7 @@ func packApp(cmd *Command, args []string) int {
|
||||
}
|
||||
|
||||
if verbose {
|
||||
fmt.Fprintf(w, "\t%s%s+ go %s%s%s\n", "\x1b[32m", "\x1b[1m", strings.Join(args, " "), "\x1b[21m", "\x1b[0m")
|
||||
fmt.Fprintf(output, "\t%s%s+ go %s%s%s\n", "\x1b[32m", "\x1b[1m", strings.Join(args, " "), "\x1b[21m", "\x1b[0m")
|
||||
}
|
||||
|
||||
execmd := exec.Command("go", args...)
|
||||
@ -571,10 +538,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 +559,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 +581,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = packDirectory(exp, exs, exr, tmpdir, thePath)
|
||||
logger.Infof("Writing to output: %s", outputP)
|
||||
|
||||
err = packDirectory(output, 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
|
||||
}
|
||||
|
64
run.go
64
run.go
@ -15,7 +15,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
path "path/filepath"
|
||||
@ -25,12 +24,13 @@ import (
|
||||
|
||||
var cmdRun = &Command{
|
||||
UsageLine: "run [appname] [watchall] [-main=*.go] [-downdoc=true] [-gendoc=true] [-vendor=true] [-e=folderToExclude] [-tags=goBuildTags] [-runmode=BEEGO_RUNMODE]",
|
||||
Short: "run the app and start a Web server for development",
|
||||
Short: "Run the application by starting a local development server",
|
||||
Long: `
|
||||
Run command will supervise the file system of the beego project using inotify,
|
||||
it will recompile and restart the app after any modifications.
|
||||
Run command will supervise the filesystem of the application for any changes, and recompile/restart it.
|
||||
|
||||
`,
|
||||
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
|
||||
Run: runApp,
|
||||
}
|
||||
|
||||
var (
|
||||
@ -56,20 +56,17 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmdRun.Run = runApp
|
||||
cmdRun.Flag.Var(&mainFiles, "main", "specify main go files")
|
||||
cmdRun.Flag.Var(&gendoc, "gendoc", "auto generate the docs")
|
||||
cmdRun.Flag.Var(&downdoc, "downdoc", "auto download swagger file when not exist")
|
||||
cmdRun.Flag.Var(&excludedPaths, "e", "Excluded paths[].")
|
||||
cmdRun.Flag.BoolVar(&vendorWatch, "vendor", false, "Watch vendor folder")
|
||||
cmdRun.Flag.StringVar(&buildTags, "tags", "", "Build tags (https://golang.org/pkg/go/build/)")
|
||||
cmdRun.Flag.StringVar(&runmode, "runmode", "", "Set BEEGO_RUNMODE env variable.")
|
||||
cmdRun.Flag.Var(&mainFiles, "main", "Specify main go files.")
|
||||
cmdRun.Flag.Var(&gendoc, "gendoc", "Enable auto-generate the docs.")
|
||||
cmdRun.Flag.Var(&downdoc, "downdoc", "Enable auto-download of the swagger file if it does not exist.")
|
||||
cmdRun.Flag.Var(&excludedPaths, "e", "List of paths to exclude.")
|
||||
cmdRun.Flag.BoolVar(&vendorWatch, "vendor", false, "Enable watch vendor folder.")
|
||||
cmdRun.Flag.StringVar(&buildTags, "tags", "", "Set the build tags. See: https://golang.org/pkg/go/build/")
|
||||
cmdRun.Flag.StringVar(&runmode, "runmode", "", "Set the Beego run mode.")
|
||||
exit = make(chan bool)
|
||||
}
|
||||
|
||||
func runApp(cmd *Command, args []string) int {
|
||||
ShowShortVersionBanner()
|
||||
|
||||
if len(args) == 0 || args[0] == "watchall" {
|
||||
currpath, _ = os.Getwd()
|
||||
|
||||
@ -77,9 +74,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 +83,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 +139,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 +198,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
|
||||
}
|
||||
}
|
||||
|
32
rundocs.go
32
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)
|
||||
}
|
||||
|
27
test.go
27
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")
|
||||
}
|
||||
|
268
util.go
268
util.go
@ -15,14 +15,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
"path"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Go is a basic promise implementation: it wraps calls a function in a goroutine
|
||||
@ -35,124 +39,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 = "<unknown>"
|
||||
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: <level> <content [highlight][path]> [ error ].
|
||||
// Level: TRAC -> blue; ERRO -> red; WARN -> Magenta; SUCC -> green; others -> default.
|
||||
// Content: default; path: yellow; error -> red.
|
||||
// Level has to be surrounded by "[" and "]".
|
||||
// Highlights have to be surrounded by "# " and " #"(space), "#" will be deleted.
|
||||
// Paths have to be surrounded by "( " and " )"(space).
|
||||
// Errors have to be surrounded by "[ " and " ]"(space).
|
||||
// Note: it hasn't support windows yet, contribute is welcome.
|
||||
func ColorLogS(format string, a ...interface{}) string {
|
||||
log := fmt.Sprintf(format, a...)
|
||||
|
||||
var clog string
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
// Level.
|
||||
i := strings.Index(log, "]")
|
||||
if log[0] == '[' && i > -1 {
|
||||
clog += "[" + getColorLevel(log[1:i]) + "]"
|
||||
}
|
||||
|
||||
log = log[i+1:]
|
||||
|
||||
// Error.
|
||||
log = strings.Replace(log, "[ ", fmt.Sprintf("[\033[%dm", Red), -1)
|
||||
log = strings.Replace(log, " ]", EndColor+"]", -1)
|
||||
|
||||
// Path.
|
||||
log = strings.Replace(log, "( ", fmt.Sprintf("(\033[%dm", Yellow), -1)
|
||||
log = strings.Replace(log, " )", EndColor+")", -1)
|
||||
|
||||
// Highlights.
|
||||
log = strings.Replace(log, "# ", fmt.Sprintf("\033[%dm", Gray), -1)
|
||||
log = strings.Replace(log, " #", EndColor, -1)
|
||||
|
||||
log = clog + log
|
||||
|
||||
} else {
|
||||
// Level.
|
||||
i := strings.Index(log, "]")
|
||||
if log[0] == '[' && i > -1 {
|
||||
clog += "[" + log[1:i] + "]"
|
||||
}
|
||||
|
||||
log = log[i+1:]
|
||||
|
||||
// Error.
|
||||
log = strings.Replace(log, "[ ", "[", -1)
|
||||
log = strings.Replace(log, " ]", "]", -1)
|
||||
|
||||
// Path.
|
||||
log = strings.Replace(log, "( ", "(", -1)
|
||||
log = strings.Replace(log, " )", ")", -1)
|
||||
|
||||
// Highlights.
|
||||
log = strings.Replace(log, "# ", "", -1)
|
||||
log = strings.Replace(log, " #", "", -1)
|
||||
|
||||
log = clog + log
|
||||
}
|
||||
|
||||
return time.Now().Format("2006/01/02 15:04:05 ") + log
|
||||
}
|
||||
|
||||
// getColorLevel returns colored level string by given level.
|
||||
func getColorLevel(level string) string {
|
||||
level = strings.ToUpper(level)
|
||||
switch level {
|
||||
case INFO:
|
||||
return fmt.Sprintf("\033[%dm%s\033[0m", Blue, level)
|
||||
case TRAC:
|
||||
return fmt.Sprintf("\033[%dm%s\033[0m", Blue, level)
|
||||
case ERRO:
|
||||
return fmt.Sprintf("\033[%dm%s\033[0m", Red, level)
|
||||
case WARN:
|
||||
return fmt.Sprintf("\033[%dm%s\033[0m", Magenta, level)
|
||||
case SUCC:
|
||||
return fmt.Sprintf("\033[%dm%s\033[0m", Green, level)
|
||||
default:
|
||||
return level
|
||||
}
|
||||
// EndLine returns the a newline escape character
|
||||
func EndLine() string {
|
||||
return "\n"
|
||||
}
|
||||
|
||||
// IsExist returns whether a file or directory exists.
|
||||
@ -174,11 +68,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 +128,6 @@ func SearchGOPATHs(app string) (bool, string, string) {
|
||||
}
|
||||
|
||||
if isExist(currentPath) {
|
||||
if !isBeegoProject(currentPath) {
|
||||
continue
|
||||
}
|
||||
return true, gopath, currentPath
|
||||
}
|
||||
}
|
||||
@ -211,7 +143,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 +181,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 +205,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 +241,69 @@ 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
|
||||
}
|
||||
|
||||
// BeeFuncMap returns a FuncMap of functions used in different templates.
|
||||
func BeeFuncMap() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"trim": strings.TrimSpace,
|
||||
"bold": bold,
|
||||
"headline": MagentaBold,
|
||||
"foldername": RedBold,
|
||||
"endline": EndLine,
|
||||
"tmpltostr": TmplToString,
|
||||
}
|
||||
}
|
||||
|
||||
// TmplToString parses a text template and return the result as a string.
|
||||
func TmplToString(tmpl string, data interface{}) string {
|
||||
t := template.New("tmpl").Funcs(BeeFuncMap())
|
||||
template.Must(t.Parse(tmpl))
|
||||
|
||||
var doc bytes.Buffer
|
||||
err := t.Execute(&doc, data)
|
||||
MustCheck(err)
|
||||
|
||||
return doc.String()
|
||||
}
|
||||
|
13
vendor/github.com/astaxie/beego/LICENSE
generated
vendored
Normal file
13
vendor/github.com/astaxie/beego/LICENSE
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
Copyright 2014 astaxie
|
||||
|
||||
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.
|
171
vendor/github.com/astaxie/beego/swagger/swagger.go
generated
vendored
Normal file
171
vendor/github.com/astaxie/beego/swagger/swagger.go
generated
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Swagger™ is a project used to describe and document RESTful APIs.
|
||||
//
|
||||
// The Swagger specification defines a set of files required to describe such an API. These files can then be used by the Swagger-UI project to display the API and Swagger-Codegen to generate clients in various languages. Additional utilities can also take advantage of the resulting files, such as testing tools.
|
||||
// Now in version 2.0, Swagger is more enabling than ever. And it's 100% open source software.
|
||||
|
||||
// Package swagger struct definition
|
||||
package swagger
|
||||
|
||||
// Swagger list the resource
|
||||
type Swagger struct {
|
||||
SwaggerVersion string `json:"swagger,omitempty" yaml:"swagger,omitempty"`
|
||||
Infos Information `json:"info" yaml:"info"`
|
||||
Host string `json:"host,omitempty" yaml:"host,omitempty"`
|
||||
BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"`
|
||||
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
|
||||
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
|
||||
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
|
||||
Paths map[string]*Item `json:"paths" yaml:"paths"`
|
||||
Definitions map[string]Schema `json:"definitions,omitempty" yaml:"definitions,omitempty"`
|
||||
SecurityDefinitions map[string]Security `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
|
||||
Security map[string][]string `json:"security,omitempty" yaml:"security,omitempty"`
|
||||
Tags []Tag `json:"tags,omitempty" yaml:"tags,omitempty"`
|
||||
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
|
||||
}
|
||||
|
||||
// Information Provides metadata about the API. The metadata can be used by the clients if needed.
|
||||
type Information struct {
|
||||
Title string `json:"title,omitempty" yaml:"title,omitempty"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
Version string `json:"version,omitempty" yaml:"version,omitempty"`
|
||||
TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"`
|
||||
|
||||
Contact Contact `json:"contact,omitempty" yaml:"contact,omitempty"`
|
||||
License *License `json:"license,omitempty" yaml:"license,omitempty"`
|
||||
}
|
||||
|
||||
// Contact information for the exposed API.
|
||||
type Contact struct {
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
URL string `json:"url,omitempty" yaml:"url,omitempty"`
|
||||
EMail string `json:"email,omitempty" yaml:"email,omitempty"`
|
||||
}
|
||||
|
||||
// License information for the exposed API.
|
||||
type License struct {
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
URL string `json:"url,omitempty" yaml:"url,omitempty"`
|
||||
}
|
||||
|
||||
// Item Describes the operations available on a single path.
|
||||
type Item struct {
|
||||
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
|
||||
Get *Operation `json:"get,omitempty" yaml:"get,omitempty"`
|
||||
Put *Operation `json:"put,omitempty" yaml:"put,omitempty"`
|
||||
Post *Operation `json:"post,omitempty" yaml:"post,omitempty"`
|
||||
Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
|
||||
Options *Operation `json:"options,omitempty" yaml:"options,omitempty"`
|
||||
Head *Operation `json:"head,omitempty" yaml:"head,omitempty"`
|
||||
Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
|
||||
}
|
||||
|
||||
// Operation Describes a single API operation on a path.
|
||||
type Operation struct {
|
||||
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
|
||||
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
|
||||
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
|
||||
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
|
||||
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
|
||||
Parameters []Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
|
||||
Responses map[string]Response `json:"responses,omitempty" yaml:"responses,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
|
||||
}
|
||||
|
||||
// Parameter Describes a single operation parameter.
|
||||
type Parameter struct {
|
||||
In string `json:"in,omitempty" yaml:"in,omitempty"`
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
|
||||
Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"`
|
||||
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||
Format string `json:"format,omitempty" yaml:"format,omitempty"`
|
||||
Items *ParameterItems `json:"items,omitempty" yaml:"items,omitempty"`
|
||||
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
|
||||
}
|
||||
|
||||
// A limited subset of JSON-Schema's items object. It is used by parameter definitions that are not located in "body".
|
||||
// http://swagger.io/specification/#itemsObject
|
||||
type ParameterItems struct {
|
||||
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||
Format string `json:"format,omitempty" yaml:"format,omitempty"`
|
||||
Items []*ParameterItems `json:"items,omitempty" yaml:"items,omitempty"` //Required if type is "array". Describes the type of items in the array.
|
||||
CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
|
||||
Default string `json:"default,omitempty" yaml:"default,omitempty"`
|
||||
}
|
||||
|
||||
// Schema Object allows the definition of input and output data types.
|
||||
type Schema struct {
|
||||
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
|
||||
Title string `json:"title,omitempty" yaml:"title,omitempty"`
|
||||
Format string `json:"format,omitempty" yaml:"format,omitempty"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
|
||||
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||
Items *Schema `json:"items,omitempty" yaml:"items,omitempty"`
|
||||
Properties map[string]Propertie `json:"properties,omitempty" yaml:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// Propertie are taken from the JSON Schema definition but their definitions were adjusted to the Swagger Specification
|
||||
type Propertie struct {
|
||||
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
|
||||
Title string `json:"title,omitempty" yaml:"title,omitempty"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
|
||||
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||
Example string `json:"example,omitempty" yaml:"example,omitempty"`
|
||||
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
|
||||
Format string `json:"format,omitempty" yaml:"format,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
|
||||
Properties map[string]Propertie `json:"properties,omitempty" yaml:"properties,omitempty"`
|
||||
Items *Propertie `json:"items,omitempty" yaml:"items,omitempty"`
|
||||
AdditionalProperties *Propertie `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"`
|
||||
}
|
||||
|
||||
// Response as they are returned from executing this operation.
|
||||
type Response struct {
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"`
|
||||
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
|
||||
}
|
||||
|
||||
// Security Allows the definition of a security scheme that can be used by the operations
|
||||
type Security struct {
|
||||
Type string `json:"type,omitempty" yaml:"type,omitempty"` // Valid values are "basic", "apiKey" or "oauth2".
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
In string `json:"in,omitempty" yaml:"in,omitempty"` // Valid values are "query" or "header".
|
||||
Flow string `json:"flow,omitempty" yaml:"flow,omitempty"` // Valid values are "implicit", "password", "application" or "accessCode".
|
||||
AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
|
||||
TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
|
||||
Scopes map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"` // The available scopes for the OAuth2 security scheme.
|
||||
}
|
||||
|
||||
// Tag Allows adding meta data to a single tag that is used by the Operation Object
|
||||
type Tag struct {
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
|
||||
}
|
||||
|
||||
// ExternalDocs include Additional external documentation
|
||||
type ExternalDocs struct {
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
URL string `json:"url,omitempty" yaml:"url,omitempty"`
|
||||
}
|
25
vendor/github.com/astaxie/beego/utils/caller.go
generated
vendored
Normal file
25
vendor/github.com/astaxie/beego/utils/caller.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// GetFuncName get function name
|
||||
func GetFuncName(i interface{}) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||
}
|
478
vendor/github.com/astaxie/beego/utils/debug.go
generated
vendored
Normal file
478
vendor/github.com/astaxie/beego/utils/debug.go
generated
vendored
Normal file
@ -0,0 +1,478 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
dunno = []byte("???")
|
||||
centerDot = []byte("·")
|
||||
dot = []byte(".")
|
||||
)
|
||||
|
||||
type pointerInfo struct {
|
||||
prev *pointerInfo
|
||||
n int
|
||||
addr uintptr
|
||||
pos int
|
||||
used []int
|
||||
}
|
||||
|
||||
// Display print the data in console
|
||||
func Display(data ...interface{}) {
|
||||
display(true, data...)
|
||||
}
|
||||
|
||||
// GetDisplayString return data print string
|
||||
func GetDisplayString(data ...interface{}) string {
|
||||
return display(false, data...)
|
||||
}
|
||||
|
||||
func display(displayed bool, data ...interface{}) string {
|
||||
var pc, file, line, ok = runtime.Caller(2)
|
||||
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
var buf = new(bytes.Buffer)
|
||||
|
||||
fmt.Fprintf(buf, "[Debug] at %s() [%s:%d]\n", function(pc), file, line)
|
||||
|
||||
fmt.Fprintf(buf, "\n[Variables]\n")
|
||||
|
||||
for i := 0; i < len(data); i += 2 {
|
||||
var output = fomateinfo(len(data[i].(string))+3, data[i+1])
|
||||
fmt.Fprintf(buf, "%s = %s", data[i], output)
|
||||
}
|
||||
|
||||
if displayed {
|
||||
log.Print(buf)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// return data dump and format bytes
|
||||
func fomateinfo(headlen int, data ...interface{}) []byte {
|
||||
var buf = new(bytes.Buffer)
|
||||
|
||||
if len(data) > 1 {
|
||||
fmt.Fprint(buf, " ")
|
||||
|
||||
fmt.Fprint(buf, "[")
|
||||
|
||||
fmt.Fprintln(buf)
|
||||
}
|
||||
|
||||
for k, v := range data {
|
||||
var buf2 = new(bytes.Buffer)
|
||||
var pointers *pointerInfo
|
||||
var interfaces = make([]reflect.Value, 0, 10)
|
||||
|
||||
printKeyValue(buf2, reflect.ValueOf(v), &pointers, &interfaces, nil, true, " ", 1)
|
||||
|
||||
if k < len(data)-1 {
|
||||
fmt.Fprint(buf2, ", ")
|
||||
}
|
||||
|
||||
fmt.Fprintln(buf2)
|
||||
|
||||
buf.Write(buf2.Bytes())
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
fmt.Fprint(buf, " ")
|
||||
|
||||
fmt.Fprint(buf, "]")
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// check data is golang basic type
|
||||
func isSimpleType(val reflect.Value, kind reflect.Kind, pointers **pointerInfo, interfaces *[]reflect.Value) bool {
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return true
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||
return true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return true
|
||||
case reflect.String:
|
||||
return true
|
||||
case reflect.Chan:
|
||||
return true
|
||||
case reflect.Invalid:
|
||||
return true
|
||||
case reflect.Interface:
|
||||
for _, in := range *interfaces {
|
||||
if reflect.DeepEqual(in, val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case reflect.UnsafePointer:
|
||||
if val.IsNil() {
|
||||
return true
|
||||
}
|
||||
|
||||
var elem = val.Elem()
|
||||
|
||||
if isSimpleType(elem, elem.Kind(), pointers, interfaces) {
|
||||
return true
|
||||
}
|
||||
|
||||
var addr = val.Elem().UnsafeAddr()
|
||||
|
||||
for p := *pointers; p != nil; p = p.prev {
|
||||
if addr == p.addr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// dump value
|
||||
func printKeyValue(buf *bytes.Buffer, val reflect.Value, pointers **pointerInfo, interfaces *[]reflect.Value, structFilter func(string, string) bool, formatOutput bool, indent string, level int) {
|
||||
var t = val.Kind()
|
||||
|
||||
switch t {
|
||||
case reflect.Bool:
|
||||
fmt.Fprint(buf, val.Bool())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
fmt.Fprint(buf, val.Int())
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||
fmt.Fprint(buf, val.Uint())
|
||||
case reflect.Float32, reflect.Float64:
|
||||
fmt.Fprint(buf, val.Float())
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
fmt.Fprint(buf, val.Complex())
|
||||
case reflect.UnsafePointer:
|
||||
fmt.Fprintf(buf, "unsafe.Pointer(0x%X)", val.Pointer())
|
||||
case reflect.Ptr:
|
||||
if val.IsNil() {
|
||||
fmt.Fprint(buf, "nil")
|
||||
return
|
||||
}
|
||||
|
||||
var addr = val.Elem().UnsafeAddr()
|
||||
|
||||
for p := *pointers; p != nil; p = p.prev {
|
||||
if addr == p.addr {
|
||||
p.used = append(p.used, buf.Len())
|
||||
fmt.Fprintf(buf, "0x%X", addr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
*pointers = &pointerInfo{
|
||||
prev: *pointers,
|
||||
addr: addr,
|
||||
pos: buf.Len(),
|
||||
used: make([]int, 0),
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, "&")
|
||||
|
||||
printKeyValue(buf, val.Elem(), pointers, interfaces, structFilter, formatOutput, indent, level)
|
||||
case reflect.String:
|
||||
fmt.Fprint(buf, "\"", val.String(), "\"")
|
||||
case reflect.Interface:
|
||||
var value = val.Elem()
|
||||
|
||||
if !value.IsValid() {
|
||||
fmt.Fprint(buf, "nil")
|
||||
} else {
|
||||
for _, in := range *interfaces {
|
||||
if reflect.DeepEqual(in, val) {
|
||||
fmt.Fprint(buf, "repeat")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
*interfaces = append(*interfaces, val)
|
||||
|
||||
printKeyValue(buf, value, pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
}
|
||||
case reflect.Struct:
|
||||
var t = val.Type()
|
||||
|
||||
fmt.Fprint(buf, t)
|
||||
fmt.Fprint(buf, "{")
|
||||
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
if formatOutput {
|
||||
fmt.Fprintln(buf)
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
var name = t.Field(i).Name
|
||||
|
||||
if formatOutput {
|
||||
for ind := 0; ind < level; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, name)
|
||||
fmt.Fprint(buf, ": ")
|
||||
|
||||
if structFilter != nil && structFilter(t.String(), name) {
|
||||
fmt.Fprint(buf, "ignore")
|
||||
} else {
|
||||
printKeyValue(buf, val.Field(i), pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, ",")
|
||||
}
|
||||
|
||||
if formatOutput {
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
for ind := 0; ind < level-1; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, "}")
|
||||
case reflect.Array, reflect.Slice:
|
||||
fmt.Fprint(buf, val.Type())
|
||||
fmt.Fprint(buf, "{")
|
||||
|
||||
var allSimple = true
|
||||
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
var elem = val.Index(i)
|
||||
|
||||
var isSimple = isSimpleType(elem, elem.Kind(), pointers, interfaces)
|
||||
|
||||
if !isSimple {
|
||||
allSimple = false
|
||||
}
|
||||
|
||||
if formatOutput && !isSimple {
|
||||
fmt.Fprintln(buf)
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
if formatOutput && !isSimple {
|
||||
for ind := 0; ind < level; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
}
|
||||
|
||||
printKeyValue(buf, elem, pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
|
||||
if i != val.Len()-1 || !allSimple {
|
||||
fmt.Fprint(buf, ",")
|
||||
}
|
||||
}
|
||||
|
||||
if formatOutput && !allSimple {
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
for ind := 0; ind < level-1; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, "}")
|
||||
case reflect.Map:
|
||||
var t = val.Type()
|
||||
var keys = val.MapKeys()
|
||||
|
||||
fmt.Fprint(buf, t)
|
||||
fmt.Fprint(buf, "{")
|
||||
|
||||
var allSimple = true
|
||||
|
||||
for i := 0; i < len(keys); i++ {
|
||||
var elem = val.MapIndex(keys[i])
|
||||
|
||||
var isSimple = isSimpleType(elem, elem.Kind(), pointers, interfaces)
|
||||
|
||||
if !isSimple {
|
||||
allSimple = false
|
||||
}
|
||||
|
||||
if formatOutput && !isSimple {
|
||||
fmt.Fprintln(buf)
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
if formatOutput && !isSimple {
|
||||
for ind := 0; ind <= level; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
}
|
||||
|
||||
printKeyValue(buf, keys[i], pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
fmt.Fprint(buf, ": ")
|
||||
printKeyValue(buf, elem, pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
|
||||
if i != val.Len()-1 || !allSimple {
|
||||
fmt.Fprint(buf, ",")
|
||||
}
|
||||
}
|
||||
|
||||
if formatOutput && !allSimple {
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
for ind := 0; ind < level-1; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, "}")
|
||||
case reflect.Chan:
|
||||
fmt.Fprint(buf, val.Type())
|
||||
case reflect.Invalid:
|
||||
fmt.Fprint(buf, "invalid")
|
||||
default:
|
||||
fmt.Fprint(buf, "unknow")
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPointerInfo dump pointer value
|
||||
func PrintPointerInfo(buf *bytes.Buffer, headlen int, pointers *pointerInfo) {
|
||||
var anyused = false
|
||||
var pointerNum = 0
|
||||
|
||||
for p := pointers; p != nil; p = p.prev {
|
||||
if len(p.used) > 0 {
|
||||
anyused = true
|
||||
}
|
||||
pointerNum++
|
||||
p.n = pointerNum
|
||||
}
|
||||
|
||||
if anyused {
|
||||
var pointerBufs = make([][]rune, pointerNum+1)
|
||||
|
||||
for i := 0; i < len(pointerBufs); i++ {
|
||||
var pointerBuf = make([]rune, buf.Len()+headlen)
|
||||
|
||||
for j := 0; j < len(pointerBuf); j++ {
|
||||
pointerBuf[j] = ' '
|
||||
}
|
||||
|
||||
pointerBufs[i] = pointerBuf
|
||||
}
|
||||
|
||||
for pn := 0; pn <= pointerNum; pn++ {
|
||||
for p := pointers; p != nil; p = p.prev {
|
||||
if len(p.used) > 0 && p.n >= pn {
|
||||
if pn == p.n {
|
||||
pointerBufs[pn][p.pos+headlen] = '└'
|
||||
|
||||
var maxpos = 0
|
||||
|
||||
for i, pos := range p.used {
|
||||
if i < len(p.used)-1 {
|
||||
pointerBufs[pn][pos+headlen] = '┴'
|
||||
} else {
|
||||
pointerBufs[pn][pos+headlen] = '┘'
|
||||
}
|
||||
|
||||
maxpos = pos
|
||||
}
|
||||
|
||||
for i := 0; i < maxpos-p.pos-1; i++ {
|
||||
if pointerBufs[pn][i+p.pos+headlen+1] == ' ' {
|
||||
pointerBufs[pn][i+p.pos+headlen+1] = '─'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pointerBufs[pn][p.pos+headlen] = '│'
|
||||
|
||||
for _, pos := range p.used {
|
||||
if pointerBufs[pn][pos+headlen] == ' ' {
|
||||
pointerBufs[pn][pos+headlen] = '│'
|
||||
} else {
|
||||
pointerBufs[pn][pos+headlen] = '┼'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(string(pointerBufs[pn]) + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stack get stack bytes
|
||||
func Stack(skip int, indent string) []byte {
|
||||
var buf = new(bytes.Buffer)
|
||||
|
||||
for i := skip; ; i++ {
|
||||
var pc, file, line, ok = runtime.Caller(i)
|
||||
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
buf.WriteString(indent)
|
||||
|
||||
fmt.Fprintf(buf, "at %s() [%s:%d]\n", function(pc), file, line)
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// return the name of the function containing the PC if possible,
|
||||
func function(pc uintptr) []byte {
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
return dunno
|
||||
}
|
||||
name := []byte(fn.Name())
|
||||
// The name includes the path name to the package, which is unnecessary
|
||||
// since the file name is already included. Plus, it has center dots.
|
||||
// That is, we see
|
||||
// runtime/debug.*T·ptrmethod
|
||||
// and want
|
||||
// *T.ptrmethod
|
||||
if period := bytes.Index(name, dot); period >= 0 {
|
||||
name = name[period+1:]
|
||||
}
|
||||
name = bytes.Replace(name, centerDot, dot, -1)
|
||||
return name
|
||||
}
|
101
vendor/github.com/astaxie/beego/utils/file.go
generated
vendored
Normal file
101
vendor/github.com/astaxie/beego/utils/file.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// SelfPath gets compiled executable file absolute path
|
||||
func SelfPath() string {
|
||||
path, _ := filepath.Abs(os.Args[0])
|
||||
return path
|
||||
}
|
||||
|
||||
// SelfDir gets compiled executable file directory
|
||||
func SelfDir() string {
|
||||
return filepath.Dir(SelfPath())
|
||||
}
|
||||
|
||||
// FileExists reports whether the named file or directory exists.
|
||||
func FileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SearchFile Search a file in paths.
|
||||
// this is often used in search config file in /etc ~/
|
||||
func SearchFile(filename string, paths ...string) (fullpath string, err error) {
|
||||
for _, path := range paths {
|
||||
if fullpath = filepath.Join(path, filename); FileExists(fullpath) {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errors.New(fullpath + " not found in paths")
|
||||
return
|
||||
}
|
||||
|
||||
// GrepFile like command grep -E
|
||||
// for example: GrepFile(`^hello`, "hello.txt")
|
||||
// \n is striped while read
|
||||
func GrepFile(patten string, filename string) (lines []string, err error) {
|
||||
re, err := regexp.Compile(patten)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fd, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
lines = make([]string, 0)
|
||||
reader := bufio.NewReader(fd)
|
||||
prefix := ""
|
||||
isLongLine := false
|
||||
for {
|
||||
byteLine, isPrefix, er := reader.ReadLine()
|
||||
if er != nil && er != io.EOF {
|
||||
return nil, er
|
||||
}
|
||||
if er == io.EOF {
|
||||
break
|
||||
}
|
||||
line := string(byteLine)
|
||||
if isPrefix {
|
||||
prefix += line
|
||||
continue
|
||||
} else {
|
||||
isLongLine = true
|
||||
}
|
||||
|
||||
line = prefix + line
|
||||
if isLongLine {
|
||||
prefix = ""
|
||||
}
|
||||
if re.MatchString(line) {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
return lines, nil
|
||||
}
|
423
vendor/github.com/astaxie/beego/utils/mail.go
generated
vendored
Normal file
423
vendor/github.com/astaxie/beego/utils/mail.go
generated
vendored
Normal file
@ -0,0 +1,423 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/mail"
|
||||
"net/smtp"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
maxLineLength = 76
|
||||
|
||||
upperhex = "0123456789ABCDEF"
|
||||
)
|
||||
|
||||
// Email is the type used for email messages
|
||||
type Email struct {
|
||||
Auth smtp.Auth
|
||||
Identity string `json:"identity"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
From string `json:"from"`
|
||||
To []string
|
||||
Bcc []string
|
||||
Cc []string
|
||||
Subject string
|
||||
Text string // Plaintext message (optional)
|
||||
HTML string // Html message (optional)
|
||||
Headers textproto.MIMEHeader
|
||||
Attachments []*Attachment
|
||||
ReadReceipt []string
|
||||
}
|
||||
|
||||
// Attachment is a struct representing an email attachment.
|
||||
// Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question
|
||||
type Attachment struct {
|
||||
Filename string
|
||||
Header textproto.MIMEHeader
|
||||
Content []byte
|
||||
}
|
||||
|
||||
// NewEMail create new Email struct with config json.
|
||||
// config json is followed from Email struct fields.
|
||||
func NewEMail(config string) *Email {
|
||||
e := new(Email)
|
||||
e.Headers = textproto.MIMEHeader{}
|
||||
err := json.Unmarshal([]byte(config), e)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Bytes Make all send information to byte
|
||||
func (e *Email) Bytes() ([]byte, error) {
|
||||
buff := &bytes.Buffer{}
|
||||
w := multipart.NewWriter(buff)
|
||||
// Set the appropriate headers (overwriting any conflicts)
|
||||
// Leave out Bcc (only included in envelope headers)
|
||||
e.Headers.Set("To", strings.Join(e.To, ","))
|
||||
if e.Cc != nil {
|
||||
e.Headers.Set("Cc", strings.Join(e.Cc, ","))
|
||||
}
|
||||
e.Headers.Set("From", e.From)
|
||||
e.Headers.Set("Subject", e.Subject)
|
||||
if len(e.ReadReceipt) != 0 {
|
||||
e.Headers.Set("Disposition-Notification-To", strings.Join(e.ReadReceipt, ","))
|
||||
}
|
||||
e.Headers.Set("MIME-Version", "1.0")
|
||||
|
||||
// Write the envelope headers (including any custom headers)
|
||||
if err := headerToBytes(buff, e.Headers); err != nil {
|
||||
return nil, fmt.Errorf("Failed to render message headers: %s", err)
|
||||
}
|
||||
|
||||
e.Headers.Set("Content-Type", fmt.Sprintf("multipart/mixed;\r\n boundary=%s\r\n", w.Boundary()))
|
||||
fmt.Fprintf(buff, "%s:", "Content-Type")
|
||||
fmt.Fprintf(buff, " %s\r\n", fmt.Sprintf("multipart/mixed;\r\n boundary=%s\r\n", w.Boundary()))
|
||||
|
||||
// Start the multipart/mixed part
|
||||
fmt.Fprintf(buff, "--%s\r\n", w.Boundary())
|
||||
header := textproto.MIMEHeader{}
|
||||
// Check to see if there is a Text or HTML field
|
||||
if e.Text != "" || e.HTML != "" {
|
||||
subWriter := multipart.NewWriter(buff)
|
||||
// Create the multipart alternative part
|
||||
header.Set("Content-Type", fmt.Sprintf("multipart/alternative;\r\n boundary=%s\r\n", subWriter.Boundary()))
|
||||
// Write the header
|
||||
if err := headerToBytes(buff, header); err != nil {
|
||||
return nil, fmt.Errorf("Failed to render multipart message headers: %s", err)
|
||||
}
|
||||
// Create the body sections
|
||||
if e.Text != "" {
|
||||
header.Set("Content-Type", fmt.Sprintf("text/plain; charset=UTF-8"))
|
||||
header.Set("Content-Transfer-Encoding", "quoted-printable")
|
||||
if _, err := subWriter.CreatePart(header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Write the text
|
||||
if err := quotePrintEncode(buff, e.Text); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if e.HTML != "" {
|
||||
header.Set("Content-Type", fmt.Sprintf("text/html; charset=UTF-8"))
|
||||
header.Set("Content-Transfer-Encoding", "quoted-printable")
|
||||
if _, err := subWriter.CreatePart(header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Write the text
|
||||
if err := quotePrintEncode(buff, e.HTML); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := subWriter.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Create attachment part, if necessary
|
||||
for _, a := range e.Attachments {
|
||||
ap, err := w.CreatePart(a.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Write the base64Wrapped content to the part
|
||||
base64Wrap(ap, a.Content)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buff.Bytes(), nil
|
||||
}
|
||||
|
||||
// AttachFile Add attach file to the send mail
|
||||
func (e *Email) AttachFile(args ...string) (a *Attachment, err error) {
|
||||
if len(args) < 1 && len(args) > 2 {
|
||||
err = errors.New("Must specify a file name and number of parameters can not exceed at least two")
|
||||
return
|
||||
}
|
||||
filename := args[0]
|
||||
id := ""
|
||||
if len(args) > 1 {
|
||||
id = args[1]
|
||||
}
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ct := mime.TypeByExtension(filepath.Ext(filename))
|
||||
basename := path.Base(filename)
|
||||
return e.Attach(f, basename, ct, id)
|
||||
}
|
||||
|
||||
// Attach is used to attach content from an io.Reader to the email.
|
||||
// Parameters include an io.Reader, the desired filename for the attachment, and the Content-Type.
|
||||
func (e *Email) Attach(r io.Reader, filename string, args ...string) (a *Attachment, err error) {
|
||||
if len(args) < 1 && len(args) > 2 {
|
||||
err = errors.New("Must specify the file type and number of parameters can not exceed at least two")
|
||||
return
|
||||
}
|
||||
c := args[0] //Content-Type
|
||||
id := ""
|
||||
if len(args) > 1 {
|
||||
id = args[1] //Content-ID
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
if _, err = io.Copy(&buffer, r); err != nil {
|
||||
return
|
||||
}
|
||||
at := &Attachment{
|
||||
Filename: filename,
|
||||
Header: textproto.MIMEHeader{},
|
||||
Content: buffer.Bytes(),
|
||||
}
|
||||
// Get the Content-Type to be used in the MIMEHeader
|
||||
if c != "" {
|
||||
at.Header.Set("Content-Type", c)
|
||||
} else {
|
||||
// If the Content-Type is blank, set the Content-Type to "application/octet-stream"
|
||||
at.Header.Set("Content-Type", "application/octet-stream")
|
||||
}
|
||||
if id != "" {
|
||||
at.Header.Set("Content-Disposition", fmt.Sprintf("inline;\r\n filename=\"%s\"", filename))
|
||||
at.Header.Set("Content-ID", fmt.Sprintf("<%s>", id))
|
||||
} else {
|
||||
at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename))
|
||||
}
|
||||
at.Header.Set("Content-Transfer-Encoding", "base64")
|
||||
e.Attachments = append(e.Attachments, at)
|
||||
return at, nil
|
||||
}
|
||||
|
||||
// Send will send out the mail
|
||||
func (e *Email) Send() error {
|
||||
if e.Auth == nil {
|
||||
e.Auth = smtp.PlainAuth(e.Identity, e.Username, e.Password, e.Host)
|
||||
}
|
||||
// Merge the To, Cc, and Bcc fields
|
||||
to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
|
||||
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
|
||||
// Check to make sure there is at least one recipient and one "From" address
|
||||
if len(to) == 0 {
|
||||
return errors.New("Must specify at least one To address")
|
||||
}
|
||||
|
||||
// Use the username if no From is provided
|
||||
if len(e.From) == 0 {
|
||||
e.From = e.Username
|
||||
}
|
||||
|
||||
from, err := mail.ParseAddress(e.From)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// use mail's RFC 2047 to encode any string
|
||||
e.Subject = qEncode("utf-8", e.Subject)
|
||||
|
||||
raw, err := e.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return smtp.SendMail(e.Host+":"+strconv.Itoa(e.Port), e.Auth, from.Address, to, raw)
|
||||
}
|
||||
|
||||
// quotePrintEncode writes the quoted-printable text to the IO Writer (according to RFC 2045)
|
||||
func quotePrintEncode(w io.Writer, s string) error {
|
||||
var buf [3]byte
|
||||
mc := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
// We're assuming Unix style text formats as input (LF line break), and
|
||||
// quoted-printble uses CRLF line breaks. (Literal CRs will become
|
||||
// "=0D", but probably shouldn't be there to begin with!)
|
||||
if c == '\n' {
|
||||
io.WriteString(w, "\r\n")
|
||||
mc = 0
|
||||
continue
|
||||
}
|
||||
|
||||
var nextOut []byte
|
||||
if isPrintable(c) {
|
||||
nextOut = append(buf[:0], c)
|
||||
} else {
|
||||
nextOut = buf[:]
|
||||
qpEscape(nextOut, c)
|
||||
}
|
||||
|
||||
// Add a soft line break if the next (encoded) byte would push this line
|
||||
// to or past the limit.
|
||||
if mc+len(nextOut) >= maxLineLength {
|
||||
if _, err := io.WriteString(w, "=\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
mc = 0
|
||||
}
|
||||
|
||||
if _, err := w.Write(nextOut); err != nil {
|
||||
return err
|
||||
}
|
||||
mc += len(nextOut)
|
||||
}
|
||||
// No trailing end-of-line?? Soft line break, then. TODO: is this sane?
|
||||
if mc > 0 {
|
||||
io.WriteString(w, "=\r\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isPrintable returns true if the rune given is "printable" according to RFC 2045, false otherwise
|
||||
func isPrintable(c byte) bool {
|
||||
return (c >= '!' && c <= '<') || (c >= '>' && c <= '~') || (c == ' ' || c == '\n' || c == '\t')
|
||||
}
|
||||
|
||||
// qpEscape is a helper function for quotePrintEncode which escapes a
|
||||
// non-printable byte. Expects len(dest) == 3.
|
||||
func qpEscape(dest []byte, c byte) {
|
||||
const nums = "0123456789ABCDEF"
|
||||
dest[0] = '='
|
||||
dest[1] = nums[(c&0xf0)>>4]
|
||||
dest[2] = nums[(c & 0xf)]
|
||||
}
|
||||
|
||||
// headerToBytes enumerates the key and values in the header, and writes the results to the IO Writer
|
||||
func headerToBytes(w io.Writer, t textproto.MIMEHeader) error {
|
||||
for k, v := range t {
|
||||
// Write the header key
|
||||
_, err := fmt.Fprintf(w, "%s:", k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Write each value in the header
|
||||
for _, c := range v {
|
||||
_, err := fmt.Fprintf(w, " %s\r\n", c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// base64Wrap encodes the attachment content, and wraps it according to RFC 2045 standards (every 76 chars)
|
||||
// The output is then written to the specified io.Writer
|
||||
func base64Wrap(w io.Writer, b []byte) {
|
||||
// 57 raw bytes per 76-byte base64 line.
|
||||
const maxRaw = 57
|
||||
// Buffer for each line, including trailing CRLF.
|
||||
var buffer [maxLineLength + len("\r\n")]byte
|
||||
copy(buffer[maxLineLength:], "\r\n")
|
||||
// Process raw chunks until there's no longer enough to fill a line.
|
||||
for len(b) >= maxRaw {
|
||||
base64.StdEncoding.Encode(buffer[:], b[:maxRaw])
|
||||
w.Write(buffer[:])
|
||||
b = b[maxRaw:]
|
||||
}
|
||||
// Handle the last chunk of bytes.
|
||||
if len(b) > 0 {
|
||||
out := buffer[:base64.StdEncoding.EncodedLen(len(b))]
|
||||
base64.StdEncoding.Encode(out, b)
|
||||
out = append(out, "\r\n"...)
|
||||
w.Write(out)
|
||||
}
|
||||
}
|
||||
|
||||
// Encode returns the encoded-word form of s. If s is ASCII without special
|
||||
// characters, it is returned unchanged. The provided charset is the IANA
|
||||
// charset name of s. It is case insensitive.
|
||||
// RFC 2047 encoded-word
|
||||
func qEncode(charset, s string) string {
|
||||
if !needsEncoding(s) {
|
||||
return s
|
||||
}
|
||||
return encodeWord(charset, s)
|
||||
}
|
||||
|
||||
func needsEncoding(s string) bool {
|
||||
for _, b := range s {
|
||||
if (b < ' ' || b > '~') && b != '\t' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// encodeWord encodes a string into an encoded-word.
|
||||
func encodeWord(charset, s string) string {
|
||||
buf := getBuffer()
|
||||
|
||||
buf.WriteString("=?")
|
||||
buf.WriteString(charset)
|
||||
buf.WriteByte('?')
|
||||
buf.WriteByte('q')
|
||||
buf.WriteByte('?')
|
||||
|
||||
enc := make([]byte, 3)
|
||||
for i := 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case b == ' ':
|
||||
buf.WriteByte('_')
|
||||
case b <= '~' && b >= '!' && b != '=' && b != '?' && b != '_':
|
||||
buf.WriteByte(b)
|
||||
default:
|
||||
enc[0] = '='
|
||||
enc[1] = upperhex[b>>4]
|
||||
enc[2] = upperhex[b&0x0f]
|
||||
buf.Write(enc)
|
||||
}
|
||||
}
|
||||
buf.WriteString("?=")
|
||||
|
||||
es := buf.String()
|
||||
putBuffer(buf)
|
||||
return es
|
||||
}
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
func getBuffer() *bytes.Buffer {
|
||||
return bufPool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func putBuffer(buf *bytes.Buffer) {
|
||||
if buf.Len() > 1024 {
|
||||
return
|
||||
}
|
||||
buf.Reset()
|
||||
bufPool.Put(buf)
|
||||
}
|
44
vendor/github.com/astaxie/beego/utils/rand.go
generated
vendored
Normal file
44
vendor/github.com/astaxie/beego/utils/rand.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
r "math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var alphaNum = []byte(`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`)
|
||||
|
||||
// RandomCreateBytes generate random []byte by specify chars.
|
||||
func RandomCreateBytes(n int, alphabets ...byte) []byte {
|
||||
if len(alphabets) == 0 {
|
||||
alphabets = alphaNum
|
||||
}
|
||||
var bytes = make([]byte, n)
|
||||
var randBy bool
|
||||
if num, err := rand.Read(bytes); num != n || err != nil {
|
||||
r.Seed(time.Now().UnixNano())
|
||||
randBy = true
|
||||
}
|
||||
for i, b := range bytes {
|
||||
if randBy {
|
||||
bytes[i] = alphabets[r.Intn(len(alphabets))]
|
||||
} else {
|
||||
bytes[i] = alphabets[b%byte(len(alphabets))]
|
||||
}
|
||||
}
|
||||
return bytes
|
||||
}
|
86
vendor/github.com/astaxie/beego/utils/safemap.go
generated
vendored
Normal file
86
vendor/github.com/astaxie/beego/utils/safemap.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// BeeMap is a map with lock
|
||||
type BeeMap struct {
|
||||
lock *sync.RWMutex
|
||||
bm map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewBeeMap return new safemap
|
||||
func NewBeeMap() *BeeMap {
|
||||
return &BeeMap{
|
||||
lock: new(sync.RWMutex),
|
||||
bm: make(map[interface{}]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Get from maps return the k's value
|
||||
func (m *BeeMap) Get(k interface{}) interface{} {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
if val, ok := m.bm[k]; ok {
|
||||
return val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set Maps the given key and value. Returns false
|
||||
// if the key is already in the map and changes nothing.
|
||||
func (m *BeeMap) Set(k interface{}, v interface{}) bool {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
if val, ok := m.bm[k]; !ok {
|
||||
m.bm[k] = v
|
||||
} else if val != v {
|
||||
m.bm[k] = v
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Check Returns true if k is exist in the map.
|
||||
func (m *BeeMap) Check(k interface{}) bool {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
if _, ok := m.bm[k]; !ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Delete the given key and value.
|
||||
func (m *BeeMap) Delete(k interface{}) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
delete(m.bm, k)
|
||||
}
|
||||
|
||||
// Items returns all items in safemap.
|
||||
func (m *BeeMap) Items() map[interface{}]interface{} {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
r := make(map[interface{}]interface{})
|
||||
for k, v := range m.bm {
|
||||
r[k] = v
|
||||
}
|
||||
return r
|
||||
}
|
170
vendor/github.com/astaxie/beego/utils/slice.go
generated
vendored
Normal file
170
vendor/github.com/astaxie/beego/utils/slice.go
generated
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type reducetype func(interface{}) interface{}
|
||||
type filtertype func(interface{}) bool
|
||||
|
||||
// InSlice checks given string in string slice or not.
|
||||
func InSlice(v string, sl []string) bool {
|
||||
for _, vv := range sl {
|
||||
if vv == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InSliceIface checks given interface in interface slice.
|
||||
func InSliceIface(v interface{}, sl []interface{}) bool {
|
||||
for _, vv := range sl {
|
||||
if vv == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SliceRandList generate an int slice from min to max.
|
||||
func SliceRandList(min, max int) []int {
|
||||
if max < min {
|
||||
min, max = max, min
|
||||
}
|
||||
length := max - min + 1
|
||||
t0 := time.Now()
|
||||
rand.Seed(int64(t0.Nanosecond()))
|
||||
list := rand.Perm(length)
|
||||
for index := range list {
|
||||
list[index] += min
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// SliceMerge merges interface slices to one slice.
|
||||
func SliceMerge(slice1, slice2 []interface{}) (c []interface{}) {
|
||||
c = append(slice1, slice2...)
|
||||
return
|
||||
}
|
||||
|
||||
// SliceReduce generates a new slice after parsing every value by reduce function
|
||||
func SliceReduce(slice []interface{}, a reducetype) (dslice []interface{}) {
|
||||
for _, v := range slice {
|
||||
dslice = append(dslice, a(v))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceRand returns random one from slice.
|
||||
func SliceRand(a []interface{}) (b interface{}) {
|
||||
randnum := rand.Intn(len(a))
|
||||
b = a[randnum]
|
||||
return
|
||||
}
|
||||
|
||||
// SliceSum sums all values in int64 slice.
|
||||
func SliceSum(intslice []int64) (sum int64) {
|
||||
for _, v := range intslice {
|
||||
sum += v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceFilter generates a new slice after filter function.
|
||||
func SliceFilter(slice []interface{}, a filtertype) (ftslice []interface{}) {
|
||||
for _, v := range slice {
|
||||
if a(v) {
|
||||
ftslice = append(ftslice, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceDiff returns diff slice of slice1 - slice2.
|
||||
func SliceDiff(slice1, slice2 []interface{}) (diffslice []interface{}) {
|
||||
for _, v := range slice1 {
|
||||
if !InSliceIface(v, slice2) {
|
||||
diffslice = append(diffslice, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceIntersect returns slice that are present in all the slice1 and slice2.
|
||||
func SliceIntersect(slice1, slice2 []interface{}) (diffslice []interface{}) {
|
||||
for _, v := range slice1 {
|
||||
if InSliceIface(v, slice2) {
|
||||
diffslice = append(diffslice, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceChunk separates one slice to some sized slice.
|
||||
func SliceChunk(slice []interface{}, size int) (chunkslice [][]interface{}) {
|
||||
if size >= len(slice) {
|
||||
chunkslice = append(chunkslice, slice)
|
||||
return
|
||||
}
|
||||
end := size
|
||||
for i := 0; i <= (len(slice) - size); i += size {
|
||||
chunkslice = append(chunkslice, slice[i:end])
|
||||
end += size
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceRange generates a new slice from begin to end with step duration of int64 number.
|
||||
func SliceRange(start, end, step int64) (intslice []int64) {
|
||||
for i := start; i <= end; i += step {
|
||||
intslice = append(intslice, i)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SlicePad prepends size number of val into slice.
|
||||
func SlicePad(slice []interface{}, size int, val interface{}) []interface{} {
|
||||
if size <= len(slice) {
|
||||
return slice
|
||||
}
|
||||
for i := 0; i < (size - len(slice)); i++ {
|
||||
slice = append(slice, val)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// SliceUnique cleans repeated values in slice.
|
||||
func SliceUnique(slice []interface{}) (uniqueslice []interface{}) {
|
||||
for _, v := range slice {
|
||||
if !InSliceIface(v, uniqueslice) {
|
||||
uniqueslice = append(uniqueslice, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceShuffle shuffles a slice.
|
||||
func SliceShuffle(slice []interface{}) []interface{} {
|
||||
for i := 0; i < len(slice); i++ {
|
||||
a := rand.Intn(len(slice))
|
||||
b := rand.Intn(len(slice))
|
||||
slice[a], slice[b] = slice[b], slice[a]
|
||||
}
|
||||
return slice
|
||||
}
|
48
vendor/github.com/go-sql-driver/mysql/AUTHORS
generated
vendored
Normal file
48
vendor/github.com/go-sql-driver/mysql/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
# This is the official list of Go-MySQL-Driver authors for copyright purposes.
|
||||
|
||||
# If you are submitting a patch, please add your name or the name of the
|
||||
# organization which holds the copyright to this list in alphabetical order.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name <email address>
|
||||
# The email address is not required for organizations.
|
||||
# Please keep the list sorted.
|
||||
|
||||
|
||||
# Individual Persons
|
||||
|
||||
Aaron Hopkins <go-sql-driver at die.net>
|
||||
Arne Hormann <arnehormann at gmail.com>
|
||||
Carlos Nieto <jose.carlos at menteslibres.net>
|
||||
Chris Moos <chris at tech9computers.com>
|
||||
Daniel Nichter <nil at codenode.com>
|
||||
DisposaBoy <disposaboy at dby.me>
|
||||
Frederick Mayle <frederickmayle at gmail.com>
|
||||
Gustavo Kristic <gkristic at gmail.com>
|
||||
Hanno Braun <mail at hannobraun.com>
|
||||
Henri Yandell <flamefew at gmail.com>
|
||||
Hirotaka Yamamoto <ymmt2005 at gmail.com>
|
||||
INADA Naoki <songofacandy at gmail.com>
|
||||
James Harr <james.harr at gmail.com>
|
||||
Jian Zhen <zhenjl at gmail.com>
|
||||
Joshua Prunier <joshua.prunier at gmail.com>
|
||||
Julien Lefevre <julien.lefevr at gmail.com>
|
||||
Julien Schmidt <go-sql-driver at julienschmidt.com>
|
||||
Kamil Dziedzic <kamil at klecza.pl>
|
||||
Kevin Malachowski <kevin at chowski.com>
|
||||
Leonardo YongUk Kim <dalinaum at gmail.com>
|
||||
Lucas Liu <extrafliu at gmail.com>
|
||||
Luke Scott <luke at webconnex.com>
|
||||
Michael Woolnough <michael.woolnough at gmail.com>
|
||||
Nicola Peduzzi <thenikso at gmail.com>
|
||||
Runrioter Wung <runrioter at gmail.com>
|
||||
Soroush Pour <me at soroushjp.com>
|
||||
Stan Putrya <root.vagner at gmail.com>
|
||||
Xiaobing Jiang <s7v7nislands at gmail.com>
|
||||
Xiuming Chen <cc at cxm.cc>
|
||||
|
||||
# Organizations
|
||||
|
||||
Barracuda Networks, Inc.
|
||||
Google Inc.
|
||||
Stripe Inc.
|
103
vendor/github.com/go-sql-driver/mysql/CHANGELOG.md
generated
vendored
Normal file
103
vendor/github.com/go-sql-driver/mysql/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
## HEAD
|
||||
|
||||
Changes:
|
||||
|
||||
- Go 1.1 is no longer supported
|
||||
- Use decimals field from MySQL to format time types (#249)
|
||||
- Buffer optimizations (#269)
|
||||
- TLS ServerName defaults to the host (#283)
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249)
|
||||
- Fixed handling of queries without columns and rows (#255)
|
||||
- Fixed a panic when SetKeepAlive() failed (#298)
|
||||
- Support receiving ERR packet while reading rows (#321)
|
||||
- Fixed reading NULL length-encoded integers in MySQL 5.6+ (#349)
|
||||
- Fixed absolute paths support in LOAD LOCAL DATA INFILE (#356)
|
||||
- Actually zero out bytes in handshake response (#378)
|
||||
- Fixed race condition in registering LOAD DATA INFILE handler (#383)
|
||||
- Fixed tests with MySQL 5.7.9+ (#380)
|
||||
- QueryUnescape TLS config names (#397)
|
||||
- Fixed "broken pipe" error by writing to closed socket (#390)
|
||||
|
||||
New Features:
|
||||
- Support for returning table alias on Columns() (#289, #359, #382)
|
||||
- Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318)
|
||||
- Support for uint64 parameters with high bit set (#332, #345)
|
||||
- Cleartext authentication plugin support (#327)
|
||||
|
||||
|
||||
|
||||
## Version 1.2 (2014-06-03)
|
||||
|
||||
Changes:
|
||||
|
||||
- We switched back to a "rolling release". `go get` installs the current master branch again
|
||||
- Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver
|
||||
- Exported errors to allow easy checking from application code
|
||||
- Enabled TCP Keepalives on TCP connections
|
||||
- Optimized INFILE handling (better buffer size calculation, lazy init, ...)
|
||||
- The DSN parser also checks for a missing separating slash
|
||||
- Faster binary date / datetime to string formatting
|
||||
- Also exported the MySQLWarning type
|
||||
- mysqlConn.Close returns the first error encountered instead of ignoring all errors
|
||||
- writePacket() automatically writes the packet size to the header
|
||||
- readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets
|
||||
|
||||
New Features:
|
||||
|
||||
- `RegisterDial` allows the usage of a custom dial function to establish the network connection
|
||||
- Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter
|
||||
- Logging of critical errors is configurable with `SetLogger`
|
||||
- Google CloudSQL support
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Allow more than 32 parameters in prepared statements
|
||||
- Various old_password fixes
|
||||
- Fixed TestConcurrent test to pass Go's race detection
|
||||
- Fixed appendLengthEncodedInteger for large numbers
|
||||
- Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo)
|
||||
|
||||
|
||||
## Version 1.1 (2013-11-02)
|
||||
|
||||
Changes:
|
||||
|
||||
- Go-MySQL-Driver now requires Go 1.1
|
||||
- Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore
|
||||
- Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors
|
||||
- `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")`
|
||||
- DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'.
|
||||
- Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries
|
||||
- Optimized the buffer for reading
|
||||
- stmt.Query now caches column metadata
|
||||
- New Logo
|
||||
- Changed the copyright header to include all contributors
|
||||
- Improved the LOAD INFILE documentation
|
||||
- The driver struct is now exported to make the driver directly accessible
|
||||
- Refactored the driver tests
|
||||
- Added more benchmarks and moved all to a separate file
|
||||
- Other small refactoring
|
||||
|
||||
New Features:
|
||||
|
||||
- Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure
|
||||
- Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs
|
||||
- Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification
|
||||
- Convert to DB timezone when inserting `time.Time`
|
||||
- Splitted packets (more than 16MB) are now merged correctly
|
||||
- Fixed false positive `io.EOF` errors when the data was fully read
|
||||
- Avoid panics on reuse of closed connections
|
||||
- Fixed empty string producing false nil values
|
||||
- Fixed sign byte for positive TIME fields
|
||||
|
||||
|
||||
## Version 1.0 (2013-05-14)
|
||||
|
||||
Initial Release
|
40
vendor/github.com/go-sql-driver/mysql/CONTRIBUTING.md
generated
vendored
Normal file
40
vendor/github.com/go-sql-driver/mysql/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
# Contributing Guidelines
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed).
|
||||
|
||||
Please provide the following minimum information:
|
||||
* Your Go-MySQL-Driver version (or git SHA)
|
||||
* Your Go version (run `go version` in your console)
|
||||
* A detailed issue description
|
||||
* Error Log if present
|
||||
* If possible, a short example
|
||||
|
||||
|
||||
## Contributing Code
|
||||
|
||||
By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
|
||||
Don't forget to add yourself to the AUTHORS file.
|
||||
|
||||
### Pull Requests Checklist
|
||||
|
||||
Please check the following points before submitting your pull request:
|
||||
- [x] Code compiles correctly
|
||||
- [x] Created tests, if possible
|
||||
- [x] All tests pass
|
||||
- [x] Extended the README / documentation, if necessary
|
||||
- [x] Added yourself to the AUTHORS file
|
||||
|
||||
### Code Review
|
||||
|
||||
Everyone is invited to review and comment on pull requests.
|
||||
If it looks fine to you, comment with "LGTM" (Looks good to me).
|
||||
|
||||
If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes.
|
||||
|
||||
Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM".
|
||||
|
||||
## Development Ideas
|
||||
|
||||
If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page.
|
373
vendor/github.com/go-sql-driver/mysql/LICENSE
generated
vendored
Normal file
373
vendor/github.com/go-sql-driver/mysql/LICENSE
generated
vendored
Normal file
@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
386
vendor/github.com/go-sql-driver/mysql/README.md
generated
vendored
Normal file
386
vendor/github.com/go-sql-driver/mysql/README.md
generated
vendored
Normal file
@ -0,0 +1,386 @@
|
||||
# Go-MySQL-Driver
|
||||
|
||||
A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) package
|
||||
|
||||
![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin")
|
||||
|
||||
**Latest stable Release:** [Version 1.2 (June 03, 2014)](https://github.com/go-sql-driver/mysql/releases)
|
||||
|
||||
[![Build Status](https://travis-ci.org/go-sql-driver/mysql.png?branch=master)](https://travis-ci.org/go-sql-driver/mysql)
|
||||
|
||||
---------------------------------------
|
||||
* [Features](#features)
|
||||
* [Requirements](#requirements)
|
||||
* [Installation](#installation)
|
||||
* [Usage](#usage)
|
||||
* [DSN (Data Source Name)](#dsn-data-source-name)
|
||||
* [Password](#password)
|
||||
* [Protocol](#protocol)
|
||||
* [Address](#address)
|
||||
* [Parameters](#parameters)
|
||||
* [Examples](#examples)
|
||||
* [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
|
||||
* [time.Time support](#timetime-support)
|
||||
* [Unicode support](#unicode-support)
|
||||
* [Testing / Development](#testing--development)
|
||||
* [License](#license)
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## Features
|
||||
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
|
||||
* Native Go implementation. No C-bindings, just pure Go
|
||||
* Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](http://godoc.org/github.com/go-sql-driver/mysql#DialFunc)
|
||||
* Automatic handling of broken connections
|
||||
* Automatic Connection Pooling *(by database/sql package)*
|
||||
* Supports queries larger than 16MB
|
||||
* Full [`sql.RawBytes`](http://golang.org/pkg/database/sql/#RawBytes) support.
|
||||
* Intelligent `LONG DATA` handling in prepared statements
|
||||
* Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
|
||||
* Optional `time.Time` parsing
|
||||
* Optional placeholder interpolation
|
||||
|
||||
## Requirements
|
||||
* Go 1.2 or higher
|
||||
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## Installation
|
||||
Simple install the package to your [$GOPATH](http://code.google.com/p/go-wiki/wiki/GOPATH "GOPATH") with the [go tool](http://golang.org/cmd/go/ "go command") from shell:
|
||||
```bash
|
||||
$ go get github.com/go-sql-driver/mysql
|
||||
```
|
||||
Make sure [Git is installed](http://git-scm.com/downloads) on your machine and in your system's `PATH`.
|
||||
|
||||
## Usage
|
||||
_Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](http://golang.org/pkg/database/sql) API then.
|
||||
|
||||
Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`:
|
||||
```go
|
||||
import "database/sql"
|
||||
import _ "github.com/go-sql-driver/mysql"
|
||||
|
||||
db, err := sql.Open("mysql", "user:password@/dbname")
|
||||
```
|
||||
|
||||
[Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples").
|
||||
|
||||
|
||||
### DSN (Data Source Name)
|
||||
|
||||
The Data Source Name has a common format, like e.g. [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php) uses it, but without type-prefix (optional parts marked by squared brackets):
|
||||
```
|
||||
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
|
||||
```
|
||||
|
||||
A DSN in its fullest form:
|
||||
```
|
||||
username:password@protocol(address)/dbname?param=value
|
||||
```
|
||||
|
||||
Except for the databasename, all values are optional. So the minimal DSN is:
|
||||
```
|
||||
/dbname
|
||||
```
|
||||
|
||||
If you do not want to preselect a database, leave `dbname` empty:
|
||||
```
|
||||
/
|
||||
```
|
||||
This has the same effect as an empty DSN string:
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
#### Password
|
||||
Passwords can consist of any character. Escaping is **not** necessary.
|
||||
|
||||
#### Protocol
|
||||
See [net.Dial](http://golang.org/pkg/net/#Dial) for more information which networks are available.
|
||||
In general you should use an Unix domain socket if available and TCP otherwise for best performance.
|
||||
|
||||
#### Address
|
||||
For TCP and UDP networks, addresses have the form `host:port`.
|
||||
If `host` is a literal IPv6 address, it must be enclosed in square brackets.
|
||||
The functions [net.JoinHostPort](http://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](http://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
|
||||
|
||||
For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`.
|
||||
|
||||
#### Parameters
|
||||
*Parameters are case-sensitive!*
|
||||
|
||||
Notice that any of `true`, `TRUE`, `True` or `1` is accepted to stand for a true boolean value. Not surprisingly, false can be specified as any of: `false`, `FALSE`, `False` or `0`.
|
||||
|
||||
##### `allowAllFiles`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files.
|
||||
[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
|
||||
|
||||
##### `allowCleartextPasswords`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`allowCleartextPasswords=true` allows using the [cleartext client side plugin](http://dev.mysql.com/doc/en/cleartext-authentication-plugin.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
|
||||
|
||||
##### `allowOldPasswords`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
`allowOldPasswords=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords).
|
||||
|
||||
##### `charset`
|
||||
|
||||
```
|
||||
Type: string
|
||||
Valid Values: <name>
|
||||
Default: none
|
||||
```
|
||||
|
||||
Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
|
||||
|
||||
Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
|
||||
Unless you need the fallback behavior, please use `collation` instead.
|
||||
|
||||
##### `collation`
|
||||
|
||||
```
|
||||
Type: string
|
||||
Valid Values: <name>
|
||||
Default: utf8_general_ci
|
||||
```
|
||||
|
||||
Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
|
||||
|
||||
A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
|
||||
|
||||
##### `clientFoundRows`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed.
|
||||
|
||||
##### `columnsWithAlias`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
When `columnsWithAlias` is true, calls to `sql.Rows.Columns()` will return the table alias and the column name separated by a dot. For example:
|
||||
|
||||
```
|
||||
SELECT u.id FROM users as u
|
||||
```
|
||||
|
||||
will return `u.id` instead of just `id` if `columnsWithAlias=true`.
|
||||
|
||||
##### `interpolateParams`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
If `interpolateParams` is true, placeholders (`?`) in calls to `db.Query()` and `db.Exec()` are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with `interpolateParams=false`.
|
||||
|
||||
*This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are blacklisted as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!*
|
||||
|
||||
##### `loc`
|
||||
|
||||
```
|
||||
Type: string
|
||||
Valid Values: <escaped name>
|
||||
Default: UTC
|
||||
```
|
||||
|
||||
Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details.
|
||||
|
||||
Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter.
|
||||
|
||||
Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
|
||||
|
||||
|
||||
##### `parseTime`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
|
||||
|
||||
|
||||
##### `strict`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`strict=true` enables the strict mode in which MySQL warnings are treated as errors.
|
||||
|
||||
By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes. See the [examples](#examples) for an DSN example.
|
||||
|
||||
|
||||
##### `timeout`
|
||||
|
||||
```
|
||||
Type: decimal number
|
||||
Default: OS default
|
||||
```
|
||||
|
||||
*Driver* side connection timeout. The value must be a string of decimal numbers, each with optional fraction and a unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
|
||||
|
||||
|
||||
##### `tls`
|
||||
|
||||
```
|
||||
Type: bool / string
|
||||
Valid Values: true, false, skip-verify, <name>
|
||||
Default: false
|
||||
```
|
||||
|
||||
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
|
||||
|
||||
|
||||
##### System Variables
|
||||
|
||||
All other parameters are interpreted as system variables:
|
||||
* `autocommit`: `"SET autocommit=<value>"`
|
||||
* [`time_zone`](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html): `"SET time_zone=<value>"`
|
||||
* [`tx_isolation`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `"SET tx_isolation=<value>"`
|
||||
* `param`: `"SET <param>=<value>"`
|
||||
|
||||
*The values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!*
|
||||
|
||||
#### Examples
|
||||
```
|
||||
user@unix(/path/to/socket)/dbname
|
||||
```
|
||||
|
||||
```
|
||||
root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local
|
||||
```
|
||||
|
||||
```
|
||||
user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true
|
||||
```
|
||||
|
||||
Use the [strict mode](#strict) but ignore notes:
|
||||
```
|
||||
user:password@/dbname?strict=true&sql_notes=false
|
||||
```
|
||||
|
||||
TCP via IPv6:
|
||||
```
|
||||
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci
|
||||
```
|
||||
|
||||
TCP on a remote host, e.g. Amazon RDS:
|
||||
```
|
||||
id:password@tcp(your-amazonaws-uri.com:3306)/dbname
|
||||
```
|
||||
|
||||
Google Cloud SQL on App Engine:
|
||||
```
|
||||
user@cloudsql(project-id:instance-name)/dbname
|
||||
```
|
||||
|
||||
TCP using default port (3306) on localhost:
|
||||
```
|
||||
user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped
|
||||
```
|
||||
|
||||
Use the default protocol (tcp) and host (localhost:3306):
|
||||
```
|
||||
user:password@/dbname
|
||||
```
|
||||
|
||||
No Database preselected:
|
||||
```
|
||||
user:password@/
|
||||
```
|
||||
|
||||
### `LOAD DATA LOCAL INFILE` support
|
||||
For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
|
||||
```go
|
||||
import "github.com/go-sql-driver/mysql"
|
||||
```
|
||||
|
||||
Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
|
||||
|
||||
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
|
||||
|
||||
See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
|
||||
|
||||
|
||||
### `time.Time` support
|
||||
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm.
|
||||
|
||||
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](http://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
|
||||
|
||||
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
|
||||
|
||||
Alternatively you can use the [`NullTime`](http://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
|
||||
|
||||
|
||||
### Unicode support
|
||||
Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default.
|
||||
|
||||
Other collations / charsets can be set using the [`collation`](#collation) DSN parameter.
|
||||
|
||||
Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default.
|
||||
|
||||
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
|
||||
|
||||
|
||||
## Testing / Development
|
||||
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
|
||||
|
||||
Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated.
|
||||
If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls).
|
||||
|
||||
See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details.
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## License
|
||||
Go-MySQL-Driver is licensed under the [Mozilla Public License Version 2.0](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
|
||||
|
||||
Mozilla summarizes the license scope as follows:
|
||||
> MPL: The copyleft applies to any files containing MPLed code.
|
||||
|
||||
|
||||
That means:
|
||||
* You can **use** the **unchanged** source code both in private and commercially
|
||||
* When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0)
|
||||
* You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**
|
||||
|
||||
Please read the [MPL 2.0 FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license.
|
||||
|
||||
You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
|
||||
|
||||
![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")
|
||||
|
19
vendor/github.com/go-sql-driver/mysql/appengine.go
generated
vendored
Normal file
19
vendor/github.com/go-sql-driver/mysql/appengine.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"appengine/cloudsql"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterDial("cloudsql", cloudsql.Dial)
|
||||
}
|
136
vendor/github.com/go-sql-driver/mysql/buffer.go
generated
vendored
Normal file
136
vendor/github.com/go-sql-driver/mysql/buffer.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import "io"
|
||||
|
||||
const defaultBufSize = 4096
|
||||
|
||||
// A buffer which is used for both reading and writing.
|
||||
// This is possible since communication on each connection is synchronous.
|
||||
// In other words, we can't write and read simultaneously on the same connection.
|
||||
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
|
||||
// Also highly optimized for this particular use case.
|
||||
type buffer struct {
|
||||
buf []byte
|
||||
rd io.Reader
|
||||
idx int
|
||||
length int
|
||||
}
|
||||
|
||||
func newBuffer(rd io.Reader) buffer {
|
||||
var b [defaultBufSize]byte
|
||||
return buffer{
|
||||
buf: b[:],
|
||||
rd: rd,
|
||||
}
|
||||
}
|
||||
|
||||
// fill reads into the buffer until at least _need_ bytes are in it
|
||||
func (b *buffer) fill(need int) error {
|
||||
n := b.length
|
||||
|
||||
// move existing data to the beginning
|
||||
if n > 0 && b.idx > 0 {
|
||||
copy(b.buf[0:n], b.buf[b.idx:])
|
||||
}
|
||||
|
||||
// grow buffer if necessary
|
||||
// TODO: let the buffer shrink again at some point
|
||||
// Maybe keep the org buf slice and swap back?
|
||||
if need > len(b.buf) {
|
||||
// Round up to the next multiple of the default size
|
||||
newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
|
||||
copy(newBuf, b.buf)
|
||||
b.buf = newBuf
|
||||
}
|
||||
|
||||
b.idx = 0
|
||||
|
||||
for {
|
||||
nn, err := b.rd.Read(b.buf[n:])
|
||||
n += nn
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
if n < need {
|
||||
continue
|
||||
}
|
||||
b.length = n
|
||||
return nil
|
||||
|
||||
case io.EOF:
|
||||
if n >= need {
|
||||
b.length = n
|
||||
return nil
|
||||
}
|
||||
return io.ErrUnexpectedEOF
|
||||
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns next N bytes from buffer.
|
||||
// The returned slice is only guaranteed to be valid until the next read
|
||||
func (b *buffer) readNext(need int) ([]byte, error) {
|
||||
if b.length < need {
|
||||
// refill
|
||||
if err := b.fill(need); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
offset := b.idx
|
||||
b.idx += need
|
||||
b.length -= need
|
||||
return b.buf[offset:b.idx], nil
|
||||
}
|
||||
|
||||
// returns a buffer with the requested size.
|
||||
// If possible, a slice from the existing buffer is returned.
|
||||
// Otherwise a bigger buffer is made.
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeBuffer(length int) []byte {
|
||||
if b.length > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// test (cheap) general case first
|
||||
if length <= defaultBufSize || length <= cap(b.buf) {
|
||||
return b.buf[:length]
|
||||
}
|
||||
|
||||
if length < maxPacketSize {
|
||||
b.buf = make([]byte, length)
|
||||
return b.buf
|
||||
}
|
||||
return make([]byte, length)
|
||||
}
|
||||
|
||||
// shortcut which can be used if the requested buffer is guaranteed to be
|
||||
// smaller than defaultBufSize
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeSmallBuffer(length int) []byte {
|
||||
if b.length == 0 {
|
||||
return b.buf[:length]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// takeCompleteBuffer returns the complete existing buffer.
|
||||
// This can be used if the necessary buffer size is unknown.
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeCompleteBuffer() []byte {
|
||||
if b.length == 0 {
|
||||
return b.buf
|
||||
}
|
||||
return nil
|
||||
}
|
250
vendor/github.com/go-sql-driver/mysql/collations.go
generated
vendored
Normal file
250
vendor/github.com/go-sql-driver/mysql/collations.go
generated
vendored
Normal file
@ -0,0 +1,250 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
const defaultCollation byte = 33 // utf8_general_ci
|
||||
|
||||
// A list of available collations mapped to the internal ID.
|
||||
// To update this map use the following MySQL query:
|
||||
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
|
||||
var collations = map[string]byte{
|
||||
"big5_chinese_ci": 1,
|
||||
"latin2_czech_cs": 2,
|
||||
"dec8_swedish_ci": 3,
|
||||
"cp850_general_ci": 4,
|
||||
"latin1_german1_ci": 5,
|
||||
"hp8_english_ci": 6,
|
||||
"koi8r_general_ci": 7,
|
||||
"latin1_swedish_ci": 8,
|
||||
"latin2_general_ci": 9,
|
||||
"swe7_swedish_ci": 10,
|
||||
"ascii_general_ci": 11,
|
||||
"ujis_japanese_ci": 12,
|
||||
"sjis_japanese_ci": 13,
|
||||
"cp1251_bulgarian_ci": 14,
|
||||
"latin1_danish_ci": 15,
|
||||
"hebrew_general_ci": 16,
|
||||
"tis620_thai_ci": 18,
|
||||
"euckr_korean_ci": 19,
|
||||
"latin7_estonian_cs": 20,
|
||||
"latin2_hungarian_ci": 21,
|
||||
"koi8u_general_ci": 22,
|
||||
"cp1251_ukrainian_ci": 23,
|
||||
"gb2312_chinese_ci": 24,
|
||||
"greek_general_ci": 25,
|
||||
"cp1250_general_ci": 26,
|
||||
"latin2_croatian_ci": 27,
|
||||
"gbk_chinese_ci": 28,
|
||||
"cp1257_lithuanian_ci": 29,
|
||||
"latin5_turkish_ci": 30,
|
||||
"latin1_german2_ci": 31,
|
||||
"armscii8_general_ci": 32,
|
||||
"utf8_general_ci": 33,
|
||||
"cp1250_czech_cs": 34,
|
||||
"ucs2_general_ci": 35,
|
||||
"cp866_general_ci": 36,
|
||||
"keybcs2_general_ci": 37,
|
||||
"macce_general_ci": 38,
|
||||
"macroman_general_ci": 39,
|
||||
"cp852_general_ci": 40,
|
||||
"latin7_general_ci": 41,
|
||||
"latin7_general_cs": 42,
|
||||
"macce_bin": 43,
|
||||
"cp1250_croatian_ci": 44,
|
||||
"utf8mb4_general_ci": 45,
|
||||
"utf8mb4_bin": 46,
|
||||
"latin1_bin": 47,
|
||||
"latin1_general_ci": 48,
|
||||
"latin1_general_cs": 49,
|
||||
"cp1251_bin": 50,
|
||||
"cp1251_general_ci": 51,
|
||||
"cp1251_general_cs": 52,
|
||||
"macroman_bin": 53,
|
||||
"utf16_general_ci": 54,
|
||||
"utf16_bin": 55,
|
||||
"utf16le_general_ci": 56,
|
||||
"cp1256_general_ci": 57,
|
||||
"cp1257_bin": 58,
|
||||
"cp1257_general_ci": 59,
|
||||
"utf32_general_ci": 60,
|
||||
"utf32_bin": 61,
|
||||
"utf16le_bin": 62,
|
||||
"binary": 63,
|
||||
"armscii8_bin": 64,
|
||||
"ascii_bin": 65,
|
||||
"cp1250_bin": 66,
|
||||
"cp1256_bin": 67,
|
||||
"cp866_bin": 68,
|
||||
"dec8_bin": 69,
|
||||
"greek_bin": 70,
|
||||
"hebrew_bin": 71,
|
||||
"hp8_bin": 72,
|
||||
"keybcs2_bin": 73,
|
||||
"koi8r_bin": 74,
|
||||
"koi8u_bin": 75,
|
||||
"latin2_bin": 77,
|
||||
"latin5_bin": 78,
|
||||
"latin7_bin": 79,
|
||||
"cp850_bin": 80,
|
||||
"cp852_bin": 81,
|
||||
"swe7_bin": 82,
|
||||
"utf8_bin": 83,
|
||||
"big5_bin": 84,
|
||||
"euckr_bin": 85,
|
||||
"gb2312_bin": 86,
|
||||
"gbk_bin": 87,
|
||||
"sjis_bin": 88,
|
||||
"tis620_bin": 89,
|
||||
"ucs2_bin": 90,
|
||||
"ujis_bin": 91,
|
||||
"geostd8_general_ci": 92,
|
||||
"geostd8_bin": 93,
|
||||
"latin1_spanish_ci": 94,
|
||||
"cp932_japanese_ci": 95,
|
||||
"cp932_bin": 96,
|
||||
"eucjpms_japanese_ci": 97,
|
||||
"eucjpms_bin": 98,
|
||||
"cp1250_polish_ci": 99,
|
||||
"utf16_unicode_ci": 101,
|
||||
"utf16_icelandic_ci": 102,
|
||||
"utf16_latvian_ci": 103,
|
||||
"utf16_romanian_ci": 104,
|
||||
"utf16_slovenian_ci": 105,
|
||||
"utf16_polish_ci": 106,
|
||||
"utf16_estonian_ci": 107,
|
||||
"utf16_spanish_ci": 108,
|
||||
"utf16_swedish_ci": 109,
|
||||
"utf16_turkish_ci": 110,
|
||||
"utf16_czech_ci": 111,
|
||||
"utf16_danish_ci": 112,
|
||||
"utf16_lithuanian_ci": 113,
|
||||
"utf16_slovak_ci": 114,
|
||||
"utf16_spanish2_ci": 115,
|
||||
"utf16_roman_ci": 116,
|
||||
"utf16_persian_ci": 117,
|
||||
"utf16_esperanto_ci": 118,
|
||||
"utf16_hungarian_ci": 119,
|
||||
"utf16_sinhala_ci": 120,
|
||||
"utf16_german2_ci": 121,
|
||||
"utf16_croatian_ci": 122,
|
||||
"utf16_unicode_520_ci": 123,
|
||||
"utf16_vietnamese_ci": 124,
|
||||
"ucs2_unicode_ci": 128,
|
||||
"ucs2_icelandic_ci": 129,
|
||||
"ucs2_latvian_ci": 130,
|
||||
"ucs2_romanian_ci": 131,
|
||||
"ucs2_slovenian_ci": 132,
|
||||
"ucs2_polish_ci": 133,
|
||||
"ucs2_estonian_ci": 134,
|
||||
"ucs2_spanish_ci": 135,
|
||||
"ucs2_swedish_ci": 136,
|
||||
"ucs2_turkish_ci": 137,
|
||||
"ucs2_czech_ci": 138,
|
||||
"ucs2_danish_ci": 139,
|
||||
"ucs2_lithuanian_ci": 140,
|
||||
"ucs2_slovak_ci": 141,
|
||||
"ucs2_spanish2_ci": 142,
|
||||
"ucs2_roman_ci": 143,
|
||||
"ucs2_persian_ci": 144,
|
||||
"ucs2_esperanto_ci": 145,
|
||||
"ucs2_hungarian_ci": 146,
|
||||
"ucs2_sinhala_ci": 147,
|
||||
"ucs2_german2_ci": 148,
|
||||
"ucs2_croatian_ci": 149,
|
||||
"ucs2_unicode_520_ci": 150,
|
||||
"ucs2_vietnamese_ci": 151,
|
||||
"ucs2_general_mysql500_ci": 159,
|
||||
"utf32_unicode_ci": 160,
|
||||
"utf32_icelandic_ci": 161,
|
||||
"utf32_latvian_ci": 162,
|
||||
"utf32_romanian_ci": 163,
|
||||
"utf32_slovenian_ci": 164,
|
||||
"utf32_polish_ci": 165,
|
||||
"utf32_estonian_ci": 166,
|
||||
"utf32_spanish_ci": 167,
|
||||
"utf32_swedish_ci": 168,
|
||||
"utf32_turkish_ci": 169,
|
||||
"utf32_czech_ci": 170,
|
||||
"utf32_danish_ci": 171,
|
||||
"utf32_lithuanian_ci": 172,
|
||||
"utf32_slovak_ci": 173,
|
||||
"utf32_spanish2_ci": 174,
|
||||
"utf32_roman_ci": 175,
|
||||
"utf32_persian_ci": 176,
|
||||
"utf32_esperanto_ci": 177,
|
||||
"utf32_hungarian_ci": 178,
|
||||
"utf32_sinhala_ci": 179,
|
||||
"utf32_german2_ci": 180,
|
||||
"utf32_croatian_ci": 181,
|
||||
"utf32_unicode_520_ci": 182,
|
||||
"utf32_vietnamese_ci": 183,
|
||||
"utf8_unicode_ci": 192,
|
||||
"utf8_icelandic_ci": 193,
|
||||
"utf8_latvian_ci": 194,
|
||||
"utf8_romanian_ci": 195,
|
||||
"utf8_slovenian_ci": 196,
|
||||
"utf8_polish_ci": 197,
|
||||
"utf8_estonian_ci": 198,
|
||||
"utf8_spanish_ci": 199,
|
||||
"utf8_swedish_ci": 200,
|
||||
"utf8_turkish_ci": 201,
|
||||
"utf8_czech_ci": 202,
|
||||
"utf8_danish_ci": 203,
|
||||
"utf8_lithuanian_ci": 204,
|
||||
"utf8_slovak_ci": 205,
|
||||
"utf8_spanish2_ci": 206,
|
||||
"utf8_roman_ci": 207,
|
||||
"utf8_persian_ci": 208,
|
||||
"utf8_esperanto_ci": 209,
|
||||
"utf8_hungarian_ci": 210,
|
||||
"utf8_sinhala_ci": 211,
|
||||
"utf8_german2_ci": 212,
|
||||
"utf8_croatian_ci": 213,
|
||||
"utf8_unicode_520_ci": 214,
|
||||
"utf8_vietnamese_ci": 215,
|
||||
"utf8_general_mysql500_ci": 223,
|
||||
"utf8mb4_unicode_ci": 224,
|
||||
"utf8mb4_icelandic_ci": 225,
|
||||
"utf8mb4_latvian_ci": 226,
|
||||
"utf8mb4_romanian_ci": 227,
|
||||
"utf8mb4_slovenian_ci": 228,
|
||||
"utf8mb4_polish_ci": 229,
|
||||
"utf8mb4_estonian_ci": 230,
|
||||
"utf8mb4_spanish_ci": 231,
|
||||
"utf8mb4_swedish_ci": 232,
|
||||
"utf8mb4_turkish_ci": 233,
|
||||
"utf8mb4_czech_ci": 234,
|
||||
"utf8mb4_danish_ci": 235,
|
||||
"utf8mb4_lithuanian_ci": 236,
|
||||
"utf8mb4_slovak_ci": 237,
|
||||
"utf8mb4_spanish2_ci": 238,
|
||||
"utf8mb4_roman_ci": 239,
|
||||
"utf8mb4_persian_ci": 240,
|
||||
"utf8mb4_esperanto_ci": 241,
|
||||
"utf8mb4_hungarian_ci": 242,
|
||||
"utf8mb4_sinhala_ci": 243,
|
||||
"utf8mb4_german2_ci": 244,
|
||||
"utf8mb4_croatian_ci": 245,
|
||||
"utf8mb4_unicode_520_ci": 246,
|
||||
"utf8mb4_vietnamese_ci": 247,
|
||||
}
|
||||
|
||||
// A blacklist of collations which is unsafe to interpolate parameters.
|
||||
// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
|
||||
var unsafeCollations = map[byte]bool{
|
||||
1: true, // big5_chinese_ci
|
||||
13: true, // sjis_japanese_ci
|
||||
28: true, // gbk_chinese_ci
|
||||
84: true, // big5_bin
|
||||
86: true, // gb2312_bin
|
||||
87: true, // gbk_bin
|
||||
88: true, // sjis_bin
|
||||
95: true, // cp932_japanese_ci
|
||||
96: true, // cp932_bin
|
||||
}
|
412
vendor/github.com/go-sql-driver/mysql/connection.go
generated
vendored
Normal file
412
vendor/github.com/go-sql-driver/mysql/connection.go
generated
vendored
Normal file
@ -0,0 +1,412 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type mysqlConn struct {
|
||||
buf buffer
|
||||
netConn net.Conn
|
||||
affectedRows uint64
|
||||
insertId uint64
|
||||
cfg *config
|
||||
maxPacketAllowed int
|
||||
maxWriteSize int
|
||||
flags clientFlag
|
||||
status statusFlag
|
||||
sequence uint8
|
||||
parseTime bool
|
||||
strict bool
|
||||
}
|
||||
|
||||
type config struct {
|
||||
user string
|
||||
passwd string
|
||||
net string
|
||||
addr string
|
||||
dbname string
|
||||
params map[string]string
|
||||
loc *time.Location
|
||||
tls *tls.Config
|
||||
timeout time.Duration
|
||||
collation uint8
|
||||
allowAllFiles bool
|
||||
allowOldPasswords bool
|
||||
allowCleartextPasswords bool
|
||||
clientFoundRows bool
|
||||
columnsWithAlias bool
|
||||
interpolateParams bool
|
||||
}
|
||||
|
||||
// Handles parameters set in DSN after the connection is established
|
||||
func (mc *mysqlConn) handleParams() (err error) {
|
||||
for param, val := range mc.cfg.params {
|
||||
switch param {
|
||||
// Charset
|
||||
case "charset":
|
||||
charsets := strings.Split(val, ",")
|
||||
for i := range charsets {
|
||||
// ignore errors here - a charset may not exist
|
||||
err = mc.exec("SET NAMES " + charsets[i])
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// time.Time parsing
|
||||
case "parseTime":
|
||||
var isBool bool
|
||||
mc.parseTime, isBool = readBool(val)
|
||||
if !isBool {
|
||||
return errors.New("Invalid Bool value: " + val)
|
||||
}
|
||||
|
||||
// Strict mode
|
||||
case "strict":
|
||||
var isBool bool
|
||||
mc.strict, isBool = readBool(val)
|
||||
if !isBool {
|
||||
return errors.New("Invalid Bool value: " + val)
|
||||
}
|
||||
|
||||
// Compression
|
||||
case "compress":
|
||||
err = errors.New("Compression not implemented yet")
|
||||
return
|
||||
|
||||
// System Vars
|
||||
default:
|
||||
err = mc.exec("SET " + param + "=" + val + "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Begin() (driver.Tx, error) {
|
||||
if mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
err := mc.exec("START TRANSACTION")
|
||||
if err == nil {
|
||||
return &mysqlTx{mc}, err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Close() (err error) {
|
||||
// Makes Close idempotent
|
||||
if mc.netConn != nil {
|
||||
err = mc.writeCommandPacket(comQuit)
|
||||
}
|
||||
|
||||
mc.cleanup()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Closes the network connection and unsets internal variables. Do not call this
|
||||
// function after successfully authentication, call Close instead. This function
|
||||
// is called before auth or on auth failure because MySQL will have already
|
||||
// closed the network connection.
|
||||
func (mc *mysqlConn) cleanup() {
|
||||
// Makes cleanup idempotent
|
||||
if mc.netConn != nil {
|
||||
if err := mc.netConn.Close(); err != nil {
|
||||
errLog.Print(err)
|
||||
}
|
||||
mc.netConn = nil
|
||||
}
|
||||
mc.cfg = nil
|
||||
mc.buf.rd = nil
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
|
||||
if mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comStmtPrepare, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stmt := &mysqlStmt{
|
||||
mc: mc,
|
||||
}
|
||||
|
||||
// Read Result
|
||||
columnCount, err := stmt.readPrepareResultPacket()
|
||||
if err == nil {
|
||||
if stmt.paramCount > 0 {
|
||||
if err = mc.readUntilEOF(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if columnCount > 0 {
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
}
|
||||
|
||||
return stmt, err
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
|
||||
buf := mc.buf.takeCompleteBuffer()
|
||||
if buf == nil {
|
||||
// can not take the buffer. Something must be wrong with the connection
|
||||
errLog.Print(ErrBusyBuffer)
|
||||
return "", driver.ErrBadConn
|
||||
}
|
||||
buf = buf[:0]
|
||||
argPos := 0
|
||||
|
||||
for i := 0; i < len(query); i++ {
|
||||
q := strings.IndexByte(query[i:], '?')
|
||||
if q == -1 {
|
||||
buf = append(buf, query[i:]...)
|
||||
break
|
||||
}
|
||||
buf = append(buf, query[i:i+q]...)
|
||||
i += q
|
||||
|
||||
arg := args[argPos]
|
||||
argPos++
|
||||
|
||||
if arg == nil {
|
||||
buf = append(buf, "NULL"...)
|
||||
continue
|
||||
}
|
||||
|
||||
switch v := arg.(type) {
|
||||
case int64:
|
||||
buf = strconv.AppendInt(buf, v, 10)
|
||||
case float64:
|
||||
buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
|
||||
case bool:
|
||||
if v {
|
||||
buf = append(buf, '1')
|
||||
} else {
|
||||
buf = append(buf, '0')
|
||||
}
|
||||
case time.Time:
|
||||
if v.IsZero() {
|
||||
buf = append(buf, "'0000-00-00'"...)
|
||||
} else {
|
||||
v := v.In(mc.cfg.loc)
|
||||
v = v.Add(time.Nanosecond * 500) // To round under microsecond
|
||||
year := v.Year()
|
||||
year100 := year / 100
|
||||
year1 := year % 100
|
||||
month := v.Month()
|
||||
day := v.Day()
|
||||
hour := v.Hour()
|
||||
minute := v.Minute()
|
||||
second := v.Second()
|
||||
micro := v.Nanosecond() / 1000
|
||||
|
||||
buf = append(buf, []byte{
|
||||
'\'',
|
||||
digits10[year100], digits01[year100],
|
||||
digits10[year1], digits01[year1],
|
||||
'-',
|
||||
digits10[month], digits01[month],
|
||||
'-',
|
||||
digits10[day], digits01[day],
|
||||
' ',
|
||||
digits10[hour], digits01[hour],
|
||||
':',
|
||||
digits10[minute], digits01[minute],
|
||||
':',
|
||||
digits10[second], digits01[second],
|
||||
}...)
|
||||
|
||||
if micro != 0 {
|
||||
micro10000 := micro / 10000
|
||||
micro100 := micro / 100 % 100
|
||||
micro1 := micro % 100
|
||||
buf = append(buf, []byte{
|
||||
'.',
|
||||
digits10[micro10000], digits01[micro10000],
|
||||
digits10[micro100], digits01[micro100],
|
||||
digits10[micro1], digits01[micro1],
|
||||
}...)
|
||||
}
|
||||
buf = append(buf, '\'')
|
||||
}
|
||||
case []byte:
|
||||
if v == nil {
|
||||
buf = append(buf, "NULL"...)
|
||||
} else {
|
||||
buf = append(buf, "_binary'"...)
|
||||
if mc.status&statusNoBackslashEscapes == 0 {
|
||||
buf = escapeBytesBackslash(buf, v)
|
||||
} else {
|
||||
buf = escapeBytesQuotes(buf, v)
|
||||
}
|
||||
buf = append(buf, '\'')
|
||||
}
|
||||
case string:
|
||||
buf = append(buf, '\'')
|
||||
if mc.status&statusNoBackslashEscapes == 0 {
|
||||
buf = escapeStringBackslash(buf, v)
|
||||
} else {
|
||||
buf = escapeStringQuotes(buf, v)
|
||||
}
|
||||
buf = append(buf, '\'')
|
||||
default:
|
||||
return "", driver.ErrSkip
|
||||
}
|
||||
|
||||
if len(buf)+4 > mc.maxPacketAllowed {
|
||||
return "", driver.ErrSkip
|
||||
}
|
||||
}
|
||||
if argPos != len(args) {
|
||||
return "", driver.ErrSkip
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
||||
if mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
if len(args) != 0 {
|
||||
if !mc.cfg.interpolateParams {
|
||||
return nil, driver.ErrSkip
|
||||
}
|
||||
// try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
|
||||
prepared, err := mc.interpolateParams(query, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query = prepared
|
||||
args = nil
|
||||
}
|
||||
mc.affectedRows = 0
|
||||
mc.insertId = 0
|
||||
|
||||
err := mc.exec(query)
|
||||
if err == nil {
|
||||
return &mysqlResult{
|
||||
affectedRows: int64(mc.affectedRows),
|
||||
insertId: int64(mc.insertId),
|
||||
}, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Internal function to execute commands
|
||||
func (mc *mysqlConn) exec(query string) error {
|
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comQuery, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err == nil && resLen > 0 {
|
||||
if err = mc.readUntilEOF(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
||||
if mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
if len(args) != 0 {
|
||||
if !mc.cfg.interpolateParams {
|
||||
return nil, driver.ErrSkip
|
||||
}
|
||||
// try client-side prepare to reduce roundtrip
|
||||
prepared, err := mc.interpolateParams(query, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query = prepared
|
||||
args = nil
|
||||
}
|
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comQuery, query)
|
||||
if err == nil {
|
||||
// Read Result
|
||||
var resLen int
|
||||
resLen, err = mc.readResultSetHeaderPacket()
|
||||
if err == nil {
|
||||
rows := new(textRows)
|
||||
rows.mc = mc
|
||||
|
||||
if resLen == 0 {
|
||||
// no columns, no more data
|
||||
return emptyRows{}, nil
|
||||
}
|
||||
// Columns
|
||||
rows.columns, err = mc.readColumns(resLen)
|
||||
return rows, err
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Gets the value of the given MySQL System Variable
|
||||
// The returned byte slice is only valid until the next read
|
||||
func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
|
||||
// Send command
|
||||
if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err == nil {
|
||||
rows := new(textRows)
|
||||
rows.mc = mc
|
||||
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
if err := mc.readUntilEOF(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
dest := make([]driver.Value, resLen)
|
||||
if err = rows.readRow(dest); err == nil {
|
||||
return dest[0].([]byte), mc.readUntilEOF()
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
162
vendor/github.com/go-sql-driver/mysql/const.go
generated
vendored
Normal file
162
vendor/github.com/go-sql-driver/mysql/const.go
generated
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
const (
|
||||
minProtocolVersion byte = 10
|
||||
maxPacketSize = 1<<24 - 1
|
||||
timeFormat = "2006-01-02 15:04:05.999999"
|
||||
)
|
||||
|
||||
// MySQL constants documentation:
|
||||
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
|
||||
|
||||
const (
|
||||
iOK byte = 0x00
|
||||
iLocalInFile byte = 0xfb
|
||||
iEOF byte = 0xfe
|
||||
iERR byte = 0xff
|
||||
)
|
||||
|
||||
// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
|
||||
type clientFlag uint32
|
||||
|
||||
const (
|
||||
clientLongPassword clientFlag = 1 << iota
|
||||
clientFoundRows
|
||||
clientLongFlag
|
||||
clientConnectWithDB
|
||||
clientNoSchema
|
||||
clientCompress
|
||||
clientODBC
|
||||
clientLocalFiles
|
||||
clientIgnoreSpace
|
||||
clientProtocol41
|
||||
clientInteractive
|
||||
clientSSL
|
||||
clientIgnoreSIGPIPE
|
||||
clientTransactions
|
||||
clientReserved
|
||||
clientSecureConn
|
||||
clientMultiStatements
|
||||
clientMultiResults
|
||||
clientPSMultiResults
|
||||
clientPluginAuth
|
||||
clientConnectAttrs
|
||||
clientPluginAuthLenEncClientData
|
||||
clientCanHandleExpiredPasswords
|
||||
clientSessionTrack
|
||||
clientDeprecateEOF
|
||||
)
|
||||
|
||||
const (
|
||||
comQuit byte = iota + 1
|
||||
comInitDB
|
||||
comQuery
|
||||
comFieldList
|
||||
comCreateDB
|
||||
comDropDB
|
||||
comRefresh
|
||||
comShutdown
|
||||
comStatistics
|
||||
comProcessInfo
|
||||
comConnect
|
||||
comProcessKill
|
||||
comDebug
|
||||
comPing
|
||||
comTime
|
||||
comDelayedInsert
|
||||
comChangeUser
|
||||
comBinlogDump
|
||||
comTableDump
|
||||
comConnectOut
|
||||
comRegisterSlave
|
||||
comStmtPrepare
|
||||
comStmtExecute
|
||||
comStmtSendLongData
|
||||
comStmtClose
|
||||
comStmtReset
|
||||
comSetOption
|
||||
comStmtFetch
|
||||
)
|
||||
|
||||
// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
|
||||
const (
|
||||
fieldTypeDecimal byte = iota
|
||||
fieldTypeTiny
|
||||
fieldTypeShort
|
||||
fieldTypeLong
|
||||
fieldTypeFloat
|
||||
fieldTypeDouble
|
||||
fieldTypeNULL
|
||||
fieldTypeTimestamp
|
||||
fieldTypeLongLong
|
||||
fieldTypeInt24
|
||||
fieldTypeDate
|
||||
fieldTypeTime
|
||||
fieldTypeDateTime
|
||||
fieldTypeYear
|
||||
fieldTypeNewDate
|
||||
fieldTypeVarChar
|
||||
fieldTypeBit
|
||||
)
|
||||
const (
|
||||
fieldTypeNewDecimal byte = iota + 0xf6
|
||||
fieldTypeEnum
|
||||
fieldTypeSet
|
||||
fieldTypeTinyBLOB
|
||||
fieldTypeMediumBLOB
|
||||
fieldTypeLongBLOB
|
||||
fieldTypeBLOB
|
||||
fieldTypeVarString
|
||||
fieldTypeString
|
||||
fieldTypeGeometry
|
||||
)
|
||||
|
||||
type fieldFlag uint16
|
||||
|
||||
const (
|
||||
flagNotNULL fieldFlag = 1 << iota
|
||||
flagPriKey
|
||||
flagUniqueKey
|
||||
flagMultipleKey
|
||||
flagBLOB
|
||||
flagUnsigned
|
||||
flagZeroFill
|
||||
flagBinary
|
||||
flagEnum
|
||||
flagAutoIncrement
|
||||
flagTimestamp
|
||||
flagSet
|
||||
flagUnknown1
|
||||
flagUnknown2
|
||||
flagUnknown3
|
||||
flagUnknown4
|
||||
)
|
||||
|
||||
// http://dev.mysql.com/doc/internals/en/status-flags.html
|
||||
type statusFlag uint16
|
||||
|
||||
const (
|
||||
statusInTrans statusFlag = 1 << iota
|
||||
statusInAutocommit
|
||||
statusReserved // Not in documentation
|
||||
statusMoreResultsExists
|
||||
statusNoGoodIndexUsed
|
||||
statusNoIndexUsed
|
||||
statusCursorExists
|
||||
statusLastRowSent
|
||||
statusDbDropped
|
||||
statusNoBackslashEscapes
|
||||
statusMetadataChanged
|
||||
statusQueryWasSlow
|
||||
statusPsOutParams
|
||||
statusInTransReadonly
|
||||
statusSessionStateChanged
|
||||
)
|
161
vendor/github.com/go-sql-driver/mysql/driver.go
generated
vendored
Normal file
161
vendor/github.com/go-sql-driver/mysql/driver.go
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// The driver should be used via the database/sql package:
|
||||
//
|
||||
// import "database/sql"
|
||||
// import _ "github.com/go-sql-driver/mysql"
|
||||
//
|
||||
// db, err := sql.Open("mysql", "user:password@/dbname")
|
||||
//
|
||||
// See https://github.com/go-sql-driver/mysql#usage for details
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"net"
|
||||
)
|
||||
|
||||
// This struct is exported to make the driver directly accessible.
|
||||
// In general the driver is used via the database/sql package.
|
||||
type MySQLDriver struct{}
|
||||
|
||||
// DialFunc is a function which can be used to establish the network connection.
|
||||
// Custom dial functions must be registered with RegisterDial
|
||||
type DialFunc func(addr string) (net.Conn, error)
|
||||
|
||||
var dials map[string]DialFunc
|
||||
|
||||
// RegisterDial registers a custom dial function. It can then be used by the
|
||||
// network address mynet(addr), where mynet is the registered new network.
|
||||
// addr is passed as a parameter to the dial function.
|
||||
func RegisterDial(net string, dial DialFunc) {
|
||||
if dials == nil {
|
||||
dials = make(map[string]DialFunc)
|
||||
}
|
||||
dials[net] = dial
|
||||
}
|
||||
|
||||
// Open new Connection.
|
||||
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
|
||||
// the DSN string is formated
|
||||
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
|
||||
var err error
|
||||
|
||||
// New mysqlConn
|
||||
mc := &mysqlConn{
|
||||
maxPacketAllowed: maxPacketSize,
|
||||
maxWriteSize: maxPacketSize - 1,
|
||||
}
|
||||
mc.cfg, err = parseDSN(dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Connect to Server
|
||||
if dial, ok := dials[mc.cfg.net]; ok {
|
||||
mc.netConn, err = dial(mc.cfg.addr)
|
||||
} else {
|
||||
nd := net.Dialer{Timeout: mc.cfg.timeout}
|
||||
mc.netConn, err = nd.Dial(mc.cfg.net, mc.cfg.addr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Enable TCP Keepalives on TCP connections
|
||||
if tc, ok := mc.netConn.(*net.TCPConn); ok {
|
||||
if err := tc.SetKeepAlive(true); err != nil {
|
||||
// Don't send COM_QUIT before handshake.
|
||||
mc.netConn.Close()
|
||||
mc.netConn = nil
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
mc.buf = newBuffer(mc.netConn)
|
||||
|
||||
// Reading Handshake Initialization Packet
|
||||
cipher, err := mc.readInitPacket()
|
||||
if err != nil {
|
||||
mc.cleanup()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send Client Authentication Packet
|
||||
if err = mc.writeAuthPacket(cipher); err != nil {
|
||||
mc.cleanup()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handle response to auth packet, switch methods if possible
|
||||
if err = handleAuthResult(mc, cipher); err != nil {
|
||||
// Authentication failed and MySQL has already closed the connection
|
||||
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
|
||||
// Do not send COM_QUIT, just cleanup and return the error.
|
||||
mc.cleanup()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get max allowed packet size
|
||||
maxap, err := mc.getSystemVar("max_allowed_packet")
|
||||
if err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
mc.maxPacketAllowed = stringToInt(maxap) - 1
|
||||
if mc.maxPacketAllowed < maxPacketSize {
|
||||
mc.maxWriteSize = mc.maxPacketAllowed
|
||||
}
|
||||
|
||||
// Handle DSN Params
|
||||
err = mc.handleParams()
|
||||
if err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mc, nil
|
||||
}
|
||||
|
||||
func handleAuthResult(mc *mysqlConn, cipher []byte) error {
|
||||
// Read Result Packet
|
||||
err := mc.readResultOK()
|
||||
if err == nil {
|
||||
return nil // auth successful
|
||||
}
|
||||
|
||||
if mc.cfg == nil {
|
||||
return err // auth failed and retry not possible
|
||||
}
|
||||
|
||||
// Retry auth if configured to do so.
|
||||
if mc.cfg.allowOldPasswords && err == ErrOldPassword {
|
||||
// Retry with old authentication method. Note: there are edge cases
|
||||
// where this should work but doesn't; this is currently "wontfix":
|
||||
// https://github.com/go-sql-driver/mysql/issues/184
|
||||
if err = mc.writeOldAuthPacket(cipher); err != nil {
|
||||
return err
|
||||
}
|
||||
err = mc.readResultOK()
|
||||
} else if mc.cfg.allowCleartextPasswords && err == ErrCleartextPassword {
|
||||
// Retry with clear text password for
|
||||
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
|
||||
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
|
||||
if err = mc.writeClearAuthPacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
err = mc.readResultOK()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
sql.Register("mysql", &MySQLDriver{})
|
||||
}
|
131
vendor/github.com/go-sql-driver/mysql/errors.go
generated
vendored
Normal file
131
vendor/github.com/go-sql-driver/mysql/errors.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Various errors the driver might return. Can change between driver versions.
|
||||
var (
|
||||
ErrInvalidConn = errors.New("Invalid Connection")
|
||||
ErrMalformPkt = errors.New("Malformed Packet")
|
||||
ErrNoTLS = errors.New("TLS encryption requested but server does not support TLS")
|
||||
ErrOldPassword = errors.New("This user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
|
||||
ErrCleartextPassword = errors.New("This user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN.")
|
||||
ErrUnknownPlugin = errors.New("The authentication plugin is not supported.")
|
||||
ErrOldProtocol = errors.New("MySQL-Server does not support required Protocol 41+")
|
||||
ErrPktSync = errors.New("Commands out of sync. You can't run this command now")
|
||||
ErrPktSyncMul = errors.New("Commands out of sync. Did you run multiple statements at once?")
|
||||
ErrPktTooLarge = errors.New("Packet for query is too large. You can change this value on the server by adjusting the 'max_allowed_packet' variable.")
|
||||
ErrBusyBuffer = errors.New("Busy buffer")
|
||||
)
|
||||
|
||||
var errLog Logger = log.New(os.Stderr, "[MySQL] ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||
|
||||
// Logger is used to log critical error messages.
|
||||
type Logger interface {
|
||||
Print(v ...interface{})
|
||||
}
|
||||
|
||||
// SetLogger is used to set the logger for critical errors.
|
||||
// The initial logger is os.Stderr.
|
||||
func SetLogger(logger Logger) error {
|
||||
if logger == nil {
|
||||
return errors.New("logger is nil")
|
||||
}
|
||||
errLog = logger
|
||||
return nil
|
||||
}
|
||||
|
||||
// MySQLError is an error type which represents a single MySQL error
|
||||
type MySQLError struct {
|
||||
Number uint16
|
||||
Message string
|
||||
}
|
||||
|
||||
func (me *MySQLError) Error() string {
|
||||
return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
|
||||
}
|
||||
|
||||
// MySQLWarnings is an error type which represents a group of one or more MySQL
|
||||
// warnings
|
||||
type MySQLWarnings []MySQLWarning
|
||||
|
||||
func (mws MySQLWarnings) Error() string {
|
||||
var msg string
|
||||
for i, warning := range mws {
|
||||
if i > 0 {
|
||||
msg += "\r\n"
|
||||
}
|
||||
msg += fmt.Sprintf(
|
||||
"%s %s: %s",
|
||||
warning.Level,
|
||||
warning.Code,
|
||||
warning.Message,
|
||||
)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// MySQLWarning is an error type which represents a single MySQL warning.
|
||||
// Warnings are returned in groups only. See MySQLWarnings
|
||||
type MySQLWarning struct {
|
||||
Level string
|
||||
Code string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) getWarnings() (err error) {
|
||||
rows, err := mc.Query("SHOW WARNINGS", nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var warnings = MySQLWarnings{}
|
||||
var values = make([]driver.Value, 3)
|
||||
|
||||
for {
|
||||
err = rows.Next(values)
|
||||
switch err {
|
||||
case nil:
|
||||
warning := MySQLWarning{}
|
||||
|
||||
if raw, ok := values[0].([]byte); ok {
|
||||
warning.Level = string(raw)
|
||||
} else {
|
||||
warning.Level = fmt.Sprintf("%s", values[0])
|
||||
}
|
||||
if raw, ok := values[1].([]byte); ok {
|
||||
warning.Code = string(raw)
|
||||
} else {
|
||||
warning.Code = fmt.Sprintf("%s", values[1])
|
||||
}
|
||||
if raw, ok := values[2].([]byte); ok {
|
||||
warning.Message = string(raw)
|
||||
} else {
|
||||
warning.Message = fmt.Sprintf("%s", values[0])
|
||||
}
|
||||
|
||||
warnings = append(warnings, warning)
|
||||
|
||||
case io.EOF:
|
||||
return warnings
|
||||
|
||||
default:
|
||||
rows.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
182
vendor/github.com/go-sql-driver/mysql/infile.go
generated
vendored
Normal file
182
vendor/github.com/go-sql-driver/mysql/infile.go
generated
vendored
Normal file
@ -0,0 +1,182 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
fileRegister map[string]bool
|
||||
fileRegisterLock sync.RWMutex
|
||||
readerRegister map[string]func() io.Reader
|
||||
readerRegisterLock sync.RWMutex
|
||||
)
|
||||
|
||||
// RegisterLocalFile adds the given file to the file whitelist,
|
||||
// so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
|
||||
// Alternatively you can allow the use of all local files with
|
||||
// the DSN parameter 'allowAllFiles=true'
|
||||
//
|
||||
// filePath := "/home/gopher/data.csv"
|
||||
// mysql.RegisterLocalFile(filePath)
|
||||
// err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
|
||||
// if err != nil {
|
||||
// ...
|
||||
//
|
||||
func RegisterLocalFile(filePath string) {
|
||||
fileRegisterLock.Lock()
|
||||
// lazy map init
|
||||
if fileRegister == nil {
|
||||
fileRegister = make(map[string]bool)
|
||||
}
|
||||
|
||||
fileRegister[strings.Trim(filePath, `"`)] = true
|
||||
fileRegisterLock.Unlock()
|
||||
}
|
||||
|
||||
// DeregisterLocalFile removes the given filepath from the whitelist.
|
||||
func DeregisterLocalFile(filePath string) {
|
||||
fileRegisterLock.Lock()
|
||||
delete(fileRegister, strings.Trim(filePath, `"`))
|
||||
fileRegisterLock.Unlock()
|
||||
}
|
||||
|
||||
// RegisterReaderHandler registers a handler function which is used
|
||||
// to receive a io.Reader.
|
||||
// The Reader can be used by "LOAD DATA LOCAL INFILE Reader::<name>".
|
||||
// If the handler returns a io.ReadCloser Close() is called when the
|
||||
// request is finished.
|
||||
//
|
||||
// mysql.RegisterReaderHandler("data", func() io.Reader {
|
||||
// var csvReader io.Reader // Some Reader that returns CSV data
|
||||
// ... // Open Reader here
|
||||
// return csvReader
|
||||
// })
|
||||
// err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
|
||||
// if err != nil {
|
||||
// ...
|
||||
//
|
||||
func RegisterReaderHandler(name string, handler func() io.Reader) {
|
||||
readerRegisterLock.Lock()
|
||||
// lazy map init
|
||||
if readerRegister == nil {
|
||||
readerRegister = make(map[string]func() io.Reader)
|
||||
}
|
||||
|
||||
readerRegister[name] = handler
|
||||
readerRegisterLock.Unlock()
|
||||
}
|
||||
|
||||
// DeregisterReaderHandler removes the ReaderHandler function with
|
||||
// the given name from the registry.
|
||||
func DeregisterReaderHandler(name string) {
|
||||
readerRegisterLock.Lock()
|
||||
delete(readerRegister, name)
|
||||
readerRegisterLock.Unlock()
|
||||
}
|
||||
|
||||
func deferredClose(err *error, closer io.Closer) {
|
||||
closeErr := closer.Close()
|
||||
if *err == nil {
|
||||
*err = closeErr
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
|
||||
var rdr io.Reader
|
||||
var data []byte
|
||||
|
||||
if idx := strings.Index(name, "Reader::"); idx == 0 || (idx > 0 && name[idx-1] == '/') { // io.Reader
|
||||
// The server might return an an absolute path. See issue #355.
|
||||
name = name[idx+8:]
|
||||
|
||||
readerRegisterLock.RLock()
|
||||
handler, inMap := readerRegister[name]
|
||||
readerRegisterLock.RUnlock()
|
||||
|
||||
if inMap {
|
||||
rdr = handler()
|
||||
if rdr != nil {
|
||||
data = make([]byte, 4+mc.maxWriteSize)
|
||||
|
||||
if cl, ok := rdr.(io.Closer); ok {
|
||||
defer deferredClose(&err, cl)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Reader '%s' is <nil>", name)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Reader '%s' is not registered", name)
|
||||
}
|
||||
} else { // File
|
||||
name = strings.Trim(name, `"`)
|
||||
fileRegisterLock.RLock()
|
||||
fr := fileRegister[name]
|
||||
fileRegisterLock.RUnlock()
|
||||
if mc.cfg.allowAllFiles || fr {
|
||||
var file *os.File
|
||||
var fi os.FileInfo
|
||||
|
||||
if file, err = os.Open(name); err == nil {
|
||||
defer deferredClose(&err, file)
|
||||
|
||||
// get file size
|
||||
if fi, err = file.Stat(); err == nil {
|
||||
rdr = file
|
||||
if fileSize := int(fi.Size()); fileSize <= mc.maxWriteSize {
|
||||
data = make([]byte, 4+fileSize)
|
||||
} else if fileSize <= mc.maxPacketAllowed {
|
||||
data = make([]byte, 4+mc.maxWriteSize)
|
||||
} else {
|
||||
err = fmt.Errorf("Local File '%s' too large: Size: %d, Max: %d", name, fileSize, mc.maxPacketAllowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Local File '%s' is not registered. Use the DSN parameter 'allowAllFiles=true' to allow all files", name)
|
||||
}
|
||||
}
|
||||
|
||||
// send content packets
|
||||
if err == nil {
|
||||
var n int
|
||||
for err == nil {
|
||||
n, err = rdr.Read(data[4:])
|
||||
if n > 0 {
|
||||
if ioErr := mc.writePacket(data[:4+n]); ioErr != nil {
|
||||
return ioErr
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
// send empty packet (termination)
|
||||
if data == nil {
|
||||
data = make([]byte, 4)
|
||||
}
|
||||
if ioErr := mc.writePacket(data[:4]); ioErr != nil {
|
||||
return ioErr
|
||||
}
|
||||
|
||||
// read OK packet
|
||||
if err == nil {
|
||||
return mc.readResultOK()
|
||||
} else {
|
||||
mc.readPacket()
|
||||
}
|
||||
return err
|
||||
}
|
1182
vendor/github.com/go-sql-driver/mysql/packets.go
generated
vendored
Normal file
1182
vendor/github.com/go-sql-driver/mysql/packets.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
22
vendor/github.com/go-sql-driver/mysql/result.go
generated
vendored
Normal file
22
vendor/github.com/go-sql-driver/mysql/result.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
type mysqlResult struct {
|
||||
affectedRows int64
|
||||
insertId int64
|
||||
}
|
||||
|
||||
func (res *mysqlResult) LastInsertId() (int64, error) {
|
||||
return res.insertId, nil
|
||||
}
|
||||
|
||||
func (res *mysqlResult) RowsAffected() (int64, error) {
|
||||
return res.affectedRows, nil
|
||||
}
|
106
vendor/github.com/go-sql-driver/mysql/rows.go
generated
vendored
Normal file
106
vendor/github.com/go-sql-driver/mysql/rows.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"io"
|
||||
)
|
||||
|
||||
type mysqlField struct {
|
||||
tableName string
|
||||
name string
|
||||
flags fieldFlag
|
||||
fieldType byte
|
||||
decimals byte
|
||||
}
|
||||
|
||||
type mysqlRows struct {
|
||||
mc *mysqlConn
|
||||
columns []mysqlField
|
||||
}
|
||||
|
||||
type binaryRows struct {
|
||||
mysqlRows
|
||||
}
|
||||
|
||||
type textRows struct {
|
||||
mysqlRows
|
||||
}
|
||||
|
||||
type emptyRows struct{}
|
||||
|
||||
func (rows *mysqlRows) Columns() []string {
|
||||
columns := make([]string, len(rows.columns))
|
||||
if rows.mc.cfg.columnsWithAlias {
|
||||
for i := range columns {
|
||||
if tableName := rows.columns[i].tableName; len(tableName) > 0 {
|
||||
columns[i] = tableName + "." + rows.columns[i].name
|
||||
} else {
|
||||
columns[i] = rows.columns[i].name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := range columns {
|
||||
columns[i] = rows.columns[i].name
|
||||
}
|
||||
}
|
||||
return columns
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) Close() error {
|
||||
mc := rows.mc
|
||||
if mc == nil {
|
||||
return nil
|
||||
}
|
||||
if mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
|
||||
// Remove unread packets from stream
|
||||
err := mc.readUntilEOF()
|
||||
rows.mc = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (rows *binaryRows) Next(dest []driver.Value) error {
|
||||
if mc := rows.mc; mc != nil {
|
||||
if mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
|
||||
// Fetch next row from stream
|
||||
return rows.readRow(dest)
|
||||
}
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
func (rows *textRows) Next(dest []driver.Value) error {
|
||||
if mc := rows.mc; mc != nil {
|
||||
if mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
|
||||
// Fetch next row from stream
|
||||
return rows.readRow(dest)
|
||||
}
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
func (rows emptyRows) Columns() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rows emptyRows) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rows emptyRows) Next(dest []driver.Value) error {
|
||||
return io.EOF
|
||||
}
|
150
vendor/github.com/go-sql-driver/mysql/statement.go
generated
vendored
Normal file
150
vendor/github.com/go-sql-driver/mysql/statement.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type mysqlStmt struct {
|
||||
mc *mysqlConn
|
||||
id uint32
|
||||
paramCount int
|
||||
columns []mysqlField // cached from the first query
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Close() error {
|
||||
if stmt.mc == nil || stmt.mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return driver.ErrBadConn
|
||||
}
|
||||
|
||||
err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)
|
||||
stmt.mc = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) NumInput() int {
|
||||
return stmt.paramCount
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
|
||||
return converter{}
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
if stmt.mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
// Send command
|
||||
err := stmt.writeExecutePacket(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mc := stmt.mc
|
||||
|
||||
mc.affectedRows = 0
|
||||
mc.insertId = 0
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err == nil {
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
err = mc.readUntilEOF()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Rows
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
if err == nil {
|
||||
return &mysqlResult{
|
||||
affectedRows: int64(mc.affectedRows),
|
||||
insertId: int64(mc.insertId),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
if stmt.mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
// Send command
|
||||
err := stmt.writeExecutePacket(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mc := stmt.mc
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows := new(binaryRows)
|
||||
rows.mc = mc
|
||||
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
// If not cached, read them and cache them
|
||||
if stmt.columns == nil {
|
||||
rows.columns, err = mc.readColumns(resLen)
|
||||
stmt.columns = rows.columns
|
||||
} else {
|
||||
rows.columns = stmt.columns
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
}
|
||||
|
||||
return rows, err
|
||||
}
|
||||
|
||||
type converter struct{}
|
||||
|
||||
func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
|
||||
if driver.IsValue(v) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Ptr:
|
||||
// indirect pointers
|
||||
if rv.IsNil() {
|
||||
return nil, nil
|
||||
}
|
||||
return c.ConvertValue(rv.Elem().Interface())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return rv.Int(), nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
return int64(rv.Uint()), nil
|
||||
case reflect.Uint64:
|
||||
u64 := rv.Uint()
|
||||
if u64 >= 1<<63 {
|
||||
return strconv.FormatUint(u64, 10), nil
|
||||
}
|
||||
return int64(u64), nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return rv.Float(), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
|
||||
}
|
31
vendor/github.com/go-sql-driver/mysql/transaction.go
generated
vendored
Normal file
31
vendor/github.com/go-sql-driver/mysql/transaction.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
type mysqlTx struct {
|
||||
mc *mysqlConn
|
||||
}
|
||||
|
||||
func (tx *mysqlTx) Commit() (err error) {
|
||||
if tx.mc == nil || tx.mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
err = tx.mc.exec("COMMIT")
|
||||
tx.mc = nil
|
||||
return
|
||||
}
|
||||
|
||||
func (tx *mysqlTx) Rollback() (err error) {
|
||||
if tx.mc == nil || tx.mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
err = tx.mc.exec("ROLLBACK")
|
||||
tx.mc = nil
|
||||
return
|
||||
}
|
975
vendor/github.com/go-sql-driver/mysql/utils.go
generated
vendored
Normal file
975
vendor/github.com/go-sql-driver/mysql/utils.go
generated
vendored
Normal file
@ -0,0 +1,975 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/tls"
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
|
||||
|
||||
errInvalidDSNUnescaped = errors.New("Invalid DSN: Did you forget to escape a param value?")
|
||||
errInvalidDSNAddr = errors.New("Invalid DSN: Network Address not terminated (missing closing brace)")
|
||||
errInvalidDSNNoSlash = errors.New("Invalid DSN: Missing the slash separating the database name")
|
||||
errInvalidDSNUnsafeCollation = errors.New("Invalid DSN: interpolateParams can be used with ascii, latin1, utf8 and utf8mb4 charset")
|
||||
)
|
||||
|
||||
func init() {
|
||||
tlsConfigRegister = make(map[string]*tls.Config)
|
||||
}
|
||||
|
||||
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
|
||||
// Use the key as a value in the DSN where tls=value.
|
||||
//
|
||||
// rootCertPool := x509.NewCertPool()
|
||||
// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
|
||||
// log.Fatal("Failed to append PEM.")
|
||||
// }
|
||||
// clientCert := make([]tls.Certificate, 0, 1)
|
||||
// certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// clientCert = append(clientCert, certs)
|
||||
// mysql.RegisterTLSConfig("custom", &tls.Config{
|
||||
// RootCAs: rootCertPool,
|
||||
// Certificates: clientCert,
|
||||
// })
|
||||
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
|
||||
//
|
||||
func RegisterTLSConfig(key string, config *tls.Config) error {
|
||||
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
|
||||
return fmt.Errorf("Key '%s' is reserved", key)
|
||||
}
|
||||
|
||||
tlsConfigRegister[key] = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeregisterTLSConfig removes the tls.Config associated with key.
|
||||
func DeregisterTLSConfig(key string) {
|
||||
delete(tlsConfigRegister, key)
|
||||
}
|
||||
|
||||
// parseDSN parses the DSN string to a config
|
||||
func parseDSN(dsn string) (cfg *config, err error) {
|
||||
// New config with some default values
|
||||
cfg = &config{
|
||||
loc: time.UTC,
|
||||
collation: defaultCollation,
|
||||
}
|
||||
|
||||
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
|
||||
// Find the last '/' (since the password or the net addr might contain a '/')
|
||||
foundSlash := false
|
||||
for i := len(dsn) - 1; i >= 0; i-- {
|
||||
if dsn[i] == '/' {
|
||||
foundSlash = true
|
||||
var j, k int
|
||||
|
||||
// left part is empty if i <= 0
|
||||
if i > 0 {
|
||||
// [username[:password]@][protocol[(address)]]
|
||||
// Find the last '@' in dsn[:i]
|
||||
for j = i; j >= 0; j-- {
|
||||
if dsn[j] == '@' {
|
||||
// username[:password]
|
||||
// Find the first ':' in dsn[:j]
|
||||
for k = 0; k < j; k++ {
|
||||
if dsn[k] == ':' {
|
||||
cfg.passwd = dsn[k+1 : j]
|
||||
break
|
||||
}
|
||||
}
|
||||
cfg.user = dsn[:k]
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// [protocol[(address)]]
|
||||
// Find the first '(' in dsn[j+1:i]
|
||||
for k = j + 1; k < i; k++ {
|
||||
if dsn[k] == '(' {
|
||||
// dsn[i-1] must be == ')' if an address is specified
|
||||
if dsn[i-1] != ')' {
|
||||
if strings.ContainsRune(dsn[k+1:i], ')') {
|
||||
return nil, errInvalidDSNUnescaped
|
||||
}
|
||||
return nil, errInvalidDSNAddr
|
||||
}
|
||||
cfg.addr = dsn[k+1 : i-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
cfg.net = dsn[j+1 : k]
|
||||
}
|
||||
|
||||
// dbname[?param1=value1&...¶mN=valueN]
|
||||
// Find the first '?' in dsn[i+1:]
|
||||
for j = i + 1; j < len(dsn); j++ {
|
||||
if dsn[j] == '?' {
|
||||
if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
cfg.dbname = dsn[i+1 : j]
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !foundSlash && len(dsn) > 0 {
|
||||
return nil, errInvalidDSNNoSlash
|
||||
}
|
||||
|
||||
if cfg.interpolateParams && unsafeCollations[cfg.collation] {
|
||||
return nil, errInvalidDSNUnsafeCollation
|
||||
}
|
||||
|
||||
// Set default network if empty
|
||||
if cfg.net == "" {
|
||||
cfg.net = "tcp"
|
||||
}
|
||||
|
||||
// Set default address if empty
|
||||
if cfg.addr == "" {
|
||||
switch cfg.net {
|
||||
case "tcp":
|
||||
cfg.addr = "127.0.0.1:3306"
|
||||
case "unix":
|
||||
cfg.addr = "/tmp/mysql.sock"
|
||||
default:
|
||||
return nil, errors.New("Default addr for network '" + cfg.net + "' unknown")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// parseDSNParams parses the DSN "query string"
|
||||
// Values must be url.QueryEscape'ed
|
||||
func parseDSNParams(cfg *config, params string) (err error) {
|
||||
for _, v := range strings.Split(params, "&") {
|
||||
param := strings.SplitN(v, "=", 2)
|
||||
if len(param) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// cfg params
|
||||
switch value := param[1]; param[0] {
|
||||
|
||||
// Enable client side placeholder substitution
|
||||
case "interpolateParams":
|
||||
var isBool bool
|
||||
cfg.interpolateParams, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Disable INFILE whitelist / enable all files
|
||||
case "allowAllFiles":
|
||||
var isBool bool
|
||||
cfg.allowAllFiles, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Use cleartext authentication mode (MySQL 5.5.10+)
|
||||
case "allowCleartextPasswords":
|
||||
var isBool bool
|
||||
cfg.allowCleartextPasswords, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Use old authentication mode (pre MySQL 4.1)
|
||||
case "allowOldPasswords":
|
||||
var isBool bool
|
||||
cfg.allowOldPasswords, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Switch "rowsAffected" mode
|
||||
case "clientFoundRows":
|
||||
var isBool bool
|
||||
cfg.clientFoundRows, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Collation
|
||||
case "collation":
|
||||
collation, ok := collations[value]
|
||||
if !ok {
|
||||
// Note possibility for false negatives:
|
||||
// could be triggered although the collation is valid if the
|
||||
// collations map does not contain entries the server supports.
|
||||
err = errors.New("unknown collation")
|
||||
return
|
||||
}
|
||||
cfg.collation = collation
|
||||
break
|
||||
|
||||
case "columnsWithAlias":
|
||||
var isBool bool
|
||||
cfg.columnsWithAlias, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Time Location
|
||||
case "loc":
|
||||
if value, err = url.QueryUnescape(value); err != nil {
|
||||
return
|
||||
}
|
||||
cfg.loc, err = time.LoadLocation(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Dial Timeout
|
||||
case "timeout":
|
||||
cfg.timeout, err = time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TLS-Encryption
|
||||
case "tls":
|
||||
boolValue, isBool := readBool(value)
|
||||
if isBool {
|
||||
if boolValue {
|
||||
cfg.tls = &tls.Config{}
|
||||
}
|
||||
} else if value, err := url.QueryUnescape(value); err != nil {
|
||||
return fmt.Errorf("Invalid value for tls config name: %v", err)
|
||||
} else {
|
||||
if strings.ToLower(value) == "skip-verify" {
|
||||
cfg.tls = &tls.Config{InsecureSkipVerify: true}
|
||||
} else if tlsConfig, ok := tlsConfigRegister[value]; ok {
|
||||
if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
|
||||
host, _, err := net.SplitHostPort(cfg.addr)
|
||||
if err == nil {
|
||||
tlsConfig.ServerName = host
|
||||
}
|
||||
}
|
||||
|
||||
cfg.tls = tlsConfig
|
||||
} else {
|
||||
return fmt.Errorf("Invalid value / unknown config name: %s", value)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
// lazy init
|
||||
if cfg.params == nil {
|
||||
cfg.params = make(map[string]string)
|
||||
}
|
||||
|
||||
if cfg.params[param[0]], err = url.QueryUnescape(value); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the bool value of the input.
|
||||
// The 2nd return value indicates if the input was a valid bool value
|
||||
func readBool(input string) (value bool, valid bool) {
|
||||
switch input {
|
||||
case "1", "true", "TRUE", "True":
|
||||
return true, true
|
||||
case "0", "false", "FALSE", "False":
|
||||
return false, true
|
||||
}
|
||||
|
||||
// Not a valid bool value
|
||||
return
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Authentication *
|
||||
******************************************************************************/
|
||||
|
||||
// Encrypt password using 4.1+ method
|
||||
func scramblePassword(scramble, password []byte) []byte {
|
||||
if len(password) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// stage1Hash = SHA1(password)
|
||||
crypt := sha1.New()
|
||||
crypt.Write(password)
|
||||
stage1 := crypt.Sum(nil)
|
||||
|
||||
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
|
||||
// inner Hash
|
||||
crypt.Reset()
|
||||
crypt.Write(stage1)
|
||||
hash := crypt.Sum(nil)
|
||||
|
||||
// outer Hash
|
||||
crypt.Reset()
|
||||
crypt.Write(scramble)
|
||||
crypt.Write(hash)
|
||||
scramble = crypt.Sum(nil)
|
||||
|
||||
// token = scrambleHash XOR stage1Hash
|
||||
for i := range scramble {
|
||||
scramble[i] ^= stage1[i]
|
||||
}
|
||||
return scramble
|
||||
}
|
||||
|
||||
// Encrypt password using pre 4.1 (old password) method
|
||||
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
|
||||
type myRnd struct {
|
||||
seed1, seed2 uint32
|
||||
}
|
||||
|
||||
const myRndMaxVal = 0x3FFFFFFF
|
||||
|
||||
// Pseudo random number generator
|
||||
func newMyRnd(seed1, seed2 uint32) *myRnd {
|
||||
return &myRnd{
|
||||
seed1: seed1 % myRndMaxVal,
|
||||
seed2: seed2 % myRndMaxVal,
|
||||
}
|
||||
}
|
||||
|
||||
// Tested to be equivalent to MariaDB's floating point variant
|
||||
// http://play.golang.org/p/QHvhd4qved
|
||||
// http://play.golang.org/p/RG0q4ElWDx
|
||||
func (r *myRnd) NextByte() byte {
|
||||
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
|
||||
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
|
||||
|
||||
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
|
||||
}
|
||||
|
||||
// Generate binary hash from byte string using insecure pre 4.1 method
|
||||
func pwHash(password []byte) (result [2]uint32) {
|
||||
var add uint32 = 7
|
||||
var tmp uint32
|
||||
|
||||
result[0] = 1345345333
|
||||
result[1] = 0x12345671
|
||||
|
||||
for _, c := range password {
|
||||
// skip spaces and tabs in password
|
||||
if c == ' ' || c == '\t' {
|
||||
continue
|
||||
}
|
||||
|
||||
tmp = uint32(c)
|
||||
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
|
||||
result[1] += (result[1] << 8) ^ result[0]
|
||||
add += tmp
|
||||
}
|
||||
|
||||
// Remove sign bit (1<<31)-1)
|
||||
result[0] &= 0x7FFFFFFF
|
||||
result[1] &= 0x7FFFFFFF
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Encrypt password using insecure pre 4.1 method
|
||||
func scrambleOldPassword(scramble, password []byte) []byte {
|
||||
if len(password) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
scramble = scramble[:8]
|
||||
|
||||
hashPw := pwHash(password)
|
||||
hashSc := pwHash(scramble)
|
||||
|
||||
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
|
||||
|
||||
var out [8]byte
|
||||
for i := range out {
|
||||
out[i] = r.NextByte() + 64
|
||||
}
|
||||
|
||||
mask := r.NextByte()
|
||||
for i := range out {
|
||||
out[i] ^= mask
|
||||
}
|
||||
|
||||
return out[:]
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Time related utils *
|
||||
******************************************************************************/
|
||||
|
||||
// NullTime represents a time.Time that may be NULL.
|
||||
// NullTime implements the Scanner interface so
|
||||
// it can be used as a scan destination:
|
||||
//
|
||||
// var nt NullTime
|
||||
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
|
||||
// ...
|
||||
// if nt.Valid {
|
||||
// // use nt.Time
|
||||
// } else {
|
||||
// // NULL value
|
||||
// }
|
||||
//
|
||||
// This NullTime implementation is not driver-specific
|
||||
type NullTime struct {
|
||||
Time time.Time
|
||||
Valid bool // Valid is true if Time is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
// The value type must be time.Time or string / []byte (formatted time-string),
|
||||
// otherwise Scan fails.
|
||||
func (nt *NullTime) Scan(value interface{}) (err error) {
|
||||
if value == nil {
|
||||
nt.Time, nt.Valid = time.Time{}, false
|
||||
return
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case time.Time:
|
||||
nt.Time, nt.Valid = v, true
|
||||
return
|
||||
case []byte:
|
||||
nt.Time, err = parseDateTime(string(v), time.UTC)
|
||||
nt.Valid = (err == nil)
|
||||
return
|
||||
case string:
|
||||
nt.Time, err = parseDateTime(v, time.UTC)
|
||||
nt.Valid = (err == nil)
|
||||
return
|
||||
}
|
||||
|
||||
nt.Valid = false
|
||||
return fmt.Errorf("Can't convert %T to time.Time", value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nt NullTime) Value() (driver.Value, error) {
|
||||
if !nt.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return nt.Time, nil
|
||||
}
|
||||
|
||||
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
|
||||
base := "0000-00-00 00:00:00.0000000"
|
||||
switch len(str) {
|
||||
case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
|
||||
if str == base[:len(str)] {
|
||||
return
|
||||
}
|
||||
t, err = time.Parse(timeFormat[:len(str)], str)
|
||||
default:
|
||||
err = fmt.Errorf("Invalid Time-String: %s", str)
|
||||
return
|
||||
}
|
||||
|
||||
// Adjust location
|
||||
if err == nil && loc != time.UTC {
|
||||
y, mo, d := t.Date()
|
||||
h, mi, s := t.Clock()
|
||||
t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
|
||||
switch num {
|
||||
case 0:
|
||||
return time.Time{}, nil
|
||||
case 4:
|
||||
return time.Date(
|
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
0, 0, 0, 0,
|
||||
loc,
|
||||
), nil
|
||||
case 7:
|
||||
return time.Date(
|
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
int(data[4]), // hour
|
||||
int(data[5]), // minutes
|
||||
int(data[6]), // seconds
|
||||
0,
|
||||
loc,
|
||||
), nil
|
||||
case 11:
|
||||
return time.Date(
|
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
int(data[4]), // hour
|
||||
int(data[5]), // minutes
|
||||
int(data[6]), // seconds
|
||||
int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds
|
||||
loc,
|
||||
), nil
|
||||
}
|
||||
return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num)
|
||||
}
|
||||
|
||||
// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
|
||||
// if the DATE or DATETIME has the zero value.
|
||||
// It must never be changed.
|
||||
// The current behavior depends on database/sql copying the result.
|
||||
var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
|
||||
|
||||
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
||||
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
|
||||
|
||||
func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
|
||||
// length expects the deterministic length of the zero value,
|
||||
// negative time and 100+ hours are automatically added if needed
|
||||
if len(src) == 0 {
|
||||
if justTime {
|
||||
return zeroDateTime[11 : 11+length], nil
|
||||
}
|
||||
return zeroDateTime[:length], nil
|
||||
}
|
||||
var dst []byte // return value
|
||||
var pt, p1, p2, p3 byte // current digit pair
|
||||
var zOffs byte // offset of value in zeroDateTime
|
||||
if justTime {
|
||||
switch length {
|
||||
case
|
||||
8, // time (can be up to 10 when negative and 100+ hours)
|
||||
10, 11, 12, 13, 14, 15: // time with fractional seconds
|
||||
default:
|
||||
return nil, fmt.Errorf("illegal TIME length %d", length)
|
||||
}
|
||||
switch len(src) {
|
||||
case 8, 12:
|
||||
default:
|
||||
return nil, fmt.Errorf("Invalid TIME-packet length %d", len(src))
|
||||
}
|
||||
// +2 to enable negative time and 100+ hours
|
||||
dst = make([]byte, 0, length+2)
|
||||
if src[0] == 1 {
|
||||
dst = append(dst, '-')
|
||||
}
|
||||
if src[1] != 0 {
|
||||
hour := uint16(src[1])*24 + uint16(src[5])
|
||||
pt = byte(hour / 100)
|
||||
p1 = byte(hour - 100*uint16(pt))
|
||||
dst = append(dst, digits01[pt])
|
||||
} else {
|
||||
p1 = src[5]
|
||||
}
|
||||
zOffs = 11
|
||||
src = src[6:]
|
||||
} else {
|
||||
switch length {
|
||||
case 10, 19, 21, 22, 23, 24, 25, 26:
|
||||
default:
|
||||
t := "DATE"
|
||||
if length > 10 {
|
||||
t += "TIME"
|
||||
}
|
||||
return nil, fmt.Errorf("illegal %s length %d", t, length)
|
||||
}
|
||||
switch len(src) {
|
||||
case 4, 7, 11:
|
||||
default:
|
||||
t := "DATE"
|
||||
if length > 10 {
|
||||
t += "TIME"
|
||||
}
|
||||
return nil, fmt.Errorf("illegal %s-packet length %d", t, len(src))
|
||||
}
|
||||
dst = make([]byte, 0, length)
|
||||
// start with the date
|
||||
year := binary.LittleEndian.Uint16(src[:2])
|
||||
pt = byte(year / 100)
|
||||
p1 = byte(year - 100*uint16(pt))
|
||||
p2, p3 = src[2], src[3]
|
||||
dst = append(dst,
|
||||
digits10[pt], digits01[pt],
|
||||
digits10[p1], digits01[p1], '-',
|
||||
digits10[p2], digits01[p2], '-',
|
||||
digits10[p3], digits01[p3],
|
||||
)
|
||||
if length == 10 {
|
||||
return dst, nil
|
||||
}
|
||||
if len(src) == 4 {
|
||||
return append(dst, zeroDateTime[10:length]...), nil
|
||||
}
|
||||
dst = append(dst, ' ')
|
||||
p1 = src[4] // hour
|
||||
src = src[5:]
|
||||
}
|
||||
// p1 is 2-digit hour, src is after hour
|
||||
p2, p3 = src[0], src[1]
|
||||
dst = append(dst,
|
||||
digits10[p1], digits01[p1], ':',
|
||||
digits10[p2], digits01[p2], ':',
|
||||
digits10[p3], digits01[p3],
|
||||
)
|
||||
if length <= byte(len(dst)) {
|
||||
return dst, nil
|
||||
}
|
||||
src = src[2:]
|
||||
if len(src) == 0 {
|
||||
return append(dst, zeroDateTime[19:zOffs+length]...), nil
|
||||
}
|
||||
microsecs := binary.LittleEndian.Uint32(src[:4])
|
||||
p1 = byte(microsecs / 10000)
|
||||
microsecs -= 10000 * uint32(p1)
|
||||
p2 = byte(microsecs / 100)
|
||||
microsecs -= 100 * uint32(p2)
|
||||
p3 = byte(microsecs)
|
||||
switch decimals := zOffs + length - 20; decimals {
|
||||
default:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2], digits01[p2],
|
||||
digits10[p3], digits01[p3],
|
||||
), nil
|
||||
case 1:
|
||||
return append(dst, '.',
|
||||
digits10[p1],
|
||||
), nil
|
||||
case 2:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
), nil
|
||||
case 3:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2],
|
||||
), nil
|
||||
case 4:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2], digits01[p2],
|
||||
), nil
|
||||
case 5:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2], digits01[p2],
|
||||
digits10[p3],
|
||||
), nil
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Convert from and to bytes *
|
||||
******************************************************************************/
|
||||
|
||||
func uint64ToBytes(n uint64) []byte {
|
||||
return []byte{
|
||||
byte(n),
|
||||
byte(n >> 8),
|
||||
byte(n >> 16),
|
||||
byte(n >> 24),
|
||||
byte(n >> 32),
|
||||
byte(n >> 40),
|
||||
byte(n >> 48),
|
||||
byte(n >> 56),
|
||||
}
|
||||
}
|
||||
|
||||
func uint64ToString(n uint64) []byte {
|
||||
var a [20]byte
|
||||
i := 20
|
||||
|
||||
// U+0030 = 0
|
||||
// ...
|
||||
// U+0039 = 9
|
||||
|
||||
var q uint64
|
||||
for n >= 10 {
|
||||
i--
|
||||
q = n / 10
|
||||
a[i] = uint8(n-q*10) + 0x30
|
||||
n = q
|
||||
}
|
||||
|
||||
i--
|
||||
a[i] = uint8(n) + 0x30
|
||||
|
||||
return a[i:]
|
||||
}
|
||||
|
||||
// treats string value as unsigned integer representation
|
||||
func stringToInt(b []byte) int {
|
||||
val := 0
|
||||
for i := range b {
|
||||
val *= 10
|
||||
val += int(b[i] - 0x30)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// returns the string read as a bytes slice, wheter the value is NULL,
|
||||
// the number of bytes read and an error, in case the string is longer than
|
||||
// the input slice
|
||||
func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
|
||||
// Get length
|
||||
num, isNull, n := readLengthEncodedInteger(b)
|
||||
if num < 1 {
|
||||
return b[n:n], isNull, n, nil
|
||||
}
|
||||
|
||||
n += int(num)
|
||||
|
||||
// Check data length
|
||||
if len(b) >= n {
|
||||
return b[n-int(num) : n], false, n, nil
|
||||
}
|
||||
return nil, false, n, io.EOF
|
||||
}
|
||||
|
||||
// returns the number of bytes skipped and an error, in case the string is
|
||||
// longer than the input slice
|
||||
func skipLengthEncodedString(b []byte) (int, error) {
|
||||
// Get length
|
||||
num, _, n := readLengthEncodedInteger(b)
|
||||
if num < 1 {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
n += int(num)
|
||||
|
||||
// Check data length
|
||||
if len(b) >= n {
|
||||
return n, nil
|
||||
}
|
||||
return n, io.EOF
|
||||
}
|
||||
|
||||
// returns the number read, whether the value is NULL and the number of bytes read
|
||||
func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
|
||||
// See issue #349
|
||||
if len(b) == 0 {
|
||||
return 0, true, 1
|
||||
}
|
||||
switch b[0] {
|
||||
|
||||
// 251: NULL
|
||||
case 0xfb:
|
||||
return 0, true, 1
|
||||
|
||||
// 252: value of following 2
|
||||
case 0xfc:
|
||||
return uint64(b[1]) | uint64(b[2])<<8, false, 3
|
||||
|
||||
// 253: value of following 3
|
||||
case 0xfd:
|
||||
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4
|
||||
|
||||
// 254: value of following 8
|
||||
case 0xfe:
|
||||
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
|
||||
uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
|
||||
uint64(b[7])<<48 | uint64(b[8])<<56,
|
||||
false, 9
|
||||
}
|
||||
|
||||
// 0-250: value of first byte
|
||||
return uint64(b[0]), false, 1
|
||||
}
|
||||
|
||||
// encodes a uint64 value and appends it to the given bytes slice
|
||||
func appendLengthEncodedInteger(b []byte, n uint64) []byte {
|
||||
switch {
|
||||
case n <= 250:
|
||||
return append(b, byte(n))
|
||||
|
||||
case n <= 0xffff:
|
||||
return append(b, 0xfc, byte(n), byte(n>>8))
|
||||
|
||||
case n <= 0xffffff:
|
||||
return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16))
|
||||
}
|
||||
return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24),
|
||||
byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
|
||||
}
|
||||
|
||||
// reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize.
|
||||
// If cap(buf) is not enough, reallocate new buffer.
|
||||
func reserveBuffer(buf []byte, appendSize int) []byte {
|
||||
newSize := len(buf) + appendSize
|
||||
if cap(buf) < newSize {
|
||||
// Grow buffer exponentially
|
||||
newBuf := make([]byte, len(buf)*2+appendSize)
|
||||
copy(newBuf, buf)
|
||||
buf = newBuf
|
||||
}
|
||||
return buf[:newSize]
|
||||
}
|
||||
|
||||
// escapeBytesBackslash escapes []byte with backslashes (\)
|
||||
// This escapes the contents of a string (provided as []byte) by adding backslashes before special
|
||||
// characters, and turning others into specific escape sequences, such as
|
||||
// turning newlines into \n and null bytes into \0.
|
||||
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L823-L932
|
||||
func escapeBytesBackslash(buf, v []byte) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for _, c := range v {
|
||||
switch c {
|
||||
case '\x00':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '0'
|
||||
pos += 2
|
||||
case '\n':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'n'
|
||||
pos += 2
|
||||
case '\r':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'r'
|
||||
pos += 2
|
||||
case '\x1a':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'Z'
|
||||
pos += 2
|
||||
case '\'':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
case '"':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '"'
|
||||
pos += 2
|
||||
case '\\':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\\'
|
||||
pos += 2
|
||||
default:
|
||||
buf[pos] = c
|
||||
pos += 1
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
|
||||
// escapeStringBackslash is similar to escapeBytesBackslash but for string.
|
||||
func escapeStringBackslash(buf []byte, v string) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for i := 0; i < len(v); i++ {
|
||||
c := v[i]
|
||||
switch c {
|
||||
case '\x00':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '0'
|
||||
pos += 2
|
||||
case '\n':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'n'
|
||||
pos += 2
|
||||
case '\r':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'r'
|
||||
pos += 2
|
||||
case '\x1a':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'Z'
|
||||
pos += 2
|
||||
case '\'':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
case '"':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '"'
|
||||
pos += 2
|
||||
case '\\':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\\'
|
||||
pos += 2
|
||||
default:
|
||||
buf[pos] = c
|
||||
pos += 1
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
|
||||
// escapeBytesQuotes escapes apostrophes in []byte by doubling them up.
|
||||
// This escapes the contents of a string by doubling up any apostrophes that
|
||||
// it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
|
||||
// effect on the server.
|
||||
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L963-L1038
|
||||
func escapeBytesQuotes(buf, v []byte) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for _, c := range v {
|
||||
if c == '\'' {
|
||||
buf[pos] = '\''
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
} else {
|
||||
buf[pos] = c
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
|
||||
// escapeStringQuotes is similar to escapeBytesQuotes but for string.
|
||||
func escapeStringQuotes(buf []byte, v string) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for i := 0; i < len(v); i++ {
|
||||
c := v[i]
|
||||
if c == '\'' {
|
||||
buf[pos] = '\''
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
} else {
|
||||
buf[pos] = c
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
28
vendor/github.com/howeyc/fsnotify/AUTHORS
generated
vendored
Normal file
28
vendor/github.com/howeyc/fsnotify/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
# Names should be added to this file as
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
# You can update this list using the following command:
|
||||
#
|
||||
# $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Adrien Bustany <adrien@bustany.org>
|
||||
Caleb Spare <cespare@gmail.com>
|
||||
Case Nelson <case@teammating.com>
|
||||
Chris Howey <howeyc@gmail.com> <chris@howey.me>
|
||||
Christoffer Buchholz <christoffer.buchholz@gmail.com>
|
||||
Dave Cheney <dave@cheney.net>
|
||||
Francisco Souza <f@souza.cc>
|
||||
John C Barstow
|
||||
Kelvin Fo <vmirage@gmail.com>
|
||||
Nathan Youngman <git@nathany.com>
|
||||
Paul Hammond <paul@paulhammond.org>
|
||||
Pursuit92 <JoshChase@techpursuit.net>
|
||||
Rob Figueiredo <robfig@gmail.com>
|
||||
Travis Cline <travis.cline@gmail.com>
|
||||
Tudor Golubenco <tudor.g@gmail.com>
|
||||
bronze1man <bronze1man@gmail.com>
|
||||
debrando <denis.brandolini@gmail.com>
|
||||
henrikedwards <henrik.edwards@gmail.com>
|
160
vendor/github.com/howeyc/fsnotify/CHANGELOG.md
generated
vendored
Normal file
160
vendor/github.com/howeyc/fsnotify/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
# Changelog
|
||||
|
||||
## v0.9.0 / 2014-01-17
|
||||
|
||||
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
||||
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
||||
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
||||
|
||||
## v0.8.12 / 2013-11-13
|
||||
|
||||
* [API] Remove FD_SET and friends from Linux adapter
|
||||
|
||||
## v0.8.11 / 2013-11-02
|
||||
|
||||
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
||||
* [Doc] Spotlight and double modify events on OS X [#62][] (reported by @paulhammond)
|
||||
|
||||
## v0.8.10 / 2013-10-19
|
||||
|
||||
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
||||
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
||||
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
||||
|
||||
## v0.8.9 / 2013-09-08
|
||||
|
||||
* [Doc] Contributing (thanks @nathany)
|
||||
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
||||
* [Doc] GoCI badge in README (Linux only) [#60][]
|
||||
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
||||
|
||||
## v0.8.8 / 2013-06-17
|
||||
|
||||
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
||||
|
||||
## v0.8.7 / 2013-06-03
|
||||
|
||||
* [API] Make syscall flags internal
|
||||
* [Fix] inotify: ignore event changes
|
||||
* [Fix] race in symlink test [#45][] (reported by @srid)
|
||||
* [Fix] tests on Windows
|
||||
* lower case error messages
|
||||
|
||||
## v0.8.6 / 2013-05-23
|
||||
|
||||
* kqueue: Use EVT_ONLY flag on Darwin
|
||||
* [Doc] Update README with full example
|
||||
|
||||
## v0.8.5 / 2013-05-09
|
||||
|
||||
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
||||
|
||||
## v0.8.4 / 2013-04-07
|
||||
|
||||
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
||||
|
||||
## v0.8.3 / 2013-03-13
|
||||
|
||||
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
||||
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
||||
|
||||
## v0.8.2 / 2013-02-07
|
||||
|
||||
* [Doc] add Authors
|
||||
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
||||
|
||||
## v0.8.1 / 2013-01-09
|
||||
|
||||
* [Fix] Windows path separators
|
||||
* [Doc] BSD License
|
||||
|
||||
## v0.8.0 / 2012-11-09
|
||||
|
||||
* kqueue: directory watching improvements (thanks @vmirage)
|
||||
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
||||
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
||||
|
||||
## v0.7.4 / 2012-10-09
|
||||
|
||||
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
||||
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
||||
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
||||
* [Fix] kqueue: modify after recreation of file
|
||||
|
||||
## v0.7.3 / 2012-09-27
|
||||
|
||||
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
||||
* [Fix] kqueue: no longer get duplicate CREATE events
|
||||
|
||||
## v0.7.2 / 2012-09-01
|
||||
|
||||
* kqueue: events for created directories
|
||||
|
||||
## v0.7.1 / 2012-07-14
|
||||
|
||||
* [Fix] for renaming files
|
||||
|
||||
## v0.7.0 / 2012-07-02
|
||||
|
||||
* [Feature] FSNotify flags
|
||||
* [Fix] inotify: Added file name back to event path
|
||||
|
||||
## v0.6.0 / 2012-06-06
|
||||
|
||||
* kqueue: watch files after directory created (thanks @tmc)
|
||||
|
||||
## v0.5.1 / 2012-05-22
|
||||
|
||||
* [Fix] inotify: remove all watches before Close()
|
||||
|
||||
## v0.5.0 / 2012-05-03
|
||||
|
||||
* [API] kqueue: return errors during watch instead of sending over channel
|
||||
* kqueue: match symlink behavior on Linux
|
||||
* inotify: add `DELETE_SELF` (requested by @taralx)
|
||||
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
||||
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
||||
|
||||
## v0.4.0 / 2012-03-30
|
||||
|
||||
* Go 1 released: build with go tool
|
||||
* [Feature] Windows support using winfsnotify
|
||||
* Windows does not have attribute change notifications
|
||||
* Roll attribute notifications into IsModify
|
||||
|
||||
## v0.3.0 / 2012-02-19
|
||||
|
||||
* kqueue: add files when watch directory
|
||||
|
||||
## v0.2.0 / 2011-12-30
|
||||
|
||||
* update to latest Go weekly code
|
||||
|
||||
## v0.1.0 / 2011-10-19
|
||||
|
||||
* kqueue: add watch on file creation to match inotify
|
||||
* kqueue: create file event
|
||||
* inotify: ignore `IN_IGNORED` events
|
||||
* event String()
|
||||
* linux: common FileEvent functions
|
||||
* initial commit
|
||||
|
||||
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
||||
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
||||
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
||||
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
||||
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
||||
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
||||
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
||||
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
||||
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
||||
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
||||
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
||||
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
||||
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
||||
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
||||
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
||||
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
||||
[#1]: https://github.com/howeyc/fsnotify/issues/1
|
7
vendor/github.com/howeyc/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
7
vendor/github.com/howeyc/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
# Contributing
|
||||
|
||||
## Moving Notice
|
||||
|
||||
There is a fork being actively developed with a new API in preparation for the Go Standard Library:
|
||||
[github.com/go-fsnotify/fsnotify](https://github.com/go-fsnotify/fsnotify)
|
||||
|
28
vendor/github.com/howeyc/fsnotify/LICENSE
generated
vendored
Normal file
28
vendor/github.com/howeyc/fsnotify/LICENSE
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2012 fsnotify Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
93
vendor/github.com/howeyc/fsnotify/README.md
generated
vendored
Normal file
93
vendor/github.com/howeyc/fsnotify/README.md
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
# File system notifications for Go
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/howeyc/fsnotify?status.png)](http://godoc.org/github.com/howeyc/fsnotify)
|
||||
|
||||
Cross platform: Windows, Linux, BSD and OS X.
|
||||
|
||||
## Moving Notice
|
||||
|
||||
There is a fork being actively developed with a new API in preparation for the Go Standard Library:
|
||||
[github.com/go-fsnotify/fsnotify](https://github.com/go-fsnotify/fsnotify)
|
||||
|
||||
## Example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/howeyc/fsnotify"
|
||||
)
|
||||
|
||||
func main() {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
done := make(chan bool)
|
||||
|
||||
// Process events
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case ev := <-watcher.Event:
|
||||
log.Println("event:", ev)
|
||||
case err := <-watcher.Error:
|
||||
log.Println("error:", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err = watcher.Watch("testDir")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Hang so program doesn't exit
|
||||
<-done
|
||||
|
||||
/* ... do stuff ... */
|
||||
watcher.Close()
|
||||
}
|
||||
```
|
||||
|
||||
For each event:
|
||||
* Name
|
||||
* IsCreate()
|
||||
* IsDelete()
|
||||
* IsModify()
|
||||
* IsRename()
|
||||
|
||||
## FAQ
|
||||
|
||||
**When a file is moved to another directory is it still being watched?**
|
||||
|
||||
No (it shouldn't be, unless you are watching where it was moved to).
|
||||
|
||||
**When I watch a directory, are all subdirectories watched as well?**
|
||||
|
||||
No, you must add watches for any directory you want to watch (a recursive watcher is in the works [#56][]).
|
||||
|
||||
**Do I have to watch the Error and Event channels in a separate goroutine?**
|
||||
|
||||
As of now, yes. Looking into making this single-thread friendly (see [#7][])
|
||||
|
||||
**Why am I receiving multiple events for the same file on OS X?**
|
||||
|
||||
Spotlight indexing on OS X can result in multiple events (see [#62][]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#54][]).
|
||||
|
||||
**How many files can be watched at once?**
|
||||
|
||||
There are OS-specific limits as to how many watches can be created:
|
||||
* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit,
|
||||
reaching this limit results in a "no space left on device" error.
|
||||
* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error.
|
||||
|
||||
|
||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||
[#56]: https://github.com/howeyc/fsnotify/issues/56
|
||||
[#54]: https://github.com/howeyc/fsnotify/issues/54
|
||||
[#7]: https://github.com/howeyc/fsnotify/issues/7
|
||||
|
111
vendor/github.com/howeyc/fsnotify/fsnotify.go
generated
vendored
Normal file
111
vendor/github.com/howeyc/fsnotify/fsnotify.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package fsnotify implements file system notification.
|
||||
package fsnotify
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
FSN_CREATE = 1
|
||||
FSN_MODIFY = 2
|
||||
FSN_DELETE = 4
|
||||
FSN_RENAME = 8
|
||||
|
||||
FSN_ALL = FSN_MODIFY | FSN_DELETE | FSN_RENAME | FSN_CREATE
|
||||
)
|
||||
|
||||
// Purge events from interal chan to external chan if passes filter
|
||||
func (w *Watcher) purgeEvents() {
|
||||
for ev := range w.internalEvent {
|
||||
sendEvent := false
|
||||
w.fsnmut.Lock()
|
||||
fsnFlags := w.fsnFlags[ev.Name]
|
||||
w.fsnmut.Unlock()
|
||||
|
||||
if (fsnFlags&FSN_CREATE == FSN_CREATE) && ev.IsCreate() {
|
||||
sendEvent = true
|
||||
}
|
||||
|
||||
if (fsnFlags&FSN_MODIFY == FSN_MODIFY) && ev.IsModify() {
|
||||
sendEvent = true
|
||||
}
|
||||
|
||||
if (fsnFlags&FSN_DELETE == FSN_DELETE) && ev.IsDelete() {
|
||||
sendEvent = true
|
||||
}
|
||||
|
||||
if (fsnFlags&FSN_RENAME == FSN_RENAME) && ev.IsRename() {
|
||||
sendEvent = true
|
||||
}
|
||||
|
||||
if sendEvent {
|
||||
w.Event <- ev
|
||||
}
|
||||
|
||||
// If there's no file, then no more events for user
|
||||
// BSD must keep watch for internal use (watches DELETEs to keep track
|
||||
// what files exist for create events)
|
||||
if ev.IsDelete() {
|
||||
w.fsnmut.Lock()
|
||||
delete(w.fsnFlags, ev.Name)
|
||||
w.fsnmut.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
close(w.Event)
|
||||
}
|
||||
|
||||
// Watch a given file path
|
||||
func (w *Watcher) Watch(path string) error {
|
||||
return w.WatchFlags(path, FSN_ALL)
|
||||
}
|
||||
|
||||
// Watch a given file path for a particular set of notifications (FSN_MODIFY etc.)
|
||||
func (w *Watcher) WatchFlags(path string, flags uint32) error {
|
||||
w.fsnmut.Lock()
|
||||
w.fsnFlags[path] = flags
|
||||
w.fsnmut.Unlock()
|
||||
return w.watch(path)
|
||||
}
|
||||
|
||||
// Remove a watch on a file
|
||||
func (w *Watcher) RemoveWatch(path string) error {
|
||||
w.fsnmut.Lock()
|
||||
delete(w.fsnFlags, path)
|
||||
w.fsnmut.Unlock()
|
||||
return w.removeWatch(path)
|
||||
}
|
||||
|
||||
// String formats the event e in the form
|
||||
// "filename: DELETE|MODIFY|..."
|
||||
func (e *FileEvent) String() string {
|
||||
var events string = ""
|
||||
|
||||
if e.IsCreate() {
|
||||
events += "|" + "CREATE"
|
||||
}
|
||||
|
||||
if e.IsDelete() {
|
||||
events += "|" + "DELETE"
|
||||
}
|
||||
|
||||
if e.IsModify() {
|
||||
events += "|" + "MODIFY"
|
||||
}
|
||||
|
||||
if e.IsRename() {
|
||||
events += "|" + "RENAME"
|
||||
}
|
||||
|
||||
if e.IsAttrib() {
|
||||
events += "|" + "ATTRIB"
|
||||
}
|
||||
|
||||
if len(events) > 0 {
|
||||
events = events[1:]
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%q: %s", e.Name, events)
|
||||
}
|
496
vendor/github.com/howeyc/fsnotify/fsnotify_bsd.go
generated
vendored
Normal file
496
vendor/github.com/howeyc/fsnotify/fsnotify_bsd.go
generated
vendored
Normal file
@ -0,0 +1,496 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build freebsd openbsd netbsd dragonfly darwin
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
// Flags (from <sys/event.h>)
|
||||
sys_NOTE_DELETE = 0x0001 /* vnode was removed */
|
||||
sys_NOTE_WRITE = 0x0002 /* data contents changed */
|
||||
sys_NOTE_EXTEND = 0x0004 /* size increased */
|
||||
sys_NOTE_ATTRIB = 0x0008 /* attributes changed */
|
||||
sys_NOTE_LINK = 0x0010 /* link count changed */
|
||||
sys_NOTE_RENAME = 0x0020 /* vnode was renamed */
|
||||
sys_NOTE_REVOKE = 0x0040 /* vnode access was revoked */
|
||||
|
||||
// Watch all events
|
||||
sys_NOTE_ALLEVENTS = sys_NOTE_DELETE | sys_NOTE_WRITE | sys_NOTE_ATTRIB | sys_NOTE_RENAME
|
||||
|
||||
// Block for 100 ms on each call to kevent
|
||||
keventWaitTime = 100e6
|
||||
)
|
||||
|
||||
type FileEvent struct {
|
||||
mask uint32 // Mask of events
|
||||
Name string // File name (optional)
|
||||
create bool // set by fsnotify package if found new file
|
||||
}
|
||||
|
||||
// IsCreate reports whether the FileEvent was triggered by a creation
|
||||
func (e *FileEvent) IsCreate() bool { return e.create }
|
||||
|
||||
// IsDelete reports whether the FileEvent was triggered by a delete
|
||||
func (e *FileEvent) IsDelete() bool { return (e.mask & sys_NOTE_DELETE) == sys_NOTE_DELETE }
|
||||
|
||||
// IsModify reports whether the FileEvent was triggered by a file modification
|
||||
func (e *FileEvent) IsModify() bool {
|
||||
return ((e.mask&sys_NOTE_WRITE) == sys_NOTE_WRITE || (e.mask&sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB)
|
||||
}
|
||||
|
||||
// IsRename reports whether the FileEvent was triggered by a change name
|
||||
func (e *FileEvent) IsRename() bool { return (e.mask & sys_NOTE_RENAME) == sys_NOTE_RENAME }
|
||||
|
||||
// IsAttrib reports whether the FileEvent was triggered by a change in the file metadata.
|
||||
func (e *FileEvent) IsAttrib() bool {
|
||||
return (e.mask & sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB
|
||||
}
|
||||
|
||||
type Watcher struct {
|
||||
mu sync.Mutex // Mutex for the Watcher itself.
|
||||
kq int // File descriptor (as returned by the kqueue() syscall)
|
||||
watches map[string]int // Map of watched file descriptors (key: path)
|
||||
wmut sync.Mutex // Protects access to watches.
|
||||
fsnFlags map[string]uint32 // Map of watched files to flags used for filter
|
||||
fsnmut sync.Mutex // Protects access to fsnFlags.
|
||||
enFlags map[string]uint32 // Map of watched files to evfilt note flags used in kqueue
|
||||
enmut sync.Mutex // Protects access to enFlags.
|
||||
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||
finfo map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor)
|
||||
pmut sync.Mutex // Protects access to paths and finfo.
|
||||
fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events)
|
||||
femut sync.Mutex // Protects access to fileExists.
|
||||
externalWatches map[string]bool // Map of watches added by user of the library.
|
||||
ewmut sync.Mutex // Protects access to externalWatches.
|
||||
Error chan error // Errors are sent on this channel
|
||||
internalEvent chan *FileEvent // Events are queued on this channel
|
||||
Event chan *FileEvent // Events are returned on this channel
|
||||
done chan bool // Channel for sending a "quit message" to the reader goroutine
|
||||
isClosed bool // Set to true when Close() is first called
|
||||
}
|
||||
|
||||
// NewWatcher creates and returns a new kevent instance using kqueue(2)
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
fd, errno := syscall.Kqueue()
|
||||
if fd == -1 {
|
||||
return nil, os.NewSyscallError("kqueue", errno)
|
||||
}
|
||||
w := &Watcher{
|
||||
kq: fd,
|
||||
watches: make(map[string]int),
|
||||
fsnFlags: make(map[string]uint32),
|
||||
enFlags: make(map[string]uint32),
|
||||
paths: make(map[int]string),
|
||||
finfo: make(map[int]os.FileInfo),
|
||||
fileExists: make(map[string]bool),
|
||||
externalWatches: make(map[string]bool),
|
||||
internalEvent: make(chan *FileEvent),
|
||||
Event: make(chan *FileEvent),
|
||||
Error: make(chan error),
|
||||
done: make(chan bool, 1),
|
||||
}
|
||||
|
||||
go w.readEvents()
|
||||
go w.purgeEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Close closes a kevent watcher instance
|
||||
// It sends a message to the reader goroutine to quit and removes all watches
|
||||
// associated with the kevent instance
|
||||
func (w *Watcher) Close() error {
|
||||
w.mu.Lock()
|
||||
if w.isClosed {
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
w.isClosed = true
|
||||
w.mu.Unlock()
|
||||
|
||||
// Send "quit" message to the reader goroutine
|
||||
w.done <- true
|
||||
w.wmut.Lock()
|
||||
ws := w.watches
|
||||
w.wmut.Unlock()
|
||||
for path := range ws {
|
||||
w.removeWatch(path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddWatch adds path to the watched file set.
|
||||
// The flags are interpreted as described in kevent(2).
|
||||
func (w *Watcher) addWatch(path string, flags uint32) error {
|
||||
w.mu.Lock()
|
||||
if w.isClosed {
|
||||
w.mu.Unlock()
|
||||
return errors.New("kevent instance already closed")
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
watchDir := false
|
||||
|
||||
w.wmut.Lock()
|
||||
watchfd, found := w.watches[path]
|
||||
w.wmut.Unlock()
|
||||
if !found {
|
||||
fi, errstat := os.Lstat(path)
|
||||
if errstat != nil {
|
||||
return errstat
|
||||
}
|
||||
|
||||
// don't watch socket
|
||||
if fi.Mode()&os.ModeSocket == os.ModeSocket {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Follow Symlinks
|
||||
// Unfortunately, Linux can add bogus symlinks to watch list without
|
||||
// issue, and Windows can't do symlinks period (AFAIK). To maintain
|
||||
// consistency, we will act like everything is fine. There will simply
|
||||
// be no file events for broken symlinks.
|
||||
// Hence the returns of nil on errors.
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
path, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fi, errstat = os.Lstat(path)
|
||||
if errstat != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
fd, errno := syscall.Open(path, open_FLAGS, 0700)
|
||||
if fd == -1 {
|
||||
return errno
|
||||
}
|
||||
watchfd = fd
|
||||
|
||||
w.wmut.Lock()
|
||||
w.watches[path] = watchfd
|
||||
w.wmut.Unlock()
|
||||
|
||||
w.pmut.Lock()
|
||||
w.paths[watchfd] = path
|
||||
w.finfo[watchfd] = fi
|
||||
w.pmut.Unlock()
|
||||
}
|
||||
// Watch the directory if it has not been watched before.
|
||||
w.pmut.Lock()
|
||||
w.enmut.Lock()
|
||||
if w.finfo[watchfd].IsDir() &&
|
||||
(flags&sys_NOTE_WRITE) == sys_NOTE_WRITE &&
|
||||
(!found || (w.enFlags[path]&sys_NOTE_WRITE) != sys_NOTE_WRITE) {
|
||||
watchDir = true
|
||||
}
|
||||
w.enmut.Unlock()
|
||||
w.pmut.Unlock()
|
||||
|
||||
w.enmut.Lock()
|
||||
w.enFlags[path] = flags
|
||||
w.enmut.Unlock()
|
||||
|
||||
var kbuf [1]syscall.Kevent_t
|
||||
watchEntry := &kbuf[0]
|
||||
watchEntry.Fflags = flags
|
||||
syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_ADD|syscall.EV_CLEAR)
|
||||
entryFlags := watchEntry.Flags
|
||||
success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil)
|
||||
if success == -1 {
|
||||
return errno
|
||||
} else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR {
|
||||
return errors.New("kevent add error")
|
||||
}
|
||||
|
||||
if watchDir {
|
||||
errdir := w.watchDirectoryFiles(path)
|
||||
if errdir != nil {
|
||||
return errdir
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watch adds path to the watched file set, watching all events.
|
||||
func (w *Watcher) watch(path string) error {
|
||||
w.ewmut.Lock()
|
||||
w.externalWatches[path] = true
|
||||
w.ewmut.Unlock()
|
||||
return w.addWatch(path, sys_NOTE_ALLEVENTS)
|
||||
}
|
||||
|
||||
// RemoveWatch removes path from the watched file set.
|
||||
func (w *Watcher) removeWatch(path string) error {
|
||||
w.wmut.Lock()
|
||||
watchfd, ok := w.watches[path]
|
||||
w.wmut.Unlock()
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("can't remove non-existent kevent watch for: %s", path))
|
||||
}
|
||||
var kbuf [1]syscall.Kevent_t
|
||||
watchEntry := &kbuf[0]
|
||||
syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_DELETE)
|
||||
entryFlags := watchEntry.Flags
|
||||
success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil)
|
||||
if success == -1 {
|
||||
return os.NewSyscallError("kevent_rm_watch", errno)
|
||||
} else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR {
|
||||
return errors.New("kevent rm error")
|
||||
}
|
||||
syscall.Close(watchfd)
|
||||
w.wmut.Lock()
|
||||
delete(w.watches, path)
|
||||
w.wmut.Unlock()
|
||||
w.enmut.Lock()
|
||||
delete(w.enFlags, path)
|
||||
w.enmut.Unlock()
|
||||
w.pmut.Lock()
|
||||
delete(w.paths, watchfd)
|
||||
fInfo := w.finfo[watchfd]
|
||||
delete(w.finfo, watchfd)
|
||||
w.pmut.Unlock()
|
||||
|
||||
// Find all watched paths that are in this directory that are not external.
|
||||
if fInfo.IsDir() {
|
||||
var pathsToRemove []string
|
||||
w.pmut.Lock()
|
||||
for _, wpath := range w.paths {
|
||||
wdir, _ := filepath.Split(wpath)
|
||||
if filepath.Clean(wdir) == filepath.Clean(path) {
|
||||
w.ewmut.Lock()
|
||||
if !w.externalWatches[wpath] {
|
||||
pathsToRemove = append(pathsToRemove, wpath)
|
||||
}
|
||||
w.ewmut.Unlock()
|
||||
}
|
||||
}
|
||||
w.pmut.Unlock()
|
||||
for _, p := range pathsToRemove {
|
||||
// Since these are internal, not much sense in propagating error
|
||||
// to the user, as that will just confuse them with an error about
|
||||
// a path they did not explicitly watch themselves.
|
||||
w.removeWatch(p)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readEvents reads from the kqueue file descriptor, converts the
|
||||
// received events into Event objects and sends them via the Event channel
|
||||
func (w *Watcher) readEvents() {
|
||||
var (
|
||||
eventbuf [10]syscall.Kevent_t // Event buffer
|
||||
events []syscall.Kevent_t // Received events
|
||||
twait *syscall.Timespec // Time to block waiting for events
|
||||
n int // Number of events returned from kevent
|
||||
errno error // Syscall errno
|
||||
)
|
||||
events = eventbuf[0:0]
|
||||
twait = new(syscall.Timespec)
|
||||
*twait = syscall.NsecToTimespec(keventWaitTime)
|
||||
|
||||
for {
|
||||
// See if there is a message on the "done" channel
|
||||
var done bool
|
||||
select {
|
||||
case done = <-w.done:
|
||||
default:
|
||||
}
|
||||
|
||||
// If "done" message is received
|
||||
if done {
|
||||
errno := syscall.Close(w.kq)
|
||||
if errno != nil {
|
||||
w.Error <- os.NewSyscallError("close", errno)
|
||||
}
|
||||
close(w.internalEvent)
|
||||
close(w.Error)
|
||||
return
|
||||
}
|
||||
|
||||
// Get new events
|
||||
if len(events) == 0 {
|
||||
n, errno = syscall.Kevent(w.kq, nil, eventbuf[:], twait)
|
||||
|
||||
// EINTR is okay, basically the syscall was interrupted before
|
||||
// timeout expired.
|
||||
if errno != nil && errno != syscall.EINTR {
|
||||
w.Error <- os.NewSyscallError("kevent", errno)
|
||||
continue
|
||||
}
|
||||
|
||||
// Received some events
|
||||
if n > 0 {
|
||||
events = eventbuf[0:n]
|
||||
}
|
||||
}
|
||||
|
||||
// Flush the events we received to the events channel
|
||||
for len(events) > 0 {
|
||||
fileEvent := new(FileEvent)
|
||||
watchEvent := &events[0]
|
||||
fileEvent.mask = uint32(watchEvent.Fflags)
|
||||
w.pmut.Lock()
|
||||
fileEvent.Name = w.paths[int(watchEvent.Ident)]
|
||||
fileInfo := w.finfo[int(watchEvent.Ident)]
|
||||
w.pmut.Unlock()
|
||||
if fileInfo != nil && fileInfo.IsDir() && !fileEvent.IsDelete() {
|
||||
// Double check to make sure the directory exist. This can happen when
|
||||
// we do a rm -fr on a recursively watched folders and we receive a
|
||||
// modification event first but the folder has been deleted and later
|
||||
// receive the delete event
|
||||
if _, err := os.Lstat(fileEvent.Name); os.IsNotExist(err) {
|
||||
// mark is as delete event
|
||||
fileEvent.mask |= sys_NOTE_DELETE
|
||||
}
|
||||
}
|
||||
|
||||
if fileInfo != nil && fileInfo.IsDir() && fileEvent.IsModify() && !fileEvent.IsDelete() {
|
||||
w.sendDirectoryChangeEvents(fileEvent.Name)
|
||||
} else {
|
||||
// Send the event on the events channel
|
||||
w.internalEvent <- fileEvent
|
||||
}
|
||||
|
||||
// Move to next event
|
||||
events = events[1:]
|
||||
|
||||
if fileEvent.IsRename() {
|
||||
w.removeWatch(fileEvent.Name)
|
||||
w.femut.Lock()
|
||||
delete(w.fileExists, fileEvent.Name)
|
||||
w.femut.Unlock()
|
||||
}
|
||||
if fileEvent.IsDelete() {
|
||||
w.removeWatch(fileEvent.Name)
|
||||
w.femut.Lock()
|
||||
delete(w.fileExists, fileEvent.Name)
|
||||
w.femut.Unlock()
|
||||
|
||||
// Look for a file that may have overwritten this
|
||||
// (ie mv f1 f2 will delete f2 then create f2)
|
||||
fileDir, _ := filepath.Split(fileEvent.Name)
|
||||
fileDir = filepath.Clean(fileDir)
|
||||
w.wmut.Lock()
|
||||
_, found := w.watches[fileDir]
|
||||
w.wmut.Unlock()
|
||||
if found {
|
||||
// make sure the directory exist before we watch for changes. When we
|
||||
// do a recursive watch and perform rm -fr, the parent directory might
|
||||
// have gone missing, ignore the missing directory and let the
|
||||
// upcoming delete event remove the watch form the parent folder
|
||||
if _, err := os.Lstat(fileDir); !os.IsNotExist(err) {
|
||||
w.sendDirectoryChangeEvents(fileDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||
// Get all files
|
||||
files, err := ioutil.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Search for new files
|
||||
for _, fileInfo := range files {
|
||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||
|
||||
// Inherit fsnFlags from parent directory
|
||||
w.fsnmut.Lock()
|
||||
if flags, found := w.fsnFlags[dirPath]; found {
|
||||
w.fsnFlags[filePath] = flags
|
||||
} else {
|
||||
w.fsnFlags[filePath] = FSN_ALL
|
||||
}
|
||||
w.fsnmut.Unlock()
|
||||
|
||||
if fileInfo.IsDir() == false {
|
||||
// Watch file to mimic linux fsnotify
|
||||
e := w.addWatch(filePath, sys_NOTE_ALLEVENTS)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
} else {
|
||||
// If the user is currently watching directory
|
||||
// we want to preserve the flags used
|
||||
w.enmut.Lock()
|
||||
currFlags, found := w.enFlags[filePath]
|
||||
w.enmut.Unlock()
|
||||
var newFlags uint32 = sys_NOTE_DELETE
|
||||
if found {
|
||||
newFlags |= currFlags
|
||||
}
|
||||
|
||||
// Linux gives deletes if not explicitly watching
|
||||
e := w.addWatch(filePath, newFlags)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
w.femut.Lock()
|
||||
w.fileExists[filePath] = true
|
||||
w.femut.Unlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendDirectoryEvents searches the directory for newly created files
|
||||
// and sends them over the event channel. This functionality is to have
|
||||
// the BSD version of fsnotify match linux fsnotify which provides a
|
||||
// create event for files created in a watched directory.
|
||||
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
||||
// Get all files
|
||||
files, err := ioutil.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
w.Error <- err
|
||||
}
|
||||
|
||||
// Search for new files
|
||||
for _, fileInfo := range files {
|
||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||
w.femut.Lock()
|
||||
_, doesExist := w.fileExists[filePath]
|
||||
w.femut.Unlock()
|
||||
if !doesExist {
|
||||
// Inherit fsnFlags from parent directory
|
||||
w.fsnmut.Lock()
|
||||
if flags, found := w.fsnFlags[dirPath]; found {
|
||||
w.fsnFlags[filePath] = flags
|
||||
} else {
|
||||
w.fsnFlags[filePath] = FSN_ALL
|
||||
}
|
||||
w.fsnmut.Unlock()
|
||||
|
||||
// Send create event
|
||||
fileEvent := new(FileEvent)
|
||||
fileEvent.Name = filePath
|
||||
fileEvent.create = true
|
||||
w.internalEvent <- fileEvent
|
||||
}
|
||||
w.femut.Lock()
|
||||
w.fileExists[filePath] = true
|
||||
w.femut.Unlock()
|
||||
}
|
||||
w.watchDirectoryFiles(dirPath)
|
||||
}
|
304
vendor/github.com/howeyc/fsnotify/fsnotify_linux.go
generated
vendored
Normal file
304
vendor/github.com/howeyc/fsnotify/fsnotify_linux.go
generated
vendored
Normal file
@ -0,0 +1,304 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Options for inotify_init() are not exported
|
||||
// sys_IN_CLOEXEC uint32 = syscall.IN_CLOEXEC
|
||||
// sys_IN_NONBLOCK uint32 = syscall.IN_NONBLOCK
|
||||
|
||||
// Options for AddWatch
|
||||
sys_IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
|
||||
sys_IN_ONESHOT uint32 = syscall.IN_ONESHOT
|
||||
sys_IN_ONLYDIR uint32 = syscall.IN_ONLYDIR
|
||||
|
||||
// The "sys_IN_MASK_ADD" option is not exported, as AddWatch
|
||||
// adds it automatically, if there is already a watch for the given path
|
||||
// sys_IN_MASK_ADD uint32 = syscall.IN_MASK_ADD
|
||||
|
||||
// Events
|
||||
sys_IN_ACCESS uint32 = syscall.IN_ACCESS
|
||||
sys_IN_ALL_EVENTS uint32 = syscall.IN_ALL_EVENTS
|
||||
sys_IN_ATTRIB uint32 = syscall.IN_ATTRIB
|
||||
sys_IN_CLOSE uint32 = syscall.IN_CLOSE
|
||||
sys_IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
|
||||
sys_IN_CLOSE_WRITE uint32 = syscall.IN_CLOSE_WRITE
|
||||
sys_IN_CREATE uint32 = syscall.IN_CREATE
|
||||
sys_IN_DELETE uint32 = syscall.IN_DELETE
|
||||
sys_IN_DELETE_SELF uint32 = syscall.IN_DELETE_SELF
|
||||
sys_IN_MODIFY uint32 = syscall.IN_MODIFY
|
||||
sys_IN_MOVE uint32 = syscall.IN_MOVE
|
||||
sys_IN_MOVED_FROM uint32 = syscall.IN_MOVED_FROM
|
||||
sys_IN_MOVED_TO uint32 = syscall.IN_MOVED_TO
|
||||
sys_IN_MOVE_SELF uint32 = syscall.IN_MOVE_SELF
|
||||
sys_IN_OPEN uint32 = syscall.IN_OPEN
|
||||
|
||||
sys_AGNOSTIC_EVENTS = sys_IN_MOVED_TO | sys_IN_MOVED_FROM | sys_IN_CREATE | sys_IN_ATTRIB | sys_IN_MODIFY | sys_IN_MOVE_SELF | sys_IN_DELETE | sys_IN_DELETE_SELF
|
||||
|
||||
// Special events
|
||||
sys_IN_ISDIR uint32 = syscall.IN_ISDIR
|
||||
sys_IN_IGNORED uint32 = syscall.IN_IGNORED
|
||||
sys_IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
|
||||
sys_IN_UNMOUNT uint32 = syscall.IN_UNMOUNT
|
||||
)
|
||||
|
||||
type FileEvent struct {
|
||||
mask uint32 // Mask of events
|
||||
cookie uint32 // Unique cookie associating related events (for rename(2))
|
||||
Name string // File name (optional)
|
||||
}
|
||||
|
||||
// IsCreate reports whether the FileEvent was triggered by a creation
|
||||
func (e *FileEvent) IsCreate() bool {
|
||||
return (e.mask&sys_IN_CREATE) == sys_IN_CREATE || (e.mask&sys_IN_MOVED_TO) == sys_IN_MOVED_TO
|
||||
}
|
||||
|
||||
// IsDelete reports whether the FileEvent was triggered by a delete
|
||||
func (e *FileEvent) IsDelete() bool {
|
||||
return (e.mask&sys_IN_DELETE_SELF) == sys_IN_DELETE_SELF || (e.mask&sys_IN_DELETE) == sys_IN_DELETE
|
||||
}
|
||||
|
||||
// IsModify reports whether the FileEvent was triggered by a file modification or attribute change
|
||||
func (e *FileEvent) IsModify() bool {
|
||||
return ((e.mask&sys_IN_MODIFY) == sys_IN_MODIFY || (e.mask&sys_IN_ATTRIB) == sys_IN_ATTRIB)
|
||||
}
|
||||
|
||||
// IsRename reports whether the FileEvent was triggered by a change name
|
||||
func (e *FileEvent) IsRename() bool {
|
||||
return ((e.mask&sys_IN_MOVE_SELF) == sys_IN_MOVE_SELF || (e.mask&sys_IN_MOVED_FROM) == sys_IN_MOVED_FROM)
|
||||
}
|
||||
|
||||
// IsAttrib reports whether the FileEvent was triggered by a change in the file metadata.
|
||||
func (e *FileEvent) IsAttrib() bool {
|
||||
return (e.mask & sys_IN_ATTRIB) == sys_IN_ATTRIB
|
||||
}
|
||||
|
||||
type watch struct {
|
||||
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||
}
|
||||
|
||||
type Watcher struct {
|
||||
mu sync.Mutex // Map access
|
||||
fd int // File descriptor (as returned by the inotify_init() syscall)
|
||||
watches map[string]*watch // Map of inotify watches (key: path)
|
||||
fsnFlags map[string]uint32 // Map of watched files to flags used for filter
|
||||
fsnmut sync.Mutex // Protects access to fsnFlags.
|
||||
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||
Error chan error // Errors are sent on this channel
|
||||
internalEvent chan *FileEvent // Events are queued on this channel
|
||||
Event chan *FileEvent // Events are returned on this channel
|
||||
done chan bool // Channel for sending a "quit message" to the reader goroutine
|
||||
isClosed bool // Set to true when Close() is first called
|
||||
}
|
||||
|
||||
// NewWatcher creates and returns a new inotify instance using inotify_init(2)
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
fd, errno := syscall.InotifyInit()
|
||||
if fd == -1 {
|
||||
return nil, os.NewSyscallError("inotify_init", errno)
|
||||
}
|
||||
w := &Watcher{
|
||||
fd: fd,
|
||||
watches: make(map[string]*watch),
|
||||
fsnFlags: make(map[string]uint32),
|
||||
paths: make(map[int]string),
|
||||
internalEvent: make(chan *FileEvent),
|
||||
Event: make(chan *FileEvent),
|
||||
Error: make(chan error),
|
||||
done: make(chan bool, 1),
|
||||
}
|
||||
|
||||
go w.readEvents()
|
||||
go w.purgeEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Close closes an inotify watcher instance
|
||||
// It sends a message to the reader goroutine to quit and removes all watches
|
||||
// associated with the inotify instance
|
||||
func (w *Watcher) Close() error {
|
||||
if w.isClosed {
|
||||
return nil
|
||||
}
|
||||
w.isClosed = true
|
||||
|
||||
// Remove all watches
|
||||
for path := range w.watches {
|
||||
w.RemoveWatch(path)
|
||||
}
|
||||
|
||||
// Send "quit" message to the reader goroutine
|
||||
w.done <- true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddWatch adds path to the watched file set.
|
||||
// The flags are interpreted as described in inotify_add_watch(2).
|
||||
func (w *Watcher) addWatch(path string, flags uint32) error {
|
||||
if w.isClosed {
|
||||
return errors.New("inotify instance already closed")
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
watchEntry, found := w.watches[path]
|
||||
w.mu.Unlock()
|
||||
if found {
|
||||
watchEntry.flags |= flags
|
||||
flags |= syscall.IN_MASK_ADD
|
||||
}
|
||||
wd, errno := syscall.InotifyAddWatch(w.fd, path, flags)
|
||||
if wd == -1 {
|
||||
return errno
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
w.watches[path] = &watch{wd: uint32(wd), flags: flags}
|
||||
w.paths[wd] = path
|
||||
w.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watch adds path to the watched file set, watching all events.
|
||||
func (w *Watcher) watch(path string) error {
|
||||
return w.addWatch(path, sys_AGNOSTIC_EVENTS)
|
||||
}
|
||||
|
||||
// RemoveWatch removes path from the watched file set.
|
||||
func (w *Watcher) removeWatch(path string) error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
watch, ok := w.watches[path]
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path))
|
||||
}
|
||||
success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
|
||||
if success == -1 {
|
||||
return os.NewSyscallError("inotify_rm_watch", errno)
|
||||
}
|
||||
delete(w.watches, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
// readEvents reads from the inotify file descriptor, converts the
|
||||
// received events into Event objects and sends them via the Event channel
|
||||
func (w *Watcher) readEvents() {
|
||||
var (
|
||||
buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||
n int // Number of bytes read with read()
|
||||
errno error // Syscall errno
|
||||
)
|
||||
|
||||
for {
|
||||
// See if there is a message on the "done" channel
|
||||
select {
|
||||
case <-w.done:
|
||||
syscall.Close(w.fd)
|
||||
close(w.internalEvent)
|
||||
close(w.Error)
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
n, errno = syscall.Read(w.fd, buf[:])
|
||||
|
||||
// If EOF is received
|
||||
if n == 0 {
|
||||
syscall.Close(w.fd)
|
||||
close(w.internalEvent)
|
||||
close(w.Error)
|
||||
return
|
||||
}
|
||||
|
||||
if n < 0 {
|
||||
w.Error <- os.NewSyscallError("read", errno)
|
||||
continue
|
||||
}
|
||||
if n < syscall.SizeofInotifyEvent {
|
||||
w.Error <- errors.New("inotify: short read in readEvents()")
|
||||
continue
|
||||
}
|
||||
|
||||
var offset uint32 = 0
|
||||
// We don't know how many events we just read into the buffer
|
||||
// While the offset points to at least one whole event...
|
||||
for offset <= uint32(n-syscall.SizeofInotifyEvent) {
|
||||
// Point "raw" to the event in the buffer
|
||||
raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||
event := new(FileEvent)
|
||||
event.mask = uint32(raw.Mask)
|
||||
event.cookie = uint32(raw.Cookie)
|
||||
nameLen := uint32(raw.Len)
|
||||
// If the event happened to the watched directory or the watched file, the kernel
|
||||
// doesn't append the filename to the event, but we would like to always fill the
|
||||
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
||||
// the "paths" map.
|
||||
w.mu.Lock()
|
||||
event.Name = w.paths[int(raw.Wd)]
|
||||
w.mu.Unlock()
|
||||
watchedName := event.Name
|
||||
if nameLen > 0 {
|
||||
// Point "bytes" at the first byte of the filename
|
||||
bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
|
||||
// The filename is padded with NUL bytes. TrimRight() gets rid of those.
|
||||
event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||
}
|
||||
|
||||
// Send the events that are not ignored on the events channel
|
||||
if !event.ignoreLinux() {
|
||||
// Setup FSNotify flags (inherit from directory watch)
|
||||
w.fsnmut.Lock()
|
||||
if _, fsnFound := w.fsnFlags[event.Name]; !fsnFound {
|
||||
if fsnFlags, watchFound := w.fsnFlags[watchedName]; watchFound {
|
||||
w.fsnFlags[event.Name] = fsnFlags
|
||||
} else {
|
||||
w.fsnFlags[event.Name] = FSN_ALL
|
||||
}
|
||||
}
|
||||
w.fsnmut.Unlock()
|
||||
|
||||
w.internalEvent <- event
|
||||
}
|
||||
|
||||
// Move to the next event in the buffer
|
||||
offset += syscall.SizeofInotifyEvent + nameLen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Certain types of events can be "ignored" and not sent over the Event
|
||||
// channel. Such as events marked ignore by the kernel, or MODIFY events
|
||||
// against files that do not exist.
|
||||
func (e *FileEvent) ignoreLinux() bool {
|
||||
// Ignore anything the inotify API says to ignore
|
||||
if e.mask&sys_IN_IGNORED == sys_IN_IGNORED {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the event is not a DELETE or RENAME, the file must exist.
|
||||
// Otherwise the event is ignored.
|
||||
// *Note*: this was put in place because it was seen that a MODIFY
|
||||
// event was sent after the DELETE. This ignores that MODIFY and
|
||||
// assumes a DELETE will come or has come if the file doesn't exist.
|
||||
if !(e.IsDelete() || e.IsRename()) {
|
||||
_, statErr := os.Lstat(e.Name)
|
||||
return os.IsNotExist(statErr)
|
||||
}
|
||||
return false
|
||||
}
|
11
vendor/github.com/howeyc/fsnotify/fsnotify_open_bsd.go
generated
vendored
Normal file
11
vendor/github.com/howeyc/fsnotify/fsnotify_open_bsd.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build freebsd openbsd netbsd dragonfly
|
||||
|
||||
package fsnotify
|
||||
|
||||
import "syscall"
|
||||
|
||||
const open_FLAGS = syscall.O_NONBLOCK | syscall.O_RDONLY
|
11
vendor/github.com/howeyc/fsnotify/fsnotify_open_darwin.go
generated
vendored
Normal file
11
vendor/github.com/howeyc/fsnotify/fsnotify_open_darwin.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin
|
||||
|
||||
package fsnotify
|
||||
|
||||
import "syscall"
|
||||
|
||||
const open_FLAGS = syscall.O_EVTONLY
|
598
vendor/github.com/howeyc/fsnotify/fsnotify_windows.go
generated
vendored
Normal file
598
vendor/github.com/howeyc/fsnotify/fsnotify_windows.go
generated
vendored
Normal file
@ -0,0 +1,598 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Options for AddWatch
|
||||
sys_FS_ONESHOT = 0x80000000
|
||||
sys_FS_ONLYDIR = 0x1000000
|
||||
|
||||
// Events
|
||||
sys_FS_ACCESS = 0x1
|
||||
sys_FS_ALL_EVENTS = 0xfff
|
||||
sys_FS_ATTRIB = 0x4
|
||||
sys_FS_CLOSE = 0x18
|
||||
sys_FS_CREATE = 0x100
|
||||
sys_FS_DELETE = 0x200
|
||||
sys_FS_DELETE_SELF = 0x400
|
||||
sys_FS_MODIFY = 0x2
|
||||
sys_FS_MOVE = 0xc0
|
||||
sys_FS_MOVED_FROM = 0x40
|
||||
sys_FS_MOVED_TO = 0x80
|
||||
sys_FS_MOVE_SELF = 0x800
|
||||
|
||||
// Special events
|
||||
sys_FS_IGNORED = 0x8000
|
||||
sys_FS_Q_OVERFLOW = 0x4000
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO(nj): Use syscall.ERROR_MORE_DATA from ztypes_windows in Go 1.3+
|
||||
sys_ERROR_MORE_DATA syscall.Errno = 234
|
||||
)
|
||||
|
||||
// Event is the type of the notification messages
|
||||
// received on the watcher's Event channel.
|
||||
type FileEvent struct {
|
||||
mask uint32 // Mask of events
|
||||
cookie uint32 // Unique cookie associating related events (for rename)
|
||||
Name string // File name (optional)
|
||||
}
|
||||
|
||||
// IsCreate reports whether the FileEvent was triggered by a creation
|
||||
func (e *FileEvent) IsCreate() bool { return (e.mask & sys_FS_CREATE) == sys_FS_CREATE }
|
||||
|
||||
// IsDelete reports whether the FileEvent was triggered by a delete
|
||||
func (e *FileEvent) IsDelete() bool {
|
||||
return ((e.mask&sys_FS_DELETE) == sys_FS_DELETE || (e.mask&sys_FS_DELETE_SELF) == sys_FS_DELETE_SELF)
|
||||
}
|
||||
|
||||
// IsModify reports whether the FileEvent was triggered by a file modification or attribute change
|
||||
func (e *FileEvent) IsModify() bool {
|
||||
return ((e.mask&sys_FS_MODIFY) == sys_FS_MODIFY || (e.mask&sys_FS_ATTRIB) == sys_FS_ATTRIB)
|
||||
}
|
||||
|
||||
// IsRename reports whether the FileEvent was triggered by a change name
|
||||
func (e *FileEvent) IsRename() bool {
|
||||
return ((e.mask&sys_FS_MOVE) == sys_FS_MOVE || (e.mask&sys_FS_MOVE_SELF) == sys_FS_MOVE_SELF || (e.mask&sys_FS_MOVED_FROM) == sys_FS_MOVED_FROM || (e.mask&sys_FS_MOVED_TO) == sys_FS_MOVED_TO)
|
||||
}
|
||||
|
||||
// IsAttrib reports whether the FileEvent was triggered by a change in the file metadata.
|
||||
func (e *FileEvent) IsAttrib() bool {
|
||||
return (e.mask & sys_FS_ATTRIB) == sys_FS_ATTRIB
|
||||
}
|
||||
|
||||
const (
|
||||
opAddWatch = iota
|
||||
opRemoveWatch
|
||||
)
|
||||
|
||||
const (
|
||||
provisional uint64 = 1 << (32 + iota)
|
||||
)
|
||||
|
||||
type input struct {
|
||||
op int
|
||||
path string
|
||||
flags uint32
|
||||
reply chan error
|
||||
}
|
||||
|
||||
type inode struct {
|
||||
handle syscall.Handle
|
||||
volume uint32
|
||||
index uint64
|
||||
}
|
||||
|
||||
type watch struct {
|
||||
ov syscall.Overlapped
|
||||
ino *inode // i-number
|
||||
path string // Directory path
|
||||
mask uint64 // Directory itself is being watched with these notify flags
|
||||
names map[string]uint64 // Map of names being watched and their notify flags
|
||||
rename string // Remembers the old name while renaming a file
|
||||
buf [4096]byte
|
||||
}
|
||||
|
||||
type indexMap map[uint64]*watch
|
||||
type watchMap map[uint32]indexMap
|
||||
|
||||
// A Watcher waits for and receives event notifications
|
||||
// for a specific set of files and directories.
|
||||
type Watcher struct {
|
||||
mu sync.Mutex // Map access
|
||||
port syscall.Handle // Handle to completion port
|
||||
watches watchMap // Map of watches (key: i-number)
|
||||
fsnFlags map[string]uint32 // Map of watched files to flags used for filter
|
||||
fsnmut sync.Mutex // Protects access to fsnFlags.
|
||||
input chan *input // Inputs to the reader are sent on this channel
|
||||
internalEvent chan *FileEvent // Events are queued on this channel
|
||||
Event chan *FileEvent // Events are returned on this channel
|
||||
Error chan error // Errors are sent on this channel
|
||||
isClosed bool // Set to true when Close() is first called
|
||||
quit chan chan<- error
|
||||
cookie uint32
|
||||
}
|
||||
|
||||
// NewWatcher creates and returns a Watcher.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
|
||||
if e != nil {
|
||||
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
|
||||
}
|
||||
w := &Watcher{
|
||||
port: port,
|
||||
watches: make(watchMap),
|
||||
fsnFlags: make(map[string]uint32),
|
||||
input: make(chan *input, 1),
|
||||
Event: make(chan *FileEvent, 50),
|
||||
internalEvent: make(chan *FileEvent),
|
||||
Error: make(chan error),
|
||||
quit: make(chan chan<- error, 1),
|
||||
}
|
||||
go w.readEvents()
|
||||
go w.purgeEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Close closes a Watcher.
|
||||
// It sends a message to the reader goroutine to quit and removes all watches
|
||||
// associated with the watcher.
|
||||
func (w *Watcher) Close() error {
|
||||
if w.isClosed {
|
||||
return nil
|
||||
}
|
||||
w.isClosed = true
|
||||
|
||||
// Send "quit" message to the reader goroutine
|
||||
ch := make(chan error)
|
||||
w.quit <- ch
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-ch
|
||||
}
|
||||
|
||||
// AddWatch adds path to the watched file set.
|
||||
func (w *Watcher) AddWatch(path string, flags uint32) error {
|
||||
if w.isClosed {
|
||||
return errors.New("watcher already closed")
|
||||
}
|
||||
in := &input{
|
||||
op: opAddWatch,
|
||||
path: filepath.Clean(path),
|
||||
flags: flags,
|
||||
reply: make(chan error),
|
||||
}
|
||||
w.input <- in
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-in.reply
|
||||
}
|
||||
|
||||
// Watch adds path to the watched file set, watching all events.
|
||||
func (w *Watcher) watch(path string) error {
|
||||
return w.AddWatch(path, sys_FS_ALL_EVENTS)
|
||||
}
|
||||
|
||||
// RemoveWatch removes path from the watched file set.
|
||||
func (w *Watcher) removeWatch(path string) error {
|
||||
in := &input{
|
||||
op: opRemoveWatch,
|
||||
path: filepath.Clean(path),
|
||||
reply: make(chan error),
|
||||
}
|
||||
w.input <- in
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-in.reply
|
||||
}
|
||||
|
||||
func (w *Watcher) wakeupReader() error {
|
||||
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
||||
if e != nil {
|
||||
return os.NewSyscallError("PostQueuedCompletionStatus", e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDir(pathname string) (dir string, err error) {
|
||||
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
|
||||
if e != nil {
|
||||
return "", os.NewSyscallError("GetFileAttributes", e)
|
||||
}
|
||||
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||
dir = pathname
|
||||
} else {
|
||||
dir, _ = filepath.Split(pathname)
|
||||
dir = filepath.Clean(dir)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getIno(path string) (ino *inode, err error) {
|
||||
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
|
||||
syscall.FILE_LIST_DIRECTORY,
|
||||
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
|
||||
nil, syscall.OPEN_EXISTING,
|
||||
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
|
||||
if e != nil {
|
||||
return nil, os.NewSyscallError("CreateFile", e)
|
||||
}
|
||||
var fi syscall.ByHandleFileInformation
|
||||
if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
|
||||
syscall.CloseHandle(h)
|
||||
return nil, os.NewSyscallError("GetFileInformationByHandle", e)
|
||||
}
|
||||
ino = &inode{
|
||||
handle: h,
|
||||
volume: fi.VolumeSerialNumber,
|
||||
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
||||
}
|
||||
return ino, nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (m watchMap) get(ino *inode) *watch {
|
||||
if i := m[ino.volume]; i != nil {
|
||||
return i[ino.index]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (m watchMap) set(ino *inode, watch *watch) {
|
||||
i := m[ino.volume]
|
||||
if i == nil {
|
||||
i = make(indexMap)
|
||||
m[ino.volume] = i
|
||||
}
|
||||
i[ino.index] = watch
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) addWatch(pathname string, flags uint64) error {
|
||||
dir, err := getDir(pathname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if flags&sys_FS_ONLYDIR != 0 && pathname != dir {
|
||||
return nil
|
||||
}
|
||||
ino, err := getIno(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.mu.Lock()
|
||||
watchEntry := w.watches.get(ino)
|
||||
w.mu.Unlock()
|
||||
if watchEntry == nil {
|
||||
if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
|
||||
syscall.CloseHandle(ino.handle)
|
||||
return os.NewSyscallError("CreateIoCompletionPort", e)
|
||||
}
|
||||
watchEntry = &watch{
|
||||
ino: ino,
|
||||
path: dir,
|
||||
names: make(map[string]uint64),
|
||||
}
|
||||
w.mu.Lock()
|
||||
w.watches.set(ino, watchEntry)
|
||||
w.mu.Unlock()
|
||||
flags |= provisional
|
||||
} else {
|
||||
syscall.CloseHandle(ino.handle)
|
||||
}
|
||||
if pathname == dir {
|
||||
watchEntry.mask |= flags
|
||||
} else {
|
||||
watchEntry.names[filepath.Base(pathname)] |= flags
|
||||
}
|
||||
if err = w.startRead(watchEntry); err != nil {
|
||||
return err
|
||||
}
|
||||
if pathname == dir {
|
||||
watchEntry.mask &= ^provisional
|
||||
} else {
|
||||
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) remWatch(pathname string) error {
|
||||
dir, err := getDir(pathname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ino, err := getIno(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.mu.Lock()
|
||||
watch := w.watches.get(ino)
|
||||
w.mu.Unlock()
|
||||
if watch == nil {
|
||||
return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
|
||||
}
|
||||
if pathname == dir {
|
||||
w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED)
|
||||
watch.mask = 0
|
||||
} else {
|
||||
name := filepath.Base(pathname)
|
||||
w.sendEvent(watch.path+"\\"+name, watch.names[name]&sys_FS_IGNORED)
|
||||
delete(watch.names, name)
|
||||
}
|
||||
return w.startRead(watch)
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) deleteWatch(watch *watch) {
|
||||
for name, mask := range watch.names {
|
||||
if mask&provisional == 0 {
|
||||
w.sendEvent(watch.path+"\\"+name, mask&sys_FS_IGNORED)
|
||||
}
|
||||
delete(watch.names, name)
|
||||
}
|
||||
if watch.mask != 0 {
|
||||
if watch.mask&provisional == 0 {
|
||||
w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED)
|
||||
}
|
||||
watch.mask = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) startRead(watch *watch) error {
|
||||
if e := syscall.CancelIo(watch.ino.handle); e != nil {
|
||||
w.Error <- os.NewSyscallError("CancelIo", e)
|
||||
w.deleteWatch(watch)
|
||||
}
|
||||
mask := toWindowsFlags(watch.mask)
|
||||
for _, m := range watch.names {
|
||||
mask |= toWindowsFlags(m)
|
||||
}
|
||||
if mask == 0 {
|
||||
if e := syscall.CloseHandle(watch.ino.handle); e != nil {
|
||||
w.Error <- os.NewSyscallError("CloseHandle", e)
|
||||
}
|
||||
w.mu.Lock()
|
||||
delete(w.watches[watch.ino.volume], watch.ino.index)
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
|
||||
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
|
||||
if e != nil {
|
||||
err := os.NewSyscallError("ReadDirectoryChanges", e)
|
||||
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
||||
// Watched directory was probably removed
|
||||
if w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF) {
|
||||
if watch.mask&sys_FS_ONESHOT != 0 {
|
||||
watch.mask = 0
|
||||
}
|
||||
}
|
||||
err = nil
|
||||
}
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readEvents reads from the I/O completion port, converts the
|
||||
// received events into Event objects and sends them via the Event channel.
|
||||
// Entry point to the I/O thread.
|
||||
func (w *Watcher) readEvents() {
|
||||
var (
|
||||
n, key uint32
|
||||
ov *syscall.Overlapped
|
||||
)
|
||||
runtime.LockOSThread()
|
||||
|
||||
for {
|
||||
e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
|
||||
watch := (*watch)(unsafe.Pointer(ov))
|
||||
|
||||
if watch == nil {
|
||||
select {
|
||||
case ch := <-w.quit:
|
||||
w.mu.Lock()
|
||||
var indexes []indexMap
|
||||
for _, index := range w.watches {
|
||||
indexes = append(indexes, index)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
for _, index := range indexes {
|
||||
for _, watch := range index {
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
if e := syscall.CloseHandle(w.port); e != nil {
|
||||
err = os.NewSyscallError("CloseHandle", e)
|
||||
}
|
||||
close(w.internalEvent)
|
||||
close(w.Error)
|
||||
ch <- err
|
||||
return
|
||||
case in := <-w.input:
|
||||
switch in.op {
|
||||
case opAddWatch:
|
||||
in.reply <- w.addWatch(in.path, uint64(in.flags))
|
||||
case opRemoveWatch:
|
||||
in.reply <- w.remWatch(in.path)
|
||||
}
|
||||
default:
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch e {
|
||||
case sys_ERROR_MORE_DATA:
|
||||
if watch == nil {
|
||||
w.Error <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
|
||||
} else {
|
||||
// The i/o succeeded but the buffer is full.
|
||||
// In theory we should be building up a full packet.
|
||||
// In practice we can get away with just carrying on.
|
||||
n = uint32(unsafe.Sizeof(watch.buf))
|
||||
}
|
||||
case syscall.ERROR_ACCESS_DENIED:
|
||||
// Watched directory was probably removed
|
||||
w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF)
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
continue
|
||||
case syscall.ERROR_OPERATION_ABORTED:
|
||||
// CancelIo was called on this handle
|
||||
continue
|
||||
default:
|
||||
w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e)
|
||||
continue
|
||||
case nil:
|
||||
}
|
||||
|
||||
var offset uint32
|
||||
for {
|
||||
if n == 0 {
|
||||
w.internalEvent <- &FileEvent{mask: sys_FS_Q_OVERFLOW}
|
||||
w.Error <- errors.New("short read in readEvents()")
|
||||
break
|
||||
}
|
||||
|
||||
// Point "raw" to the event in the buffer
|
||||
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
||||
buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
|
||||
name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
|
||||
fullname := watch.path + "\\" + name
|
||||
|
||||
var mask uint64
|
||||
switch raw.Action {
|
||||
case syscall.FILE_ACTION_REMOVED:
|
||||
mask = sys_FS_DELETE_SELF
|
||||
case syscall.FILE_ACTION_MODIFIED:
|
||||
mask = sys_FS_MODIFY
|
||||
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||
watch.rename = name
|
||||
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||
if watch.names[watch.rename] != 0 {
|
||||
watch.names[name] |= watch.names[watch.rename]
|
||||
delete(watch.names, watch.rename)
|
||||
mask = sys_FS_MOVE_SELF
|
||||
}
|
||||
}
|
||||
|
||||
sendNameEvent := func() {
|
||||
if w.sendEvent(fullname, watch.names[name]&mask) {
|
||||
if watch.names[name]&sys_FS_ONESHOT != 0 {
|
||||
delete(watch.names, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||
sendNameEvent()
|
||||
}
|
||||
if raw.Action == syscall.FILE_ACTION_REMOVED {
|
||||
w.sendEvent(fullname, watch.names[name]&sys_FS_IGNORED)
|
||||
delete(watch.names, name)
|
||||
}
|
||||
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
|
||||
if watch.mask&sys_FS_ONESHOT != 0 {
|
||||
watch.mask = 0
|
||||
}
|
||||
}
|
||||
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||
fullname = watch.path + "\\" + watch.rename
|
||||
sendNameEvent()
|
||||
}
|
||||
|
||||
// Move to the next event in the buffer
|
||||
if raw.NextEntryOffset == 0 {
|
||||
break
|
||||
}
|
||||
offset += raw.NextEntryOffset
|
||||
|
||||
// Error!
|
||||
if offset >= n {
|
||||
w.Error <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.startRead(watch); err != nil {
|
||||
w.Error <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
||||
if mask == 0 {
|
||||
return false
|
||||
}
|
||||
event := &FileEvent{mask: uint32(mask), Name: name}
|
||||
if mask&sys_FS_MOVE != 0 {
|
||||
if mask&sys_FS_MOVED_FROM != 0 {
|
||||
w.cookie++
|
||||
}
|
||||
event.cookie = w.cookie
|
||||
}
|
||||
select {
|
||||
case ch := <-w.quit:
|
||||
w.quit <- ch
|
||||
case w.Event <- event:
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func toWindowsFlags(mask uint64) uint32 {
|
||||
var m uint32
|
||||
if mask&sys_FS_ACCESS != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
|
||||
}
|
||||
if mask&sys_FS_MODIFY != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||
}
|
||||
if mask&sys_FS_ATTRIB != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
||||
}
|
||||
if mask&(sys_FS_MOVE|sys_FS_CREATE|sys_FS_DELETE) != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func toFSnotifyFlags(action uint32) uint64 {
|
||||
switch action {
|
||||
case syscall.FILE_ACTION_ADDED:
|
||||
return sys_FS_CREATE
|
||||
case syscall.FILE_ACTION_REMOVED:
|
||||
return sys_FS_DELETE
|
||||
case syscall.FILE_ACTION_MODIFIED:
|
||||
return sys_FS_MODIFY
|
||||
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||
return sys_FS_MOVED_FROM
|
||||
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||
return sys_FS_MOVED_TO
|
||||
}
|
||||
return 0
|
||||
}
|
18
vendor/github.com/jtolds/gls/LICENSE
generated
vendored
Normal file
18
vendor/github.com/jtolds/gls/LICENSE
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
Copyright (c) 2013, Space Monkey, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
89
vendor/github.com/jtolds/gls/README.md
generated
vendored
Normal file
89
vendor/github.com/jtolds/gls/README.md
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
gls
|
||||
===
|
||||
|
||||
Goroutine local storage
|
||||
|
||||
### IMPORTANT NOTE ###
|
||||
|
||||
It is my duty to point you to https://blog.golang.org/context, which is how
|
||||
Google solves all of the problems you'd perhaps consider using this package
|
||||
for at scale.
|
||||
|
||||
One downside to Google's approach is that *all* of your functions must have
|
||||
a new first argument, but after clearing that hurdle everything else is much
|
||||
better.
|
||||
|
||||
If you aren't interested in this warning, read on.
|
||||
|
||||
### Huhwaht? Why? ###
|
||||
|
||||
Every so often, a thread shows up on the
|
||||
[golang-nuts](https://groups.google.com/d/forum/golang-nuts) asking for some
|
||||
form of goroutine-local-storage, or some kind of goroutine id, or some kind of
|
||||
context. There are a few valid use cases for goroutine-local-storage, one of
|
||||
the most prominent being log line context. One poster was interested in being
|
||||
able to log an HTTP request context id in every log line in the same goroutine
|
||||
as the incoming HTTP request, without having to change every library and
|
||||
function call he was interested in logging.
|
||||
|
||||
This would be pretty useful. Provided that you could get some kind of
|
||||
goroutine-local-storage, you could call
|
||||
[log.SetOutput](http://golang.org/pkg/log/#SetOutput) with your own logging
|
||||
writer that checks goroutine-local-storage for some context information and
|
||||
adds that context to your log lines.
|
||||
|
||||
But alas, Andrew Gerrand's typically diplomatic answer to the question of
|
||||
goroutine-local variables was:
|
||||
|
||||
> We wouldn't even be having this discussion if thread local storage wasn't
|
||||
> useful. But every feature comes at a cost, and in my opinion the cost of
|
||||
> threadlocals far outweighs their benefits. They're just not a good fit for
|
||||
> Go.
|
||||
|
||||
So, yeah, that makes sense. That's a pretty good reason for why the language
|
||||
won't support a specific and (relatively) unuseful feature that requires some
|
||||
runtime changes, just for the sake of a little bit of log improvement.
|
||||
|
||||
But does Go require runtime changes?
|
||||
|
||||
### How it works ###
|
||||
|
||||
Go has pretty fantastic introspective and reflective features, but one thing Go
|
||||
doesn't give you is any kind of access to the stack pointer, or frame pointer,
|
||||
or goroutine id, or anything contextual about your current stack. It gives you
|
||||
access to your list of callers, but only along with program counters, which are
|
||||
fixed at compile time.
|
||||
|
||||
But it does give you the stack.
|
||||
|
||||
So, we define 16 special functions and embed base-16 tags into the stack using
|
||||
the call order of those 16 functions. Then, we can read our tags back out of
|
||||
the stack looking at the callers list.
|
||||
|
||||
We then use these tags as an index into a traditional map for implementing
|
||||
this library.
|
||||
|
||||
### What are people saying? ###
|
||||
|
||||
"Wow, that's horrifying."
|
||||
|
||||
"This is the most terrible thing I have seen in a very long time."
|
||||
|
||||
"Where is it getting a context from? Is this serializing all the requests?
|
||||
What the heck is the client being bound to? What are these tags? Why does he
|
||||
need callers? Oh god no. No no no."
|
||||
|
||||
### Docs ###
|
||||
|
||||
Please see the docs at http://godoc.org/github.com/jtolds/gls
|
||||
|
||||
### Related ###
|
||||
|
||||
If you're okay relying on the string format of the current runtime stacktrace
|
||||
including a unique goroutine id (not guaranteed by the spec or anything, but
|
||||
very unlikely to change within a Go release), you might be able to squeeze
|
||||
out a bit more performance by using this similar library, inspired by some
|
||||
code Brad Fitzpatrick wrote for debugging his HTTP/2 library:
|
||||
https://github.com/tylerb/gls (in contrast, jtolds/gls doesn't require
|
||||
any knowledge of the string format of the runtime stacktrace, which
|
||||
probably adds unnecessary overhead).
|
144
vendor/github.com/jtolds/gls/context.go
generated
vendored
Normal file
144
vendor/github.com/jtolds/gls/context.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
// Package gls implements goroutine-local storage.
|
||||
package gls
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
maxCallers = 64
|
||||
)
|
||||
|
||||
var (
|
||||
stackTagPool = &idPool{}
|
||||
mgrRegistry = make(map[*ContextManager]bool)
|
||||
mgrRegistryMtx sync.RWMutex
|
||||
)
|
||||
|
||||
// Values is simply a map of key types to value types. Used by SetValues to
|
||||
// set multiple values at once.
|
||||
type Values map[interface{}]interface{}
|
||||
|
||||
// ContextManager is the main entrypoint for interacting with
|
||||
// Goroutine-local-storage. You can have multiple independent ContextManagers
|
||||
// at any given time. ContextManagers are usually declared globally for a given
|
||||
// class of context variables. You should use NewContextManager for
|
||||
// construction.
|
||||
type ContextManager struct {
|
||||
mtx sync.RWMutex
|
||||
values map[uint]Values
|
||||
}
|
||||
|
||||
// NewContextManager returns a brand new ContextManager. It also registers the
|
||||
// new ContextManager in the ContextManager registry which is used by the Go
|
||||
// method. ContextManagers are typically defined globally at package scope.
|
||||
func NewContextManager() *ContextManager {
|
||||
mgr := &ContextManager{values: make(map[uint]Values)}
|
||||
mgrRegistryMtx.Lock()
|
||||
defer mgrRegistryMtx.Unlock()
|
||||
mgrRegistry[mgr] = true
|
||||
return mgr
|
||||
}
|
||||
|
||||
// Unregister removes a ContextManager from the global registry, used by the
|
||||
// Go method. Only intended for use when you're completely done with a
|
||||
// ContextManager. Use of Unregister at all is rare.
|
||||
func (m *ContextManager) Unregister() {
|
||||
mgrRegistryMtx.Lock()
|
||||
defer mgrRegistryMtx.Unlock()
|
||||
delete(mgrRegistry, m)
|
||||
}
|
||||
|
||||
// SetValues takes a collection of values and a function to call for those
|
||||
// values to be set in. Anything further down the stack will have the set
|
||||
// values available through GetValue. SetValues will add new values or replace
|
||||
// existing values of the same key and will not mutate or change values for
|
||||
// previous stack frames.
|
||||
// SetValues is slow (makes a copy of all current and new values for the new
|
||||
// gls-context) in order to reduce the amount of lookups GetValue requires.
|
||||
func (m *ContextManager) SetValues(new_values Values, context_call func()) {
|
||||
if len(new_values) == 0 {
|
||||
context_call()
|
||||
return
|
||||
}
|
||||
|
||||
tags := readStackTags(1)
|
||||
|
||||
m.mtx.Lock()
|
||||
values := new_values
|
||||
for _, tag := range tags {
|
||||
if existing_values, ok := m.values[tag]; ok {
|
||||
// oh, we found existing values, let's make a copy
|
||||
values = make(Values, len(existing_values)+len(new_values))
|
||||
for key, val := range existing_values {
|
||||
values[key] = val
|
||||
}
|
||||
for key, val := range new_values {
|
||||
values[key] = val
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
new_tag := stackTagPool.Acquire()
|
||||
m.values[new_tag] = values
|
||||
m.mtx.Unlock()
|
||||
defer func() {
|
||||
m.mtx.Lock()
|
||||
delete(m.values, new_tag)
|
||||
m.mtx.Unlock()
|
||||
stackTagPool.Release(new_tag)
|
||||
}()
|
||||
|
||||
addStackTag(new_tag, context_call)
|
||||
}
|
||||
|
||||
// GetValue will return a previously set value, provided that the value was set
|
||||
// by SetValues somewhere higher up the stack. If the value is not found, ok
|
||||
// will be false.
|
||||
func (m *ContextManager) GetValue(key interface{}) (value interface{}, ok bool) {
|
||||
|
||||
tags := readStackTags(1)
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
for _, tag := range tags {
|
||||
if values, ok := m.values[tag]; ok {
|
||||
value, ok := values[key]
|
||||
return value, ok
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (m *ContextManager) getValues() Values {
|
||||
tags := readStackTags(2)
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
for _, tag := range tags {
|
||||
if values, ok := m.values[tag]; ok {
|
||||
return values
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Go preserves ContextManager values and Goroutine-local-storage across new
|
||||
// goroutine invocations. The Go method makes a copy of all existing values on
|
||||
// all registered context managers and makes sure they are still set after
|
||||
// kicking off the provided function in a new goroutine. If you don't use this
|
||||
// Go method instead of the standard 'go' keyword, you will lose values in
|
||||
// ContextManagers, as goroutines have brand new stacks.
|
||||
func Go(cb func()) {
|
||||
mgrRegistryMtx.RLock()
|
||||
defer mgrRegistryMtx.RUnlock()
|
||||
|
||||
for mgr, _ := range mgrRegistry {
|
||||
values := mgr.getValues()
|
||||
if len(values) > 0 {
|
||||
mgr_copy := mgr
|
||||
cb_copy := cb
|
||||
cb = func() { mgr_copy.SetValues(values, cb_copy) }
|
||||
}
|
||||
}
|
||||
|
||||
go cb()
|
||||
}
|
13
vendor/github.com/jtolds/gls/gen_sym.go
generated
vendored
Normal file
13
vendor/github.com/jtolds/gls/gen_sym.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
package gls
|
||||
|
||||
var (
|
||||
symPool = &idPool{}
|
||||
)
|
||||
|
||||
// ContextKey is a throwaway value you can use as a key to a ContextManager
|
||||
type ContextKey struct{ id uint }
|
||||
|
||||
// GenSym will return a brand new, never-before-used ContextKey
|
||||
func GenSym() ContextKey {
|
||||
return ContextKey{id: symPool.Acquire()}
|
||||
}
|
34
vendor/github.com/jtolds/gls/id_pool.go
generated
vendored
Normal file
34
vendor/github.com/jtolds/gls/id_pool.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
package gls
|
||||
|
||||
// though this could probably be better at keeping ids smaller, the goal of
|
||||
// this class is to keep a registry of the smallest unique integer ids
|
||||
// per-process possible
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type idPool struct {
|
||||
mtx sync.Mutex
|
||||
released []uint
|
||||
max_id uint
|
||||
}
|
||||
|
||||
func (p *idPool) Acquire() (id uint) {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
if len(p.released) > 0 {
|
||||
id = p.released[len(p.released)-1]
|
||||
p.released = p.released[:len(p.released)-1]
|
||||
return id
|
||||
}
|
||||
id = p.max_id
|
||||
p.max_id++
|
||||
return id
|
||||
}
|
||||
|
||||
func (p *idPool) Release(id uint) {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
p.released = append(p.released, id)
|
||||
}
|
43
vendor/github.com/jtolds/gls/stack_tags.go
generated
vendored
Normal file
43
vendor/github.com/jtolds/gls/stack_tags.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
package gls
|
||||
|
||||
// so, basically, we're going to encode integer tags in base-16 on the stack
|
||||
|
||||
const (
|
||||
bitWidth = 4
|
||||
)
|
||||
|
||||
func addStackTag(tag uint, context_call func()) {
|
||||
if context_call == nil {
|
||||
return
|
||||
}
|
||||
markS(tag, context_call)
|
||||
}
|
||||
|
||||
func markS(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark0(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark1(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark2(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark3(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark4(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark5(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark6(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark7(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark8(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark9(tag uint, cb func()) { _m(tag, cb) }
|
||||
func markA(tag uint, cb func()) { _m(tag, cb) }
|
||||
func markB(tag uint, cb func()) { _m(tag, cb) }
|
||||
func markC(tag uint, cb func()) { _m(tag, cb) }
|
||||
func markD(tag uint, cb func()) { _m(tag, cb) }
|
||||
func markE(tag uint, cb func()) { _m(tag, cb) }
|
||||
func markF(tag uint, cb func()) { _m(tag, cb) }
|
||||
|
||||
var pc_lookup = make(map[uintptr]int8, 17)
|
||||
var mark_lookup [16]func(uint, func())
|
||||
|
||||
func _m(tag_remainder uint, cb func()) {
|
||||
if tag_remainder == 0 {
|
||||
cb()
|
||||
} else {
|
||||
mark_lookup[tag_remainder&0xf](tag_remainder>>bitWidth, cb)
|
||||
}
|
||||
}
|
101
vendor/github.com/jtolds/gls/stack_tags_js.go
generated
vendored
Normal file
101
vendor/github.com/jtolds/gls/stack_tags_js.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
// +build js
|
||||
|
||||
package gls
|
||||
|
||||
// This file is used for GopherJS builds, which don't have normal runtime support
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gopherjs/gopherjs/js"
|
||||
)
|
||||
|
||||
var stackRE = regexp.MustCompile("\\s+at (\\S*) \\([^:]+:(\\d+):(\\d+)")
|
||||
|
||||
func findPtr() uintptr {
|
||||
jsStack := js.Global.Get("Error").New().Get("stack").Call("split", "\n")
|
||||
for i := 1; i < jsStack.Get("length").Int(); i++ {
|
||||
item := jsStack.Index(i).String()
|
||||
matches := stackRE.FindAllStringSubmatch(item, -1)
|
||||
if matches == nil {
|
||||
return 0
|
||||
}
|
||||
pkgPath := matches[0][1]
|
||||
if strings.HasPrefix(pkgPath, "$packages.github.com/jtolds/gls.mark") {
|
||||
line, _ := strconv.Atoi(matches[0][2])
|
||||
char, _ := strconv.Atoi(matches[0][3])
|
||||
x := (uintptr(line) << 16) | uintptr(char)
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
setEntries := func(f func(uint, func()), v int8) {
|
||||
var ptr uintptr
|
||||
f(0, func() {
|
||||
ptr = findPtr()
|
||||
})
|
||||
pc_lookup[ptr] = v
|
||||
if v >= 0 {
|
||||
mark_lookup[v] = f
|
||||
}
|
||||
}
|
||||
setEntries(markS, -0x1)
|
||||
setEntries(mark0, 0x0)
|
||||
setEntries(mark1, 0x1)
|
||||
setEntries(mark2, 0x2)
|
||||
setEntries(mark3, 0x3)
|
||||
setEntries(mark4, 0x4)
|
||||
setEntries(mark5, 0x5)
|
||||
setEntries(mark6, 0x6)
|
||||
setEntries(mark7, 0x7)
|
||||
setEntries(mark8, 0x8)
|
||||
setEntries(mark9, 0x9)
|
||||
setEntries(markA, 0xa)
|
||||
setEntries(markB, 0xb)
|
||||
setEntries(markC, 0xc)
|
||||
setEntries(markD, 0xd)
|
||||
setEntries(markE, 0xe)
|
||||
setEntries(markF, 0xf)
|
||||
}
|
||||
|
||||
func currentStack(skip int) (stack []uintptr) {
|
||||
jsStack := js.Global.Get("Error").New().Get("stack").Call("split", "\n")
|
||||
for i := skip + 2; i < jsStack.Get("length").Int(); i++ {
|
||||
item := jsStack.Index(i).String()
|
||||
matches := stackRE.FindAllStringSubmatch(item, -1)
|
||||
if matches == nil {
|
||||
return stack
|
||||
}
|
||||
line, _ := strconv.Atoi(matches[0][2])
|
||||
char, _ := strconv.Atoi(matches[0][3])
|
||||
x := (uintptr(line) << 16) | uintptr(char)&0xffff
|
||||
stack = append(stack, x)
|
||||
}
|
||||
|
||||
return stack
|
||||
}
|
||||
|
||||
func readStackTags(skip int) (tags []uint) {
|
||||
stack := currentStack(skip)
|
||||
var current_tag uint
|
||||
for _, pc := range stack {
|
||||
val, ok := pc_lookup[pc]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if val < 0 {
|
||||
tags = append(tags, current_tag)
|
||||
current_tag = 0
|
||||
continue
|
||||
}
|
||||
current_tag <<= bitWidth
|
||||
current_tag += uint(val)
|
||||
}
|
||||
return
|
||||
}
|
61
vendor/github.com/jtolds/gls/stack_tags_main.go
generated
vendored
Normal file
61
vendor/github.com/jtolds/gls/stack_tags_main.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
// +build !js
|
||||
|
||||
package gls
|
||||
|
||||
// This file is used for standard Go builds, which have the expected runtime support
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func init() {
|
||||
setEntries := func(f func(uint, func()), v int8) {
|
||||
pc_lookup[reflect.ValueOf(f).Pointer()] = v
|
||||
if v >= 0 {
|
||||
mark_lookup[v] = f
|
||||
}
|
||||
}
|
||||
setEntries(markS, -0x1)
|
||||
setEntries(mark0, 0x0)
|
||||
setEntries(mark1, 0x1)
|
||||
setEntries(mark2, 0x2)
|
||||
setEntries(mark3, 0x3)
|
||||
setEntries(mark4, 0x4)
|
||||
setEntries(mark5, 0x5)
|
||||
setEntries(mark6, 0x6)
|
||||
setEntries(mark7, 0x7)
|
||||
setEntries(mark8, 0x8)
|
||||
setEntries(mark9, 0x9)
|
||||
setEntries(markA, 0xa)
|
||||
setEntries(markB, 0xb)
|
||||
setEntries(markC, 0xc)
|
||||
setEntries(markD, 0xd)
|
||||
setEntries(markE, 0xe)
|
||||
setEntries(markF, 0xf)
|
||||
}
|
||||
|
||||
func currentStack(skip int) []uintptr {
|
||||
stack := make([]uintptr, maxCallers)
|
||||
return stack[:runtime.Callers(3+skip, stack)]
|
||||
}
|
||||
|
||||
func readStackTags(skip int) (tags []uint) {
|
||||
stack := currentStack(skip)
|
||||
var current_tag uint
|
||||
for _, pc := range stack {
|
||||
pc = runtime.FuncForPC(pc).Entry()
|
||||
val, ok := pc_lookup[pc]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if val < 0 {
|
||||
tags = append(tags, current_tag)
|
||||
current_tag = 0
|
||||
continue
|
||||
}
|
||||
current_tag <<= bitWidth
|
||||
current_tag += uint(val)
|
||||
}
|
||||
return
|
||||
}
|
29
vendor/github.com/lib/pq/CONTRIBUTING.md
generated
vendored
Normal file
29
vendor/github.com/lib/pq/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
## Contributing to pq
|
||||
|
||||
`pq` has a backlog of pull requests, but contributions are still very
|
||||
much welcome. You can help with patch review, submitting bug reports,
|
||||
or adding new functionality. There is no formal style guide, but
|
||||
please conform to the style of existing code and general Go formatting
|
||||
conventions when submitting patches.
|
||||
|
||||
### Patch review
|
||||
|
||||
Help review existing open pull requests by commenting on the code or
|
||||
proposed functionality.
|
||||
|
||||
### Bug reports
|
||||
|
||||
We appreciate any bug reports, but especially ones with self-contained
|
||||
(doesn't depend on code outside of pq), minimal (can't be simplified
|
||||
further) test cases. It's especially helpful if you can submit a pull
|
||||
request with just the failing test case (you'll probably want to
|
||||
pattern it after the tests in
|
||||
[conn_test.go](https://github.com/lib/pq/blob/master/conn_test.go).
|
||||
|
||||
### New functionality
|
||||
|
||||
There are a number of pending patches for new functionality, so
|
||||
additional feature patches will take a while to merge. Still, patches
|
||||
are generally reviewed based on usefulness and complexity in addition
|
||||
to time-in-queue, so if you have a knockout idea, take a shot. Feel
|
||||
free to open an issue discussion your proposed patch beforehand.
|
8
vendor/github.com/lib/pq/LICENSE.md
generated
vendored
Normal file
8
vendor/github.com/lib/pq/LICENSE.md
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
Copyright (c) 2011-2013, 'pq' Contributors
|
||||
Portions Copyright (C) 2011 Blake Mizerany
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
105
vendor/github.com/lib/pq/README.md
generated
vendored
Normal file
105
vendor/github.com/lib/pq/README.md
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
# pq - A pure Go postgres driver for Go's database/sql package
|
||||
|
||||
[![Build Status](https://travis-ci.org/lib/pq.png?branch=master)](https://travis-ci.org/lib/pq)
|
||||
|
||||
## Install
|
||||
|
||||
go get github.com/lib/pq
|
||||
|
||||
## Docs
|
||||
|
||||
For detailed documentation and basic usage examples, please see the package
|
||||
documentation at <http://godoc.org/github.com/lib/pq>.
|
||||
|
||||
## Tests
|
||||
|
||||
`go test` is used for testing. A running PostgreSQL server is
|
||||
required, with the ability to log in. The default database to connect
|
||||
to test with is "pqgotest," but it can be overridden using environment
|
||||
variables.
|
||||
|
||||
Example:
|
||||
|
||||
PGHOST=/run/postgresql go test github.com/lib/pq
|
||||
|
||||
Optionally, a benchmark suite can be run as part of the tests:
|
||||
|
||||
PGHOST=/run/postgresql go test -bench .
|
||||
|
||||
## Features
|
||||
|
||||
* SSL
|
||||
* Handles bad connections for `database/sql`
|
||||
* Scan `time.Time` correctly (i.e. `timestamp[tz]`, `time[tz]`, `date`)
|
||||
* Scan binary blobs correctly (i.e. `bytea`)
|
||||
* Package for `hstore` support
|
||||
* COPY FROM support
|
||||
* pq.ParseURL for converting urls to connection strings for sql.Open.
|
||||
* Many libpq compatible environment variables
|
||||
* Unix socket support
|
||||
* Notifications: `LISTEN`/`NOTIFY`
|
||||
* pgpass support
|
||||
|
||||
## Future / Things you can help with
|
||||
|
||||
* Better COPY FROM / COPY TO (see discussion in #181)
|
||||
|
||||
## Thank you (alphabetical)
|
||||
|
||||
Some of these contributors are from the original library `bmizerany/pq.go` whose
|
||||
code still exists in here.
|
||||
|
||||
* Andy Balholm (andybalholm)
|
||||
* Ben Berkert (benburkert)
|
||||
* Benjamin Heatwole (bheatwole)
|
||||
* Bill Mill (llimllib)
|
||||
* Bjørn Madsen (aeons)
|
||||
* Blake Gentry (bgentry)
|
||||
* Brad Fitzpatrick (bradfitz)
|
||||
* Charlie Melbye (cmelbye)
|
||||
* Chris Bandy (cbandy)
|
||||
* Chris Gilling (cgilling)
|
||||
* Chris Walsh (cwds)
|
||||
* Dan Sosedoff (sosedoff)
|
||||
* Daniel Farina (fdr)
|
||||
* Eric Chlebek (echlebek)
|
||||
* Eric Garrido (minusnine)
|
||||
* Eric Urban (hydrogen18)
|
||||
* Everyone at The Go Team
|
||||
* Evan Shaw (edsrzf)
|
||||
* Ewan Chou (coocood)
|
||||
* Fazal Majid (fazalmajid)
|
||||
* Federico Romero (federomero)
|
||||
* Fumin (fumin)
|
||||
* Gary Burd (garyburd)
|
||||
* Heroku (heroku)
|
||||
* James Pozdena (jpoz)
|
||||
* Jason McVetta (jmcvetta)
|
||||
* Jeremy Jay (pbnjay)
|
||||
* Joakim Sernbrant (serbaut)
|
||||
* John Gallagher (jgallagher)
|
||||
* Jonathan Rudenberg (titanous)
|
||||
* Joël Stemmer (jstemmer)
|
||||
* Kamil Kisiel (kisielk)
|
||||
* Kelly Dunn (kellydunn)
|
||||
* Keith Rarick (kr)
|
||||
* Kir Shatrov (kirs)
|
||||
* Lann Martin (lann)
|
||||
* Maciek Sakrejda (uhoh-itsmaciek)
|
||||
* Marc Brinkmann (mbr)
|
||||
* Marko Tiikkaja (johto)
|
||||
* Matt Newberry (MattNewberry)
|
||||
* Matt Robenolt (mattrobenolt)
|
||||
* Martin Olsen (martinolsen)
|
||||
* Mike Lewis (mikelikespie)
|
||||
* Nicolas Patry (Narsil)
|
||||
* Oliver Tonnhofer (olt)
|
||||
* Patrick Hayes (phayes)
|
||||
* Paul Hammond (paulhammond)
|
||||
* Ryan Smith (ryandotsmith)
|
||||
* Samuel Stauffer (samuel)
|
||||
* Timothée Peignier (cyberdelia)
|
||||
* Travis Cline (tmc)
|
||||
* TruongSinh Tran-Nguyen (truongsinh)
|
||||
* Yaismel Miranda (ympons)
|
||||
* notedit (notedit)
|
727
vendor/github.com/lib/pq/array.go
generated
vendored
Normal file
727
vendor/github.com/lib/pq/array.go
generated
vendored
Normal file
@ -0,0 +1,727 @@
|
||||
package pq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var typeByteSlice = reflect.TypeOf([]byte{})
|
||||
var typeDriverValuer = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
||||
var typeSqlScanner = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
|
||||
|
||||
// Array returns the optimal driver.Valuer and sql.Scanner for an array or
|
||||
// slice of any dimension.
|
||||
//
|
||||
// For example:
|
||||
// db.Query(`SELECT * FROM t WHERE id = ANY($1)`, pq.Array([]int{235, 401}))
|
||||
//
|
||||
// var x []sql.NullInt64
|
||||
// db.QueryRow('SELECT ARRAY[235, 401]').Scan(pq.Array(&x))
|
||||
//
|
||||
// Scanning multi-dimensional arrays is not supported. Arrays where the lower
|
||||
// bound is not one (such as `[0:0]={1}') are not supported.
|
||||
func Array(a interface{}) interface {
|
||||
driver.Valuer
|
||||
sql.Scanner
|
||||
} {
|
||||
switch a := a.(type) {
|
||||
case []bool:
|
||||
return (*BoolArray)(&a)
|
||||
case []float64:
|
||||
return (*Float64Array)(&a)
|
||||
case []int64:
|
||||
return (*Int64Array)(&a)
|
||||
case []string:
|
||||
return (*StringArray)(&a)
|
||||
|
||||
case *[]bool:
|
||||
return (*BoolArray)(a)
|
||||
case *[]float64:
|
||||
return (*Float64Array)(a)
|
||||
case *[]int64:
|
||||
return (*Int64Array)(a)
|
||||
case *[]string:
|
||||
return (*StringArray)(a)
|
||||
}
|
||||
|
||||
return GenericArray{a}
|
||||
}
|
||||
|
||||
// ArrayDelimiter may be optionally implemented by driver.Valuer or sql.Scanner
|
||||
// to override the array delimiter used by GenericArray.
|
||||
type ArrayDelimiter interface {
|
||||
// ArrayDelimiter returns the delimiter character(s) for this element's type.
|
||||
ArrayDelimiter() string
|
||||
}
|
||||
|
||||
// BoolArray represents a one-dimensional array of the PostgreSQL boolean type.
|
||||
type BoolArray []bool
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a *BoolArray) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src))
|
||||
}
|
||||
|
||||
return fmt.Errorf("pq: cannot convert %T to BoolArray", src)
|
||||
}
|
||||
|
||||
func (a *BoolArray) scanBytes(src []byte) error {
|
||||
elems, err := scanLinearArray(src, []byte{','}, "BoolArray")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(elems) == 0 {
|
||||
*a = (*a)[:0]
|
||||
} else {
|
||||
b := make(BoolArray, len(elems))
|
||||
for i, v := range elems {
|
||||
if len(v) != 1 {
|
||||
return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||
}
|
||||
switch v[0] {
|
||||
case 't':
|
||||
b[i] = true
|
||||
case 'f':
|
||||
b[i] = false
|
||||
default:
|
||||
return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a BoolArray) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if n := len(a); n > 0 {
|
||||
// There will be exactly two curly brackets, N bytes of values,
|
||||
// and N-1 bytes of delimiters.
|
||||
b := make([]byte, 1+2*n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
b[2*i] = ','
|
||||
if a[i] {
|
||||
b[1+2*i] = 't'
|
||||
} else {
|
||||
b[1+2*i] = 'f'
|
||||
}
|
||||
}
|
||||
|
||||
b[0] = '{'
|
||||
b[2*n] = '}'
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// ByteaArray represents a one-dimensional array of the PostgreSQL bytea type.
|
||||
type ByteaArray [][]byte
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a *ByteaArray) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src))
|
||||
}
|
||||
|
||||
return fmt.Errorf("pq: cannot convert %T to ByteaArray", src)
|
||||
}
|
||||
|
||||
func (a *ByteaArray) scanBytes(src []byte) error {
|
||||
elems, err := scanLinearArray(src, []byte{','}, "ByteaArray")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(elems) == 0 {
|
||||
*a = (*a)[:0]
|
||||
} else {
|
||||
b := make(ByteaArray, len(elems))
|
||||
for i, v := range elems {
|
||||
b[i], err = parseBytea(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse bytea array index %d: %s", i, err.Error())
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface. It uses the "hex" format which
|
||||
// is only supported on PostgreSQL 9.0 or newer.
|
||||
func (a ByteaArray) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if n := len(a); n > 0 {
|
||||
// There will be at least two curly brackets, 2*N bytes of quotes,
|
||||
// 3*N bytes of hex formatting, and N-1 bytes of delimiters.
|
||||
size := 1 + 6*n
|
||||
for _, x := range a {
|
||||
size += hex.EncodedLen(len(x))
|
||||
}
|
||||
|
||||
b := make([]byte, size)
|
||||
|
||||
for i, s := 0, b; i < n; i++ {
|
||||
o := copy(s, `,"\\x`)
|
||||
o += hex.Encode(s[o:], a[i])
|
||||
s[o] = '"'
|
||||
s = s[o+1:]
|
||||
}
|
||||
|
||||
b[0] = '{'
|
||||
b[size-1] = '}'
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// Float64Array represents a one-dimensional array of the PostgreSQL double
|
||||
// precision type.
|
||||
type Float64Array []float64
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a *Float64Array) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src))
|
||||
}
|
||||
|
||||
return fmt.Errorf("pq: cannot convert %T to Float64Array", src)
|
||||
}
|
||||
|
||||
func (a *Float64Array) scanBytes(src []byte) error {
|
||||
elems, err := scanLinearArray(src, []byte{','}, "Float64Array")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(elems) == 0 {
|
||||
*a = (*a)[:0]
|
||||
} else {
|
||||
b := make(Float64Array, len(elems))
|
||||
for i, v := range elems {
|
||||
if b[i], err = strconv.ParseFloat(string(v), 64); err != nil {
|
||||
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a Float64Array) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if n := len(a); n > 0 {
|
||||
// There will be at least two curly brackets, N bytes of values,
|
||||
// and N-1 bytes of delimiters.
|
||||
b := make([]byte, 1, 1+2*n)
|
||||
b[0] = '{'
|
||||
|
||||
b = strconv.AppendFloat(b, a[0], 'f', -1, 64)
|
||||
for i := 1; i < n; i++ {
|
||||
b = append(b, ',')
|
||||
b = strconv.AppendFloat(b, a[i], 'f', -1, 64)
|
||||
}
|
||||
|
||||
return string(append(b, '}')), nil
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// GenericArray implements the driver.Valuer and sql.Scanner interfaces for
|
||||
// an array or slice of any dimension.
|
||||
type GenericArray struct{ A interface{} }
|
||||
|
||||
func (GenericArray) evaluateDestination(rt reflect.Type) (reflect.Type, func([]byte, reflect.Value) error, string) {
|
||||
var assign func([]byte, reflect.Value) error
|
||||
var del = ","
|
||||
|
||||
// TODO calculate the assign function for other types
|
||||
// TODO repeat this section on the element type of arrays or slices (multidimensional)
|
||||
{
|
||||
if reflect.PtrTo(rt).Implements(typeSqlScanner) {
|
||||
// dest is always addressable because it is an element of a slice.
|
||||
assign = func(src []byte, dest reflect.Value) (err error) {
|
||||
ss := dest.Addr().Interface().(sql.Scanner)
|
||||
if src == nil {
|
||||
err = ss.Scan(nil)
|
||||
} else {
|
||||
err = ss.Scan(src)
|
||||
}
|
||||
return
|
||||
}
|
||||
goto FoundType
|
||||
}
|
||||
|
||||
assign = func([]byte, reflect.Value) error {
|
||||
return fmt.Errorf("pq: scanning to %s is not implemented; only sql.Scanner", rt)
|
||||
}
|
||||
}
|
||||
|
||||
FoundType:
|
||||
|
||||
if ad, ok := reflect.Zero(rt).Interface().(ArrayDelimiter); ok {
|
||||
del = ad.ArrayDelimiter()
|
||||
}
|
||||
|
||||
return rt, assign, del
|
||||
}
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a GenericArray) Scan(src interface{}) error {
|
||||
dpv := reflect.ValueOf(a.A)
|
||||
switch {
|
||||
case dpv.Kind() != reflect.Ptr:
|
||||
return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A)
|
||||
case dpv.IsNil():
|
||||
return fmt.Errorf("pq: destination %T is nil", a.A)
|
||||
}
|
||||
|
||||
dv := dpv.Elem()
|
||||
switch dv.Kind() {
|
||||
case reflect.Slice:
|
||||
case reflect.Array:
|
||||
default:
|
||||
return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src, dv)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src), dv)
|
||||
}
|
||||
|
||||
return fmt.Errorf("pq: cannot convert %T to %s", src, dv.Type())
|
||||
}
|
||||
|
||||
func (a GenericArray) scanBytes(src []byte, dv reflect.Value) error {
|
||||
dtype, assign, del := a.evaluateDestination(dv.Type().Elem())
|
||||
dims, elems, err := parseArray(src, []byte(del))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO allow multidimensional
|
||||
|
||||
if len(dims) > 1 {
|
||||
return fmt.Errorf("pq: scanning from multidimensional ARRAY%s is not implemented",
|
||||
strings.Replace(fmt.Sprint(dims), " ", "][", -1))
|
||||
}
|
||||
|
||||
// Treat a zero-dimensional array like an array with a single dimension of zero.
|
||||
if len(dims) == 0 {
|
||||
dims = append(dims, 0)
|
||||
}
|
||||
|
||||
for i, rt := 0, dv.Type(); i < len(dims); i, rt = i+1, rt.Elem() {
|
||||
switch rt.Kind() {
|
||||
case reflect.Slice:
|
||||
case reflect.Array:
|
||||
if rt.Len() != dims[i] {
|
||||
return fmt.Errorf("pq: cannot convert ARRAY%s to %s",
|
||||
strings.Replace(fmt.Sprint(dims), " ", "][", -1), dv.Type())
|
||||
}
|
||||
default:
|
||||
// TODO handle multidimensional
|
||||
}
|
||||
}
|
||||
|
||||
values := reflect.MakeSlice(reflect.SliceOf(dtype), len(elems), len(elems))
|
||||
for i, e := range elems {
|
||||
if err := assign(e, values.Index(i)); err != nil {
|
||||
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO handle multidimensional
|
||||
|
||||
switch dv.Kind() {
|
||||
case reflect.Slice:
|
||||
dv.Set(values.Slice(0, dims[0]))
|
||||
case reflect.Array:
|
||||
for i := 0; i < dims[0]; i++ {
|
||||
dv.Index(i).Set(values.Index(i))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a GenericArray) Value() (driver.Value, error) {
|
||||
if a.A == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(a.A)
|
||||
|
||||
if k := rv.Kind(); k != reflect.Array && k != reflect.Slice {
|
||||
return nil, fmt.Errorf("pq: Unable to convert %T to array", a.A)
|
||||
}
|
||||
|
||||
if n := rv.Len(); n > 0 {
|
||||
// There will be at least two curly brackets, N bytes of values,
|
||||
// and N-1 bytes of delimiters.
|
||||
b := make([]byte, 0, 1+2*n)
|
||||
|
||||
b, _, err := appendArray(b, rv, n)
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// Int64Array represents a one-dimensional array of the PostgreSQL integer types.
|
||||
type Int64Array []int64
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a *Int64Array) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src))
|
||||
}
|
||||
|
||||
return fmt.Errorf("pq: cannot convert %T to Int64Array", src)
|
||||
}
|
||||
|
||||
func (a *Int64Array) scanBytes(src []byte) error {
|
||||
elems, err := scanLinearArray(src, []byte{','}, "Int64Array")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(elems) == 0 {
|
||||
*a = (*a)[:0]
|
||||
} else {
|
||||
b := make(Int64Array, len(elems))
|
||||
for i, v := range elems {
|
||||
if b[i], err = strconv.ParseInt(string(v), 10, 64); err != nil {
|
||||
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a Int64Array) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if n := len(a); n > 0 {
|
||||
// There will be at least two curly brackets, N bytes of values,
|
||||
// and N-1 bytes of delimiters.
|
||||
b := make([]byte, 1, 1+2*n)
|
||||
b[0] = '{'
|
||||
|
||||
b = strconv.AppendInt(b, a[0], 10)
|
||||
for i := 1; i < n; i++ {
|
||||
b = append(b, ',')
|
||||
b = strconv.AppendInt(b, a[i], 10)
|
||||
}
|
||||
|
||||
return string(append(b, '}')), nil
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// StringArray represents a one-dimensional array of the PostgreSQL character types.
|
||||
type StringArray []string
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a *StringArray) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src))
|
||||
}
|
||||
|
||||
return fmt.Errorf("pq: cannot convert %T to StringArray", src)
|
||||
}
|
||||
|
||||
func (a *StringArray) scanBytes(src []byte) error {
|
||||
elems, err := scanLinearArray(src, []byte{','}, "StringArray")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(elems) == 0 {
|
||||
*a = (*a)[:0]
|
||||
} else {
|
||||
b := make(StringArray, len(elems))
|
||||
for i, v := range elems {
|
||||
if b[i] = string(v); v == nil {
|
||||
return fmt.Errorf("pq: parsing array element index %d: cannot convert nil to string", i)
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a StringArray) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if n := len(a); n > 0 {
|
||||
// There will be at least two curly brackets, 2*N bytes of quotes,
|
||||
// and N-1 bytes of delimiters.
|
||||
b := make([]byte, 1, 1+3*n)
|
||||
b[0] = '{'
|
||||
|
||||
b = appendArrayQuotedBytes(b, []byte(a[0]))
|
||||
for i := 1; i < n; i++ {
|
||||
b = append(b, ',')
|
||||
b = appendArrayQuotedBytes(b, []byte(a[i]))
|
||||
}
|
||||
|
||||
return string(append(b, '}')), nil
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// appendArray appends rv to the buffer, returning the extended buffer and
|
||||
// the delimiter used between elements.
|
||||
//
|
||||
// It panics when n <= 0 or rv's Kind is not reflect.Array nor reflect.Slice.
|
||||
func appendArray(b []byte, rv reflect.Value, n int) ([]byte, string, error) {
|
||||
var del string
|
||||
var err error
|
||||
|
||||
b = append(b, '{')
|
||||
|
||||
if b, del, err = appendArrayElement(b, rv.Index(0)); err != nil {
|
||||
return b, del, err
|
||||
}
|
||||
|
||||
for i := 1; i < n; i++ {
|
||||
b = append(b, del...)
|
||||
if b, del, err = appendArrayElement(b, rv.Index(i)); err != nil {
|
||||
return b, del, err
|
||||
}
|
||||
}
|
||||
|
||||
return append(b, '}'), del, nil
|
||||
}
|
||||
|
||||
// appendArrayElement appends rv to the buffer, returning the extended buffer
|
||||
// and the delimiter to use before the next element.
|
||||
//
|
||||
// When rv's Kind is neither reflect.Array nor reflect.Slice, it is converted
|
||||
// using driver.DefaultParameterConverter and the resulting []byte or string
|
||||
// is double-quoted.
|
||||
//
|
||||
// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
||||
func appendArrayElement(b []byte, rv reflect.Value) ([]byte, string, error) {
|
||||
if k := rv.Kind(); k == reflect.Array || k == reflect.Slice {
|
||||
if t := rv.Type(); t != typeByteSlice && !t.Implements(typeDriverValuer) {
|
||||
if n := rv.Len(); n > 0 {
|
||||
return appendArray(b, rv, n)
|
||||
}
|
||||
|
||||
return b, "", nil
|
||||
}
|
||||
}
|
||||
|
||||
var del string = ","
|
||||
var err error
|
||||
var iv interface{} = rv.Interface()
|
||||
|
||||
if ad, ok := iv.(ArrayDelimiter); ok {
|
||||
del = ad.ArrayDelimiter()
|
||||
}
|
||||
|
||||
if iv, err = driver.DefaultParameterConverter.ConvertValue(iv); err != nil {
|
||||
return b, del, err
|
||||
}
|
||||
|
||||
switch v := iv.(type) {
|
||||
case nil:
|
||||
return append(b, "NULL"...), del, nil
|
||||
case []byte:
|
||||
return appendArrayQuotedBytes(b, v), del, nil
|
||||
case string:
|
||||
return appendArrayQuotedBytes(b, []byte(v)), del, nil
|
||||
}
|
||||
|
||||
b, err = appendValue(b, iv)
|
||||
return b, del, err
|
||||
}
|
||||
|
||||
func appendArrayQuotedBytes(b, v []byte) []byte {
|
||||
b = append(b, '"')
|
||||
for {
|
||||
i := bytes.IndexAny(v, `"\`)
|
||||
if i < 0 {
|
||||
b = append(b, v...)
|
||||
break
|
||||
}
|
||||
if i > 0 {
|
||||
b = append(b, v[:i]...)
|
||||
}
|
||||
b = append(b, '\\', v[i])
|
||||
v = v[i+1:]
|
||||
}
|
||||
return append(b, '"')
|
||||
}
|
||||
|
||||
func appendValue(b []byte, v driver.Value) ([]byte, error) {
|
||||
return append(b, encode(nil, v, 0)...), nil
|
||||
}
|
||||
|
||||
// parseArray extracts the dimensions and elements of an array represented in
|
||||
// text format. Only representations emitted by the backend are supported.
|
||||
// Notably, whitespace around brackets and delimiters is significant, and NULL
|
||||
// is case-sensitive.
|
||||
//
|
||||
// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
||||
func parseArray(src, del []byte) (dims []int, elems [][]byte, err error) {
|
||||
var depth, i int
|
||||
|
||||
if len(src) < 1 || src[0] != '{' {
|
||||
return nil, nil, fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '{', 0)
|
||||
}
|
||||
|
||||
Open:
|
||||
for i < len(src) {
|
||||
switch src[i] {
|
||||
case '{':
|
||||
depth++
|
||||
i++
|
||||
case '}':
|
||||
elems = make([][]byte, 0)
|
||||
goto Close
|
||||
default:
|
||||
break Open
|
||||
}
|
||||
}
|
||||
dims = make([]int, i)
|
||||
|
||||
Element:
|
||||
for i < len(src) {
|
||||
switch src[i] {
|
||||
case '{':
|
||||
depth++
|
||||
dims[depth-1] = 0
|
||||
i++
|
||||
case '"':
|
||||
var elem = []byte{}
|
||||
var escape bool
|
||||
for i++; i < len(src); i++ {
|
||||
if escape {
|
||||
elem = append(elem, src[i])
|
||||
escape = false
|
||||
} else {
|
||||
switch src[i] {
|
||||
default:
|
||||
elem = append(elem, src[i])
|
||||
case '\\':
|
||||
escape = true
|
||||
case '"':
|
||||
elems = append(elems, elem)
|
||||
i++
|
||||
break Element
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
for start := i; i < len(src); i++ {
|
||||
if bytes.HasPrefix(src[i:], del) || src[i] == '}' {
|
||||
elem := src[start:i]
|
||||
if len(elem) == 0 {
|
||||
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||
}
|
||||
if bytes.Equal(elem, []byte("NULL")) {
|
||||
elem = nil
|
||||
}
|
||||
elems = append(elems, elem)
|
||||
break Element
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i < len(src) {
|
||||
if bytes.HasPrefix(src[i:], del) {
|
||||
dims[depth-1]++
|
||||
i += len(del)
|
||||
goto Element
|
||||
} else if src[i] == '}' {
|
||||
dims[depth-1]++
|
||||
depth--
|
||||
i++
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||
}
|
||||
}
|
||||
|
||||
Close:
|
||||
for i < len(src) {
|
||||
if src[i] == '}' && depth > 0 {
|
||||
depth--
|
||||
i++
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||
}
|
||||
}
|
||||
if depth > 0 {
|
||||
err = fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '}', i)
|
||||
}
|
||||
if err == nil {
|
||||
for _, d := range dims {
|
||||
if (len(elems) % d) != 0 {
|
||||
err = fmt.Errorf("pq: multidimensional arrays must have elements with matching dimensions")
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func scanLinearArray(src, del []byte, typ string) (elems [][]byte, err error) {
|
||||
dims, elems, err := parseArray(src, del)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(dims) > 1 {
|
||||
return nil, fmt.Errorf("pq: cannot convert ARRAY%s to %s", strings.Replace(fmt.Sprint(dims), " ", "][", -1), typ)
|
||||
}
|
||||
return elems, err
|
||||
}
|
91
vendor/github.com/lib/pq/buf.go
generated
vendored
Normal file
91
vendor/github.com/lib/pq/buf.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
package pq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/lib/pq/oid"
|
||||
)
|
||||
|
||||
type readBuf []byte
|
||||
|
||||
func (b *readBuf) int32() (n int) {
|
||||
n = int(int32(binary.BigEndian.Uint32(*b)))
|
||||
*b = (*b)[4:]
|
||||
return
|
||||
}
|
||||
|
||||
func (b *readBuf) oid() (n oid.Oid) {
|
||||
n = oid.Oid(binary.BigEndian.Uint32(*b))
|
||||
*b = (*b)[4:]
|
||||
return
|
||||
}
|
||||
|
||||
// N.B: this is actually an unsigned 16-bit integer, unlike int32
|
||||
func (b *readBuf) int16() (n int) {
|
||||
n = int(binary.BigEndian.Uint16(*b))
|
||||
*b = (*b)[2:]
|
||||
return
|
||||
}
|
||||
|
||||
func (b *readBuf) string() string {
|
||||
i := bytes.IndexByte(*b, 0)
|
||||
if i < 0 {
|
||||
errorf("invalid message format; expected string terminator")
|
||||
}
|
||||
s := (*b)[:i]
|
||||
*b = (*b)[i+1:]
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (b *readBuf) next(n int) (v []byte) {
|
||||
v = (*b)[:n]
|
||||
*b = (*b)[n:]
|
||||
return
|
||||
}
|
||||
|
||||
func (b *readBuf) byte() byte {
|
||||
return b.next(1)[0]
|
||||
}
|
||||
|
||||
type writeBuf struct {
|
||||
buf []byte
|
||||
pos int
|
||||
}
|
||||
|
||||
func (b *writeBuf) int32(n int) {
|
||||
x := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(x, uint32(n))
|
||||
b.buf = append(b.buf, x...)
|
||||
}
|
||||
|
||||
func (b *writeBuf) int16(n int) {
|
||||
x := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(x, uint16(n))
|
||||
b.buf = append(b.buf, x...)
|
||||
}
|
||||
|
||||
func (b *writeBuf) string(s string) {
|
||||
b.buf = append(b.buf, (s + "\000")...)
|
||||
}
|
||||
|
||||
func (b *writeBuf) byte(c byte) {
|
||||
b.buf = append(b.buf, c)
|
||||
}
|
||||
|
||||
func (b *writeBuf) bytes(v []byte) {
|
||||
b.buf = append(b.buf, v...)
|
||||
}
|
||||
|
||||
func (b *writeBuf) wrap() []byte {
|
||||
p := b.buf[b.pos:]
|
||||
binary.BigEndian.PutUint32(p, uint32(len(p)))
|
||||
return b.buf
|
||||
}
|
||||
|
||||
func (b *writeBuf) next(c byte) {
|
||||
p := b.buf[b.pos:]
|
||||
binary.BigEndian.PutUint32(p, uint32(len(p)))
|
||||
b.pos = len(b.buf) + 1
|
||||
b.buf = append(b.buf, c, 0, 0, 0, 0)
|
||||
}
|
1862
vendor/github.com/lib/pq/conn.go
generated
vendored
Normal file
1862
vendor/github.com/lib/pq/conn.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
267
vendor/github.com/lib/pq/copy.go
generated
vendored
Normal file
267
vendor/github.com/lib/pq/copy.go
generated
vendored
Normal file
@ -0,0 +1,267 @@
|
||||
package pq
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
errCopyInClosed = errors.New("pq: copyin statement has already been closed")
|
||||
errBinaryCopyNotSupported = errors.New("pq: only text format supported for COPY")
|
||||
errCopyToNotSupported = errors.New("pq: COPY TO is not supported")
|
||||
errCopyNotSupportedOutsideTxn = errors.New("pq: COPY is only allowed inside a transaction")
|
||||
)
|
||||
|
||||
// CopyIn creates a COPY FROM statement which can be prepared with
|
||||
// Tx.Prepare(). The target table should be visible in search_path.
|
||||
func CopyIn(table string, columns ...string) string {
|
||||
stmt := "COPY " + QuoteIdentifier(table) + " ("
|
||||
for i, col := range columns {
|
||||
if i != 0 {
|
||||
stmt += ", "
|
||||
}
|
||||
stmt += QuoteIdentifier(col)
|
||||
}
|
||||
stmt += ") FROM STDIN"
|
||||
return stmt
|
||||
}
|
||||
|
||||
// CopyInSchema creates a COPY FROM statement which can be prepared with
|
||||
// Tx.Prepare().
|
||||
func CopyInSchema(schema, table string, columns ...string) string {
|
||||
stmt := "COPY " + QuoteIdentifier(schema) + "." + QuoteIdentifier(table) + " ("
|
||||
for i, col := range columns {
|
||||
if i != 0 {
|
||||
stmt += ", "
|
||||
}
|
||||
stmt += QuoteIdentifier(col)
|
||||
}
|
||||
stmt += ") FROM STDIN"
|
||||
return stmt
|
||||
}
|
||||
|
||||
type copyin struct {
|
||||
cn *conn
|
||||
buffer []byte
|
||||
rowData chan []byte
|
||||
done chan bool
|
||||
|
||||
closed bool
|
||||
|
||||
sync.Mutex // guards err
|
||||
err error
|
||||
}
|
||||
|
||||
const ciBufferSize = 64 * 1024
|
||||
|
||||
// flush buffer before the buffer is filled up and needs reallocation
|
||||
const ciBufferFlushSize = 63 * 1024
|
||||
|
||||
func (cn *conn) prepareCopyIn(q string) (_ driver.Stmt, err error) {
|
||||
if !cn.isInTransaction() {
|
||||
return nil, errCopyNotSupportedOutsideTxn
|
||||
}
|
||||
|
||||
ci := ©in{
|
||||
cn: cn,
|
||||
buffer: make([]byte, 0, ciBufferSize),
|
||||
rowData: make(chan []byte),
|
||||
done: make(chan bool, 1),
|
||||
}
|
||||
// add CopyData identifier + 4 bytes for message length
|
||||
ci.buffer = append(ci.buffer, 'd', 0, 0, 0, 0)
|
||||
|
||||
b := cn.writeBuf('Q')
|
||||
b.string(q)
|
||||
cn.send(b)
|
||||
|
||||
awaitCopyInResponse:
|
||||
for {
|
||||
t, r := cn.recv1()
|
||||
switch t {
|
||||
case 'G':
|
||||
if r.byte() != 0 {
|
||||
err = errBinaryCopyNotSupported
|
||||
break awaitCopyInResponse
|
||||
}
|
||||
go ci.resploop()
|
||||
return ci, nil
|
||||
case 'H':
|
||||
err = errCopyToNotSupported
|
||||
break awaitCopyInResponse
|
||||
case 'E':
|
||||
err = parseError(r)
|
||||
case 'Z':
|
||||
if err == nil {
|
||||
cn.bad = true
|
||||
errorf("unexpected ReadyForQuery in response to COPY")
|
||||
}
|
||||
cn.processReadyForQuery(r)
|
||||
return nil, err
|
||||
default:
|
||||
cn.bad = true
|
||||
errorf("unknown response for copy query: %q", t)
|
||||
}
|
||||
}
|
||||
|
||||
// something went wrong, abort COPY before we return
|
||||
b = cn.writeBuf('f')
|
||||
b.string(err.Error())
|
||||
cn.send(b)
|
||||
|
||||
for {
|
||||
t, r := cn.recv1()
|
||||
switch t {
|
||||
case 'c', 'C', 'E':
|
||||
case 'Z':
|
||||
// correctly aborted, we're done
|
||||
cn.processReadyForQuery(r)
|
||||
return nil, err
|
||||
default:
|
||||
cn.bad = true
|
||||
errorf("unknown response for CopyFail: %q", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ci *copyin) flush(buf []byte) {
|
||||
// set message length (without message identifier)
|
||||
binary.BigEndian.PutUint32(buf[1:], uint32(len(buf)-1))
|
||||
|
||||
_, err := ci.cn.c.Write(buf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (ci *copyin) resploop() {
|
||||
for {
|
||||
var r readBuf
|
||||
t, err := ci.cn.recvMessage(&r)
|
||||
if err != nil {
|
||||
ci.cn.bad = true
|
||||
ci.setError(err)
|
||||
ci.done <- true
|
||||
return
|
||||
}
|
||||
switch t {
|
||||
case 'C':
|
||||
// complete
|
||||
case 'N':
|
||||
// NoticeResponse
|
||||
case 'Z':
|
||||
ci.cn.processReadyForQuery(&r)
|
||||
ci.done <- true
|
||||
return
|
||||
case 'E':
|
||||
err := parseError(&r)
|
||||
ci.setError(err)
|
||||
default:
|
||||
ci.cn.bad = true
|
||||
ci.setError(fmt.Errorf("unknown response during CopyIn: %q", t))
|
||||
ci.done <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ci *copyin) isErrorSet() bool {
|
||||
ci.Lock()
|
||||
isSet := (ci.err != nil)
|
||||
ci.Unlock()
|
||||
return isSet
|
||||
}
|
||||
|
||||
// setError() sets ci.err if one has not been set already. Caller must not be
|
||||
// holding ci.Mutex.
|
||||
func (ci *copyin) setError(err error) {
|
||||
ci.Lock()
|
||||
if ci.err == nil {
|
||||
ci.err = err
|
||||
}
|
||||
ci.Unlock()
|
||||
}
|
||||
|
||||
func (ci *copyin) NumInput() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (ci *copyin) Query(v []driver.Value) (r driver.Rows, err error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
// Exec inserts values into the COPY stream. The insert is asynchronous
|
||||
// and Exec can return errors from previous Exec calls to the same
|
||||
// COPY stmt.
|
||||
//
|
||||
// You need to call Exec(nil) to sync the COPY stream and to get any
|
||||
// errors from pending data, since Stmt.Close() doesn't return errors
|
||||
// to the user.
|
||||
func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) {
|
||||
if ci.closed {
|
||||
return nil, errCopyInClosed
|
||||
}
|
||||
|
||||
if ci.cn.bad {
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
defer ci.cn.errRecover(&err)
|
||||
|
||||
if ci.isErrorSet() {
|
||||
return nil, ci.err
|
||||
}
|
||||
|
||||
if len(v) == 0 {
|
||||
return nil, ci.Close()
|
||||
}
|
||||
|
||||
numValues := len(v)
|
||||
for i, value := range v {
|
||||
ci.buffer = appendEncodedText(&ci.cn.parameterStatus, ci.buffer, value)
|
||||
if i < numValues-1 {
|
||||
ci.buffer = append(ci.buffer, '\t')
|
||||
}
|
||||
}
|
||||
|
||||
ci.buffer = append(ci.buffer, '\n')
|
||||
|
||||
if len(ci.buffer) > ciBufferFlushSize {
|
||||
ci.flush(ci.buffer)
|
||||
// reset buffer, keep bytes for message identifier and length
|
||||
ci.buffer = ci.buffer[:5]
|
||||
}
|
||||
|
||||
return driver.RowsAffected(0), nil
|
||||
}
|
||||
|
||||
func (ci *copyin) Close() (err error) {
|
||||
if ci.closed { // Don't do anything, we're already closed
|
||||
return nil
|
||||
}
|
||||
ci.closed = true
|
||||
|
||||
if ci.cn.bad {
|
||||
return driver.ErrBadConn
|
||||
}
|
||||
defer ci.cn.errRecover(&err)
|
||||
|
||||
if len(ci.buffer) > 0 {
|
||||
ci.flush(ci.buffer)
|
||||
}
|
||||
// Avoid touching the scratch buffer as resploop could be using it.
|
||||
err = ci.cn.sendSimpleMessage('c')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
<-ci.done
|
||||
|
||||
if ci.isErrorSet() {
|
||||
err = ci.err
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
212
vendor/github.com/lib/pq/doc.go
generated
vendored
Normal file
212
vendor/github.com/lib/pq/doc.go
generated
vendored
Normal file
@ -0,0 +1,212 @@
|
||||
/*
|
||||
Package pq is a pure Go Postgres driver for the database/sql package.
|
||||
|
||||
In most cases clients will use the database/sql package instead of
|
||||
using this package directly. For example:
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
func main() {
|
||||
db, err := sql.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=verify-full")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
age := 21
|
||||
rows, err := db.Query("SELECT name FROM users WHERE age = $1", age)
|
||||
…
|
||||
}
|
||||
|
||||
You can also connect to a database using a URL. For example:
|
||||
|
||||
db, err := sql.Open("postgres", "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full")
|
||||
|
||||
|
||||
Connection String Parameters
|
||||
|
||||
|
||||
Similarly to libpq, when establishing a connection using pq you are expected to
|
||||
supply a connection string containing zero or more parameters.
|
||||
A subset of the connection parameters supported by libpq are also supported by pq.
|
||||
Additionally, pq also lets you specify run-time parameters (such as search_path or work_mem)
|
||||
directly in the connection string. This is different from libpq, which does not allow
|
||||
run-time parameters in the connection string, instead requiring you to supply
|
||||
them in the options parameter.
|
||||
|
||||
For compatibility with libpq, the following special connection parameters are
|
||||
supported:
|
||||
|
||||
* dbname - The name of the database to connect to
|
||||
* user - The user to sign in as
|
||||
* password - The user's password
|
||||
* host - The host to connect to. Values that start with / are for unix domain sockets. (default is localhost)
|
||||
* port - The port to bind to. (default is 5432)
|
||||
* sslmode - Whether or not to use SSL (default is require, this is not the default for libpq)
|
||||
* fallback_application_name - An application_name to fall back to if one isn't provided.
|
||||
* connect_timeout - Maximum wait for connection, in seconds. Zero or not specified means wait indefinitely.
|
||||
* sslcert - Cert file location. The file must contain PEM encoded data.
|
||||
* sslkey - Key file location. The file must contain PEM encoded data.
|
||||
* sslrootcert - The location of the root certificate file. The file must contain PEM encoded data.
|
||||
|
||||
Valid values for sslmode are:
|
||||
|
||||
* disable - No SSL
|
||||
* require - Always SSL (skip verification)
|
||||
* verify-ca - Always SSL (verify that the certificate presented by the server was signed by a trusted CA)
|
||||
* verify-full - Always SSL (verify that the certification presented by the server was signed by a trusted CA and the server host name matches the one in the certificate)
|
||||
|
||||
See http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
|
||||
for more information about connection string parameters.
|
||||
|
||||
Use single quotes for values that contain whitespace:
|
||||
|
||||
"user=pqgotest password='with spaces'"
|
||||
|
||||
A backslash will escape the next character in values:
|
||||
|
||||
"user=space\ man password='it\'s valid'
|
||||
|
||||
Note that the connection parameter client_encoding (which sets the
|
||||
text encoding for the connection) may be set but must be "UTF8",
|
||||
matching with the same rules as Postgres. It is an error to provide
|
||||
any other value.
|
||||
|
||||
In addition to the parameters listed above, any run-time parameter that can be
|
||||
set at backend start time can be set in the connection string. For more
|
||||
information, see
|
||||
http://www.postgresql.org/docs/current/static/runtime-config.html.
|
||||
|
||||
Most environment variables as specified at http://www.postgresql.org/docs/current/static/libpq-envars.html
|
||||
supported by libpq are also supported by pq. If any of the environment
|
||||
variables not supported by pq are set, pq will panic during connection
|
||||
establishment. Environment variables have a lower precedence than explicitly
|
||||
provided connection parameters.
|
||||
|
||||
The pgpass mechanism as described in http://www.postgresql.org/docs/current/static/libpq-pgpass.html
|
||||
is supported, but on Windows PGPASSFILE must be specified explicitly.
|
||||
|
||||
Queries
|
||||
|
||||
database/sql does not dictate any specific format for parameter
|
||||
markers in query strings, and pq uses the Postgres-native ordinal markers,
|
||||
as shown above. The same marker can be reused for the same parameter:
|
||||
|
||||
rows, err := db.Query(`SELECT name FROM users WHERE favorite_fruit = $1
|
||||
OR age BETWEEN $2 AND $2 + 3`, "orange", 64)
|
||||
|
||||
pq does not support the LastInsertId() method of the Result type in database/sql.
|
||||
To return the identifier of an INSERT (or UPDATE or DELETE), use the Postgres
|
||||
RETURNING clause with a standard Query or QueryRow call:
|
||||
|
||||
var userid int
|
||||
err := db.QueryRow(`INSERT INTO users(name, favorite_fruit, age)
|
||||
VALUES('beatrice', 'starfruit', 93) RETURNING id`).Scan(&userid)
|
||||
|
||||
For more details on RETURNING, see the Postgres documentation:
|
||||
|
||||
http://www.postgresql.org/docs/current/static/sql-insert.html
|
||||
http://www.postgresql.org/docs/current/static/sql-update.html
|
||||
http://www.postgresql.org/docs/current/static/sql-delete.html
|
||||
|
||||
For additional instructions on querying see the documentation for the database/sql package.
|
||||
|
||||
Errors
|
||||
|
||||
pq may return errors of type *pq.Error which can be interrogated for error details:
|
||||
|
||||
if err, ok := err.(*pq.Error); ok {
|
||||
fmt.Println("pq error:", err.Code.Name())
|
||||
}
|
||||
|
||||
See the pq.Error type for details.
|
||||
|
||||
|
||||
Bulk imports
|
||||
|
||||
You can perform bulk imports by preparing a statement returned by pq.CopyIn (or
|
||||
pq.CopyInSchema) in an explicit transaction (sql.Tx). The returned statement
|
||||
handle can then be repeatedly "executed" to copy data into the target table.
|
||||
After all data has been processed you should call Exec() once with no arguments
|
||||
to flush all buffered data. Any call to Exec() might return an error which
|
||||
should be handled appropriately, but because of the internal buffering an error
|
||||
returned by Exec() might not be related to the data passed in the call that
|
||||
failed.
|
||||
|
||||
CopyIn uses COPY FROM internally. It is not possible to COPY outside of an
|
||||
explicit transaction in pq.
|
||||
|
||||
Usage example:
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, err := txn.Prepare(pq.CopyIn("users", "name", "age"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
_, err = stmt.Exec(user.Name, int64(user.Age))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = txn.Commit()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
|
||||
Notifications
|
||||
|
||||
|
||||
PostgreSQL supports a simple publish/subscribe model over database
|
||||
connections. See http://www.postgresql.org/docs/current/static/sql-notify.html
|
||||
for more information about the general mechanism.
|
||||
|
||||
To start listening for notifications, you first have to open a new connection
|
||||
to the database by calling NewListener. This connection can not be used for
|
||||
anything other than LISTEN / NOTIFY. Calling Listen will open a "notification
|
||||
channel"; once a notification channel is open, a notification generated on that
|
||||
channel will effect a send on the Listener.Notify channel. A notification
|
||||
channel will remain open until Unlisten is called, though connection loss might
|
||||
result in some notifications being lost. To solve this problem, Listener sends
|
||||
a nil pointer over the Notify channel any time the connection is re-established
|
||||
following a connection loss. The application can get information about the
|
||||
state of the underlying connection by setting an event callback in the call to
|
||||
NewListener.
|
||||
|
||||
A single Listener can safely be used from concurrent goroutines, which means
|
||||
that there is often no need to create more than one Listener in your
|
||||
application. However, a Listener is always connected to a single database, so
|
||||
you will need to create a new Listener instance for every database you want to
|
||||
receive notifications in.
|
||||
|
||||
The channel name in both Listen and Unlisten is case sensitive, and can contain
|
||||
any characters legal in an identifier (see
|
||||
http://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||
for more information). Note that the channel name will be truncated to 63
|
||||
bytes by the PostgreSQL server.
|
||||
|
||||
You can find a complete, working example of Listener usage at
|
||||
http://godoc.org/github.com/lib/pq/listen_example.
|
||||
|
||||
*/
|
||||
package pq
|
589
vendor/github.com/lib/pq/encode.go
generated
vendored
Normal file
589
vendor/github.com/lib/pq/encode.go
generated
vendored
Normal file
@ -0,0 +1,589 @@
|
||||
package pq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq/oid"
|
||||
)
|
||||
|
||||
func binaryEncode(parameterStatus *parameterStatus, x interface{}) []byte {
|
||||
switch v := x.(type) {
|
||||
case []byte:
|
||||
return v
|
||||
default:
|
||||
return encode(parameterStatus, x, oid.T_unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func encode(parameterStatus *parameterStatus, x interface{}, pgtypOid oid.Oid) []byte {
|
||||
switch v := x.(type) {
|
||||
case int64:
|
||||
return strconv.AppendInt(nil, v, 10)
|
||||
case float64:
|
||||
return strconv.AppendFloat(nil, v, 'f', -1, 64)
|
||||
case []byte:
|
||||
if pgtypOid == oid.T_bytea {
|
||||
return encodeBytea(parameterStatus.serverVersion, v)
|
||||
}
|
||||
|
||||
return v
|
||||
case string:
|
||||
if pgtypOid == oid.T_bytea {
|
||||
return encodeBytea(parameterStatus.serverVersion, []byte(v))
|
||||
}
|
||||
|
||||
return []byte(v)
|
||||
case bool:
|
||||
return strconv.AppendBool(nil, v)
|
||||
case time.Time:
|
||||
return formatTs(v)
|
||||
|
||||
default:
|
||||
errorf("encode: unknown type for %T", v)
|
||||
}
|
||||
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func decode(parameterStatus *parameterStatus, s []byte, typ oid.Oid, f format) interface{} {
|
||||
switch f {
|
||||
case formatBinary:
|
||||
return binaryDecode(parameterStatus, s, typ)
|
||||
case formatText:
|
||||
return textDecode(parameterStatus, s, typ)
|
||||
default:
|
||||
panic("not reached")
|
||||
}
|
||||
}
|
||||
|
||||
func binaryDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} {
|
||||
switch typ {
|
||||
case oid.T_bytea:
|
||||
return s
|
||||
case oid.T_int8:
|
||||
return int64(binary.BigEndian.Uint64(s))
|
||||
case oid.T_int4:
|
||||
return int64(int32(binary.BigEndian.Uint32(s)))
|
||||
case oid.T_int2:
|
||||
return int64(int16(binary.BigEndian.Uint16(s)))
|
||||
|
||||
default:
|
||||
errorf("don't know how to decode binary parameter of type %d", uint32(typ))
|
||||
}
|
||||
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func textDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} {
|
||||
switch typ {
|
||||
case oid.T_char, oid.T_varchar, oid.T_text:
|
||||
return string(s)
|
||||
case oid.T_bytea:
|
||||
b, err := parseBytea(s)
|
||||
if err != nil {
|
||||
errorf("%s", err)
|
||||
}
|
||||
return b
|
||||
case oid.T_timestamptz:
|
||||
return parseTs(parameterStatus.currentLocation, string(s))
|
||||
case oid.T_timestamp, oid.T_date:
|
||||
return parseTs(nil, string(s))
|
||||
case oid.T_time:
|
||||
return mustParse("15:04:05", typ, s)
|
||||
case oid.T_timetz:
|
||||
return mustParse("15:04:05-07", typ, s)
|
||||
case oid.T_bool:
|
||||
return s[0] == 't'
|
||||
case oid.T_int8, oid.T_int4, oid.T_int2:
|
||||
i, err := strconv.ParseInt(string(s), 10, 64)
|
||||
if err != nil {
|
||||
errorf("%s", err)
|
||||
}
|
||||
return i
|
||||
case oid.T_float4, oid.T_float8:
|
||||
bits := 64
|
||||
if typ == oid.T_float4 {
|
||||
bits = 32
|
||||
}
|
||||
f, err := strconv.ParseFloat(string(s), bits)
|
||||
if err != nil {
|
||||
errorf("%s", err)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// appendEncodedText encodes item in text format as required by COPY
|
||||
// and appends to buf
|
||||
func appendEncodedText(parameterStatus *parameterStatus, buf []byte, x interface{}) []byte {
|
||||
switch v := x.(type) {
|
||||
case int64:
|
||||
return strconv.AppendInt(buf, v, 10)
|
||||
case float64:
|
||||
return strconv.AppendFloat(buf, v, 'f', -1, 64)
|
||||
case []byte:
|
||||
encodedBytea := encodeBytea(parameterStatus.serverVersion, v)
|
||||
return appendEscapedText(buf, string(encodedBytea))
|
||||
case string:
|
||||
return appendEscapedText(buf, v)
|
||||
case bool:
|
||||
return strconv.AppendBool(buf, v)
|
||||
case time.Time:
|
||||
return append(buf, formatTs(v)...)
|
||||
case nil:
|
||||
return append(buf, "\\N"...)
|
||||
default:
|
||||
errorf("encode: unknown type for %T", v)
|
||||
}
|
||||
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func appendEscapedText(buf []byte, text string) []byte {
|
||||
escapeNeeded := false
|
||||
startPos := 0
|
||||
var c byte
|
||||
|
||||
// check if we need to escape
|
||||
for i := 0; i < len(text); i++ {
|
||||
c = text[i]
|
||||
if c == '\\' || c == '\n' || c == '\r' || c == '\t' {
|
||||
escapeNeeded = true
|
||||
startPos = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if !escapeNeeded {
|
||||
return append(buf, text...)
|
||||
}
|
||||
|
||||
// copy till first char to escape, iterate the rest
|
||||
result := append(buf, text[:startPos]...)
|
||||
for i := startPos; i < len(text); i++ {
|
||||
c = text[i]
|
||||
switch c {
|
||||
case '\\':
|
||||
result = append(result, '\\', '\\')
|
||||
case '\n':
|
||||
result = append(result, '\\', 'n')
|
||||
case '\r':
|
||||
result = append(result, '\\', 'r')
|
||||
case '\t':
|
||||
result = append(result, '\\', 't')
|
||||
default:
|
||||
result = append(result, c)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func mustParse(f string, typ oid.Oid, s []byte) time.Time {
|
||||
str := string(s)
|
||||
|
||||
// check for a 30-minute-offset timezone
|
||||
if (typ == oid.T_timestamptz || typ == oid.T_timetz) &&
|
||||
str[len(str)-3] == ':' {
|
||||
f += ":00"
|
||||
}
|
||||
t, err := time.Parse(f, str)
|
||||
if err != nil {
|
||||
errorf("decode: %s", err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
var errInvalidTimestamp = errors.New("invalid timestamp")
|
||||
|
||||
type timestampParser struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (p *timestampParser) expect(str string, char byte, pos int) {
|
||||
if p.err != nil {
|
||||
return
|
||||
}
|
||||
if pos+1 > len(str) {
|
||||
p.err = errInvalidTimestamp
|
||||
return
|
||||
}
|
||||
if c := str[pos]; c != char && p.err == nil {
|
||||
p.err = fmt.Errorf("expected '%v' at position %v; got '%v'", char, pos, c)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *timestampParser) mustAtoi(str string, begin int, end int) int {
|
||||
if p.err != nil {
|
||||
return 0
|
||||
}
|
||||
if begin < 0 || end < 0 || begin > end || end > len(str) {
|
||||
p.err = errInvalidTimestamp
|
||||
return 0
|
||||
}
|
||||
result, err := strconv.Atoi(str[begin:end])
|
||||
if err != nil {
|
||||
if p.err == nil {
|
||||
p.err = fmt.Errorf("expected number; got '%v'", str)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// The location cache caches the time zones typically used by the client.
|
||||
type locationCache struct {
|
||||
cache map[int]*time.Location
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// All connections share the same list of timezones. Benchmarking shows that
|
||||
// about 5% speed could be gained by putting the cache in the connection and
|
||||
// losing the mutex, at the cost of a small amount of memory and a somewhat
|
||||
// significant increase in code complexity.
|
||||
var globalLocationCache = newLocationCache()
|
||||
|
||||
func newLocationCache() *locationCache {
|
||||
return &locationCache{cache: make(map[int]*time.Location)}
|
||||
}
|
||||
|
||||
// Returns the cached timezone for the specified offset, creating and caching
|
||||
// it if necessary.
|
||||
func (c *locationCache) getLocation(offset int) *time.Location {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
location, ok := c.cache[offset]
|
||||
if !ok {
|
||||
location = time.FixedZone("", offset)
|
||||
c.cache[offset] = location
|
||||
}
|
||||
|
||||
return location
|
||||
}
|
||||
|
||||
var infinityTsEnabled = false
|
||||
var infinityTsNegative time.Time
|
||||
var infinityTsPositive time.Time
|
||||
|
||||
const (
|
||||
infinityTsEnabledAlready = "pq: infinity timestamp enabled already"
|
||||
infinityTsNegativeMustBeSmaller = "pq: infinity timestamp: negative value must be smaller (before) than positive"
|
||||
)
|
||||
|
||||
// EnableInfinityTs controls the handling of Postgres' "-infinity" and
|
||||
// "infinity" "timestamp"s.
|
||||
//
|
||||
// If EnableInfinityTs is not called, "-infinity" and "infinity" will return
|
||||
// []byte("-infinity") and []byte("infinity") respectively, and potentially
|
||||
// cause error "sql: Scan error on column index 0: unsupported driver -> Scan
|
||||
// pair: []uint8 -> *time.Time", when scanning into a time.Time value.
|
||||
//
|
||||
// Once EnableInfinityTs has been called, all connections created using this
|
||||
// driver will decode Postgres' "-infinity" and "infinity" for "timestamp",
|
||||
// "timestamp with time zone" and "date" types to the predefined minimum and
|
||||
// maximum times, respectively. When encoding time.Time values, any time which
|
||||
// equals or precedes the predefined minimum time will be encoded to
|
||||
// "-infinity". Any values at or past the maximum time will similarly be
|
||||
// encoded to "infinity".
|
||||
//
|
||||
// If EnableInfinityTs is called with negative >= positive, it will panic.
|
||||
// Calling EnableInfinityTs after a connection has been established results in
|
||||
// undefined behavior. If EnableInfinityTs is called more than once, it will
|
||||
// panic.
|
||||
func EnableInfinityTs(negative time.Time, positive time.Time) {
|
||||
if infinityTsEnabled {
|
||||
panic(infinityTsEnabledAlready)
|
||||
}
|
||||
if !negative.Before(positive) {
|
||||
panic(infinityTsNegativeMustBeSmaller)
|
||||
}
|
||||
infinityTsEnabled = true
|
||||
infinityTsNegative = negative
|
||||
infinityTsPositive = positive
|
||||
}
|
||||
|
||||
/*
|
||||
* Testing might want to toggle infinityTsEnabled
|
||||
*/
|
||||
func disableInfinityTs() {
|
||||
infinityTsEnabled = false
|
||||
}
|
||||
|
||||
// This is a time function specific to the Postgres default DateStyle
|
||||
// setting ("ISO, MDY"), the only one we currently support. This
|
||||
// accounts for the discrepancies between the parsing available with
|
||||
// time.Parse and the Postgres date formatting quirks.
|
||||
func parseTs(currentLocation *time.Location, str string) interface{} {
|
||||
switch str {
|
||||
case "-infinity":
|
||||
if infinityTsEnabled {
|
||||
return infinityTsNegative
|
||||
}
|
||||
return []byte(str)
|
||||
case "infinity":
|
||||
if infinityTsEnabled {
|
||||
return infinityTsPositive
|
||||
}
|
||||
return []byte(str)
|
||||
}
|
||||
t, err := ParseTimestamp(currentLocation, str)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ParseTimestamp parses Postgres' text format. It returns a time.Time in
|
||||
// currentLocation iff that time's offset agrees with the offset sent from the
|
||||
// Postgres server. Otherwise, ParseTimestamp returns a time.Time with the
|
||||
// fixed offset offset provided by the Postgres server.
|
||||
func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, error) {
|
||||
p := timestampParser{}
|
||||
|
||||
monSep := strings.IndexRune(str, '-')
|
||||
// this is Gregorian year, not ISO Year
|
||||
// In Gregorian system, the year 1 BC is followed by AD 1
|
||||
year := p.mustAtoi(str, 0, monSep)
|
||||
daySep := monSep + 3
|
||||
month := p.mustAtoi(str, monSep+1, daySep)
|
||||
p.expect(str, '-', daySep)
|
||||
timeSep := daySep + 3
|
||||
day := p.mustAtoi(str, daySep+1, timeSep)
|
||||
|
||||
var hour, minute, second int
|
||||
if len(str) > monSep+len("01-01")+1 {
|
||||
p.expect(str, ' ', timeSep)
|
||||
minSep := timeSep + 3
|
||||
p.expect(str, ':', minSep)
|
||||
hour = p.mustAtoi(str, timeSep+1, minSep)
|
||||
secSep := minSep + 3
|
||||
p.expect(str, ':', secSep)
|
||||
minute = p.mustAtoi(str, minSep+1, secSep)
|
||||
secEnd := secSep + 3
|
||||
second = p.mustAtoi(str, secSep+1, secEnd)
|
||||
}
|
||||
remainderIdx := monSep + len("01-01 00:00:00") + 1
|
||||
// Three optional (but ordered) sections follow: the
|
||||
// fractional seconds, the time zone offset, and the BC
|
||||
// designation. We set them up here and adjust the other
|
||||
// offsets if the preceding sections exist.
|
||||
|
||||
nanoSec := 0
|
||||
tzOff := 0
|
||||
|
||||
if remainderIdx < len(str) && str[remainderIdx] == '.' {
|
||||
fracStart := remainderIdx + 1
|
||||
fracOff := strings.IndexAny(str[fracStart:], "-+ ")
|
||||
if fracOff < 0 {
|
||||
fracOff = len(str) - fracStart
|
||||
}
|
||||
fracSec := p.mustAtoi(str, fracStart, fracStart+fracOff)
|
||||
nanoSec = fracSec * (1000000000 / int(math.Pow(10, float64(fracOff))))
|
||||
|
||||
remainderIdx += fracOff + 1
|
||||
}
|
||||
if tzStart := remainderIdx; tzStart < len(str) && (str[tzStart] == '-' || str[tzStart] == '+') {
|
||||
// time zone separator is always '-' or '+' (UTC is +00)
|
||||
var tzSign int
|
||||
switch c := str[tzStart]; c {
|
||||
case '-':
|
||||
tzSign = -1
|
||||
case '+':
|
||||
tzSign = +1
|
||||
default:
|
||||
return time.Time{}, fmt.Errorf("expected '-' or '+' at position %v; got %v", tzStart, c)
|
||||
}
|
||||
tzHours := p.mustAtoi(str, tzStart+1, tzStart+3)
|
||||
remainderIdx += 3
|
||||
var tzMin, tzSec int
|
||||
if remainderIdx < len(str) && str[remainderIdx] == ':' {
|
||||
tzMin = p.mustAtoi(str, remainderIdx+1, remainderIdx+3)
|
||||
remainderIdx += 3
|
||||
}
|
||||
if remainderIdx < len(str) && str[remainderIdx] == ':' {
|
||||
tzSec = p.mustAtoi(str, remainderIdx+1, remainderIdx+3)
|
||||
remainderIdx += 3
|
||||
}
|
||||
tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec)
|
||||
}
|
||||
var isoYear int
|
||||
if remainderIdx+3 <= len(str) && str[remainderIdx:remainderIdx+3] == " BC" {
|
||||
isoYear = 1 - year
|
||||
remainderIdx += 3
|
||||
} else {
|
||||
isoYear = year
|
||||
}
|
||||
if remainderIdx < len(str) {
|
||||
return time.Time{}, fmt.Errorf("expected end of input, got %v", str[remainderIdx:])
|
||||
}
|
||||
t := time.Date(isoYear, time.Month(month), day,
|
||||
hour, minute, second, nanoSec,
|
||||
globalLocationCache.getLocation(tzOff))
|
||||
|
||||
if currentLocation != nil {
|
||||
// Set the location of the returned Time based on the session's
|
||||
// TimeZone value, but only if the local time zone database agrees with
|
||||
// the remote database on the offset.
|
||||
lt := t.In(currentLocation)
|
||||
_, newOff := lt.Zone()
|
||||
if newOff == tzOff {
|
||||
t = lt
|
||||
}
|
||||
}
|
||||
|
||||
return t, p.err
|
||||
}
|
||||
|
||||
// formatTs formats t into a format postgres understands.
|
||||
func formatTs(t time.Time) []byte {
|
||||
if infinityTsEnabled {
|
||||
// t <= -infinity : ! (t > -infinity)
|
||||
if !t.After(infinityTsNegative) {
|
||||
return []byte("-infinity")
|
||||
}
|
||||
// t >= infinity : ! (!t < infinity)
|
||||
if !t.Before(infinityTsPositive) {
|
||||
return []byte("infinity")
|
||||
}
|
||||
}
|
||||
return FormatTimestamp(t)
|
||||
}
|
||||
|
||||
// FormatTimestamp formats t into Postgres' text format for timestamps.
|
||||
func FormatTimestamp(t time.Time) []byte {
|
||||
// Need to send dates before 0001 A.D. with " BC" suffix, instead of the
|
||||
// minus sign preferred by Go.
|
||||
// Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on
|
||||
bc := false
|
||||
if t.Year() <= 0 {
|
||||
// flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11"
|
||||
t = t.AddDate((-t.Year())*2+1, 0, 0)
|
||||
bc = true
|
||||
}
|
||||
b := []byte(t.Format(time.RFC3339Nano))
|
||||
|
||||
_, offset := t.Zone()
|
||||
offset = offset % 60
|
||||
if offset != 0 {
|
||||
// RFC3339Nano already printed the minus sign
|
||||
if offset < 0 {
|
||||
offset = -offset
|
||||
}
|
||||
|
||||
b = append(b, ':')
|
||||
if offset < 10 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
b = strconv.AppendInt(b, int64(offset), 10)
|
||||
}
|
||||
|
||||
if bc {
|
||||
b = append(b, " BC"...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Parse a bytea value received from the server. Both "hex" and the legacy
|
||||
// "escape" format are supported.
|
||||
func parseBytea(s []byte) (result []byte, err error) {
|
||||
if len(s) >= 2 && bytes.Equal(s[:2], []byte("\\x")) {
|
||||
// bytea_output = hex
|
||||
s = s[2:] // trim off leading "\\x"
|
||||
result = make([]byte, hex.DecodedLen(len(s)))
|
||||
_, err := hex.Decode(result, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// bytea_output = escape
|
||||
for len(s) > 0 {
|
||||
if s[0] == '\\' {
|
||||
// escaped '\\'
|
||||
if len(s) >= 2 && s[1] == '\\' {
|
||||
result = append(result, '\\')
|
||||
s = s[2:]
|
||||
continue
|
||||
}
|
||||
|
||||
// '\\' followed by an octal number
|
||||
if len(s) < 4 {
|
||||
return nil, fmt.Errorf("invalid bytea sequence %v", s)
|
||||
}
|
||||
r, err := strconv.ParseInt(string(s[1:4]), 8, 9)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse bytea value: %s", err.Error())
|
||||
}
|
||||
result = append(result, byte(r))
|
||||
s = s[4:]
|
||||
} else {
|
||||
// We hit an unescaped, raw byte. Try to read in as many as
|
||||
// possible in one go.
|
||||
i := bytes.IndexByte(s, '\\')
|
||||
if i == -1 {
|
||||
result = append(result, s...)
|
||||
break
|
||||
}
|
||||
result = append(result, s[:i]...)
|
||||
s = s[i:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func encodeBytea(serverVersion int, v []byte) (result []byte) {
|
||||
if serverVersion >= 90000 {
|
||||
// Use the hex format if we know that the server supports it
|
||||
result = make([]byte, 2+hex.EncodedLen(len(v)))
|
||||
result[0] = '\\'
|
||||
result[1] = 'x'
|
||||
hex.Encode(result[2:], v)
|
||||
} else {
|
||||
// .. or resort to "escape"
|
||||
for _, b := range v {
|
||||
if b == '\\' {
|
||||
result = append(result, '\\', '\\')
|
||||
} else if b < 0x20 || b > 0x7e {
|
||||
result = append(result, []byte(fmt.Sprintf("\\%03o", b))...)
|
||||
} else {
|
||||
result = append(result, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// NullTime represents a time.Time that may be null. NullTime implements the
|
||||
// sql.Scanner interface so it can be used as a scan destination, similar to
|
||||
// sql.NullString.
|
||||
type NullTime struct {
|
||||
Time time.Time
|
||||
Valid bool // Valid is true if Time is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (nt *NullTime) Scan(value interface{}) error {
|
||||
nt.Time, nt.Valid = value.(time.Time)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nt NullTime) Value() (driver.Value, error) {
|
||||
if !nt.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return nt.Time, nil
|
||||
}
|
508
vendor/github.com/lib/pq/error.go
generated
vendored
Normal file
508
vendor/github.com/lib/pq/error.go
generated
vendored
Normal file
@ -0,0 +1,508 @@
|
||||
package pq
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Error severities
|
||||
const (
|
||||
Efatal = "FATAL"
|
||||
Epanic = "PANIC"
|
||||
Ewarning = "WARNING"
|
||||
Enotice = "NOTICE"
|
||||
Edebug = "DEBUG"
|
||||
Einfo = "INFO"
|
||||
Elog = "LOG"
|
||||
)
|
||||
|
||||
// Error represents an error communicating with the server.
|
||||
//
|
||||
// See http://www.postgresql.org/docs/current/static/protocol-error-fields.html for details of the fields
|
||||
type Error struct {
|
||||
Severity string
|
||||
Code ErrorCode
|
||||
Message string
|
||||
Detail string
|
||||
Hint string
|
||||
Position string
|
||||
InternalPosition string
|
||||
InternalQuery string
|
||||
Where string
|
||||
Schema string
|
||||
Table string
|
||||
Column string
|
||||
DataTypeName string
|
||||
Constraint string
|
||||
File string
|
||||
Line string
|
||||
Routine string
|
||||
}
|
||||
|
||||
// ErrorCode is a five-character error code.
|
||||
type ErrorCode string
|
||||
|
||||
// Name returns a more human friendly rendering of the error code, namely the
|
||||
// "condition name".
|
||||
//
|
||||
// See http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html for
|
||||
// details.
|
||||
func (ec ErrorCode) Name() string {
|
||||
return errorCodeNames[ec]
|
||||
}
|
||||
|
||||
// ErrorClass is only the class part of an error code.
|
||||
type ErrorClass string
|
||||
|
||||
// Name returns the condition name of an error class. It is equivalent to the
|
||||
// condition name of the "standard" error code (i.e. the one having the last
|
||||
// three characters "000").
|
||||
func (ec ErrorClass) Name() string {
|
||||
return errorCodeNames[ErrorCode(ec+"000")]
|
||||
}
|
||||
|
||||
// Class returns the error class, e.g. "28".
|
||||
//
|
||||
// See http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html for
|
||||
// details.
|
||||
func (ec ErrorCode) Class() ErrorClass {
|
||||
return ErrorClass(ec[0:2])
|
||||
}
|
||||
|
||||
// errorCodeNames is a mapping between the five-character error codes and the
|
||||
// human readable "condition names". It is derived from the list at
|
||||
// http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html
|
||||
var errorCodeNames = map[ErrorCode]string{
|
||||
// Class 00 - Successful Completion
|
||||
"00000": "successful_completion",
|
||||
// Class 01 - Warning
|
||||
"01000": "warning",
|
||||
"0100C": "dynamic_result_sets_returned",
|
||||
"01008": "implicit_zero_bit_padding",
|
||||
"01003": "null_value_eliminated_in_set_function",
|
||||
"01007": "privilege_not_granted",
|
||||
"01006": "privilege_not_revoked",
|
||||
"01004": "string_data_right_truncation",
|
||||
"01P01": "deprecated_feature",
|
||||
// Class 02 - No Data (this is also a warning class per the SQL standard)
|
||||
"02000": "no_data",
|
||||
"02001": "no_additional_dynamic_result_sets_returned",
|
||||
// Class 03 - SQL Statement Not Yet Complete
|
||||
"03000": "sql_statement_not_yet_complete",
|
||||
// Class 08 - Connection Exception
|
||||
"08000": "connection_exception",
|
||||
"08003": "connection_does_not_exist",
|
||||
"08006": "connection_failure",
|
||||
"08001": "sqlclient_unable_to_establish_sqlconnection",
|
||||
"08004": "sqlserver_rejected_establishment_of_sqlconnection",
|
||||
"08007": "transaction_resolution_unknown",
|
||||
"08P01": "protocol_violation",
|
||||
// Class 09 - Triggered Action Exception
|
||||
"09000": "triggered_action_exception",
|
||||
// Class 0A - Feature Not Supported
|
||||
"0A000": "feature_not_supported",
|
||||
// Class 0B - Invalid Transaction Initiation
|
||||
"0B000": "invalid_transaction_initiation",
|
||||
// Class 0F - Locator Exception
|
||||
"0F000": "locator_exception",
|
||||
"0F001": "invalid_locator_specification",
|
||||
// Class 0L - Invalid Grantor
|
||||
"0L000": "invalid_grantor",
|
||||
"0LP01": "invalid_grant_operation",
|
||||
// Class 0P - Invalid Role Specification
|
||||
"0P000": "invalid_role_specification",
|
||||
// Class 0Z - Diagnostics Exception
|
||||
"0Z000": "diagnostics_exception",
|
||||
"0Z002": "stacked_diagnostics_accessed_without_active_handler",
|
||||
// Class 20 - Case Not Found
|
||||
"20000": "case_not_found",
|
||||
// Class 21 - Cardinality Violation
|
||||
"21000": "cardinality_violation",
|
||||
// Class 22 - Data Exception
|
||||
"22000": "data_exception",
|
||||
"2202E": "array_subscript_error",
|
||||
"22021": "character_not_in_repertoire",
|
||||
"22008": "datetime_field_overflow",
|
||||
"22012": "division_by_zero",
|
||||
"22005": "error_in_assignment",
|
||||
"2200B": "escape_character_conflict",
|
||||
"22022": "indicator_overflow",
|
||||
"22015": "interval_field_overflow",
|
||||
"2201E": "invalid_argument_for_logarithm",
|
||||
"22014": "invalid_argument_for_ntile_function",
|
||||
"22016": "invalid_argument_for_nth_value_function",
|
||||
"2201F": "invalid_argument_for_power_function",
|
||||
"2201G": "invalid_argument_for_width_bucket_function",
|
||||
"22018": "invalid_character_value_for_cast",
|
||||
"22007": "invalid_datetime_format",
|
||||
"22019": "invalid_escape_character",
|
||||
"2200D": "invalid_escape_octet",
|
||||
"22025": "invalid_escape_sequence",
|
||||
"22P06": "nonstandard_use_of_escape_character",
|
||||
"22010": "invalid_indicator_parameter_value",
|
||||
"22023": "invalid_parameter_value",
|
||||
"2201B": "invalid_regular_expression",
|
||||
"2201W": "invalid_row_count_in_limit_clause",
|
||||
"2201X": "invalid_row_count_in_result_offset_clause",
|
||||
"22009": "invalid_time_zone_displacement_value",
|
||||
"2200C": "invalid_use_of_escape_character",
|
||||
"2200G": "most_specific_type_mismatch",
|
||||
"22004": "null_value_not_allowed",
|
||||
"22002": "null_value_no_indicator_parameter",
|
||||
"22003": "numeric_value_out_of_range",
|
||||
"22026": "string_data_length_mismatch",
|
||||
"22001": "string_data_right_truncation",
|
||||
"22011": "substring_error",
|
||||
"22027": "trim_error",
|
||||
"22024": "unterminated_c_string",
|
||||
"2200F": "zero_length_character_string",
|
||||
"22P01": "floating_point_exception",
|
||||
"22P02": "invalid_text_representation",
|
||||
"22P03": "invalid_binary_representation",
|
||||
"22P04": "bad_copy_file_format",
|
||||
"22P05": "untranslatable_character",
|
||||
"2200L": "not_an_xml_document",
|
||||
"2200M": "invalid_xml_document",
|
||||
"2200N": "invalid_xml_content",
|
||||
"2200S": "invalid_xml_comment",
|
||||
"2200T": "invalid_xml_processing_instruction",
|
||||
// Class 23 - Integrity Constraint Violation
|
||||
"23000": "integrity_constraint_violation",
|
||||
"23001": "restrict_violation",
|
||||
"23502": "not_null_violation",
|
||||
"23503": "foreign_key_violation",
|
||||
"23505": "unique_violation",
|
||||
"23514": "check_violation",
|
||||
"23P01": "exclusion_violation",
|
||||
// Class 24 - Invalid Cursor State
|
||||
"24000": "invalid_cursor_state",
|
||||
// Class 25 - Invalid Transaction State
|
||||
"25000": "invalid_transaction_state",
|
||||
"25001": "active_sql_transaction",
|
||||
"25002": "branch_transaction_already_active",
|
||||
"25008": "held_cursor_requires_same_isolation_level",
|
||||
"25003": "inappropriate_access_mode_for_branch_transaction",
|
||||
"25004": "inappropriate_isolation_level_for_branch_transaction",
|
||||
"25005": "no_active_sql_transaction_for_branch_transaction",
|
||||
"25006": "read_only_sql_transaction",
|
||||
"25007": "schema_and_data_statement_mixing_not_supported",
|
||||
"25P01": "no_active_sql_transaction",
|
||||
"25P02": "in_failed_sql_transaction",
|
||||
// Class 26 - Invalid SQL Statement Name
|
||||
"26000": "invalid_sql_statement_name",
|
||||
// Class 27 - Triggered Data Change Violation
|
||||
"27000": "triggered_data_change_violation",
|
||||
// Class 28 - Invalid Authorization Specification
|
||||
"28000": "invalid_authorization_specification",
|
||||
"28P01": "invalid_password",
|
||||
// Class 2B - Dependent Privilege Descriptors Still Exist
|
||||
"2B000": "dependent_privilege_descriptors_still_exist",
|
||||
"2BP01": "dependent_objects_still_exist",
|
||||
// Class 2D - Invalid Transaction Termination
|
||||
"2D000": "invalid_transaction_termination",
|
||||
// Class 2F - SQL Routine Exception
|
||||
"2F000": "sql_routine_exception",
|
||||
"2F005": "function_executed_no_return_statement",
|
||||
"2F002": "modifying_sql_data_not_permitted",
|
||||
"2F003": "prohibited_sql_statement_attempted",
|
||||
"2F004": "reading_sql_data_not_permitted",
|
||||
// Class 34 - Invalid Cursor Name
|
||||
"34000": "invalid_cursor_name",
|
||||
// Class 38 - External Routine Exception
|
||||
"38000": "external_routine_exception",
|
||||
"38001": "containing_sql_not_permitted",
|
||||
"38002": "modifying_sql_data_not_permitted",
|
||||
"38003": "prohibited_sql_statement_attempted",
|
||||
"38004": "reading_sql_data_not_permitted",
|
||||
// Class 39 - External Routine Invocation Exception
|
||||
"39000": "external_routine_invocation_exception",
|
||||
"39001": "invalid_sqlstate_returned",
|
||||
"39004": "null_value_not_allowed",
|
||||
"39P01": "trigger_protocol_violated",
|
||||
"39P02": "srf_protocol_violated",
|
||||
// Class 3B - Savepoint Exception
|
||||
"3B000": "savepoint_exception",
|
||||
"3B001": "invalid_savepoint_specification",
|
||||
// Class 3D - Invalid Catalog Name
|
||||
"3D000": "invalid_catalog_name",
|
||||
// Class 3F - Invalid Schema Name
|
||||
"3F000": "invalid_schema_name",
|
||||
// Class 40 - Transaction Rollback
|
||||
"40000": "transaction_rollback",
|
||||
"40002": "transaction_integrity_constraint_violation",
|
||||
"40001": "serialization_failure",
|
||||
"40003": "statement_completion_unknown",
|
||||
"40P01": "deadlock_detected",
|
||||
// Class 42 - Syntax Error or Access Rule Violation
|
||||
"42000": "syntax_error_or_access_rule_violation",
|
||||
"42601": "syntax_error",
|
||||
"42501": "insufficient_privilege",
|
||||
"42846": "cannot_coerce",
|
||||
"42803": "grouping_error",
|
||||
"42P20": "windowing_error",
|
||||
"42P19": "invalid_recursion",
|
||||
"42830": "invalid_foreign_key",
|
||||
"42602": "invalid_name",
|
||||
"42622": "name_too_long",
|
||||
"42939": "reserved_name",
|
||||
"42804": "datatype_mismatch",
|
||||
"42P18": "indeterminate_datatype",
|
||||
"42P21": "collation_mismatch",
|
||||
"42P22": "indeterminate_collation",
|
||||
"42809": "wrong_object_type",
|
||||
"42703": "undefined_column",
|
||||
"42883": "undefined_function",
|
||||
"42P01": "undefined_table",
|
||||
"42P02": "undefined_parameter",
|
||||
"42704": "undefined_object",
|
||||
"42701": "duplicate_column",
|
||||
"42P03": "duplicate_cursor",
|
||||
"42P04": "duplicate_database",
|
||||
"42723": "duplicate_function",
|
||||
"42P05": "duplicate_prepared_statement",
|
||||
"42P06": "duplicate_schema",
|
||||
"42P07": "duplicate_table",
|
||||
"42712": "duplicate_alias",
|
||||
"42710": "duplicate_object",
|
||||
"42702": "ambiguous_column",
|
||||
"42725": "ambiguous_function",
|
||||
"42P08": "ambiguous_parameter",
|
||||
"42P09": "ambiguous_alias",
|
||||
"42P10": "invalid_column_reference",
|
||||
"42611": "invalid_column_definition",
|
||||
"42P11": "invalid_cursor_definition",
|
||||
"42P12": "invalid_database_definition",
|
||||
"42P13": "invalid_function_definition",
|
||||
"42P14": "invalid_prepared_statement_definition",
|
||||
"42P15": "invalid_schema_definition",
|
||||
"42P16": "invalid_table_definition",
|
||||
"42P17": "invalid_object_definition",
|
||||
// Class 44 - WITH CHECK OPTION Violation
|
||||
"44000": "with_check_option_violation",
|
||||
// Class 53 - Insufficient Resources
|
||||
"53000": "insufficient_resources",
|
||||
"53100": "disk_full",
|
||||
"53200": "out_of_memory",
|
||||
"53300": "too_many_connections",
|
||||
"53400": "configuration_limit_exceeded",
|
||||
// Class 54 - Program Limit Exceeded
|
||||
"54000": "program_limit_exceeded",
|
||||
"54001": "statement_too_complex",
|
||||
"54011": "too_many_columns",
|
||||
"54023": "too_many_arguments",
|
||||
// Class 55 - Object Not In Prerequisite State
|
||||
"55000": "object_not_in_prerequisite_state",
|
||||
"55006": "object_in_use",
|
||||
"55P02": "cant_change_runtime_param",
|
||||
"55P03": "lock_not_available",
|
||||
// Class 57 - Operator Intervention
|
||||
"57000": "operator_intervention",
|
||||
"57014": "query_canceled",
|
||||
"57P01": "admin_shutdown",
|
||||
"57P02": "crash_shutdown",
|
||||
"57P03": "cannot_connect_now",
|
||||
"57P04": "database_dropped",
|
||||
// Class 58 - System Error (errors external to PostgreSQL itself)
|
||||
"58000": "system_error",
|
||||
"58030": "io_error",
|
||||
"58P01": "undefined_file",
|
||||
"58P02": "duplicate_file",
|
||||
// Class F0 - Configuration File Error
|
||||
"F0000": "config_file_error",
|
||||
"F0001": "lock_file_exists",
|
||||
// Class HV - Foreign Data Wrapper Error (SQL/MED)
|
||||
"HV000": "fdw_error",
|
||||
"HV005": "fdw_column_name_not_found",
|
||||
"HV002": "fdw_dynamic_parameter_value_needed",
|
||||
"HV010": "fdw_function_sequence_error",
|
||||
"HV021": "fdw_inconsistent_descriptor_information",
|
||||
"HV024": "fdw_invalid_attribute_value",
|
||||
"HV007": "fdw_invalid_column_name",
|
||||
"HV008": "fdw_invalid_column_number",
|
||||
"HV004": "fdw_invalid_data_type",
|
||||
"HV006": "fdw_invalid_data_type_descriptors",
|
||||
"HV091": "fdw_invalid_descriptor_field_identifier",
|
||||
"HV00B": "fdw_invalid_handle",
|
||||
"HV00C": "fdw_invalid_option_index",
|
||||
"HV00D": "fdw_invalid_option_name",
|
||||
"HV090": "fdw_invalid_string_length_or_buffer_length",
|
||||
"HV00A": "fdw_invalid_string_format",
|
||||
"HV009": "fdw_invalid_use_of_null_pointer",
|
||||
"HV014": "fdw_too_many_handles",
|
||||
"HV001": "fdw_out_of_memory",
|
||||
"HV00P": "fdw_no_schemas",
|
||||
"HV00J": "fdw_option_name_not_found",
|
||||
"HV00K": "fdw_reply_handle",
|
||||
"HV00Q": "fdw_schema_not_found",
|
||||
"HV00R": "fdw_table_not_found",
|
||||
"HV00L": "fdw_unable_to_create_execution",
|
||||
"HV00M": "fdw_unable_to_create_reply",
|
||||
"HV00N": "fdw_unable_to_establish_connection",
|
||||
// Class P0 - PL/pgSQL Error
|
||||
"P0000": "plpgsql_error",
|
||||
"P0001": "raise_exception",
|
||||
"P0002": "no_data_found",
|
||||
"P0003": "too_many_rows",
|
||||
// Class XX - Internal Error
|
||||
"XX000": "internal_error",
|
||||
"XX001": "data_corrupted",
|
||||
"XX002": "index_corrupted",
|
||||
}
|
||||
|
||||
func parseError(r *readBuf) *Error {
|
||||
err := new(Error)
|
||||
for t := r.byte(); t != 0; t = r.byte() {
|
||||
msg := r.string()
|
||||
switch t {
|
||||
case 'S':
|
||||
err.Severity = msg
|
||||
case 'C':
|
||||
err.Code = ErrorCode(msg)
|
||||
case 'M':
|
||||
err.Message = msg
|
||||
case 'D':
|
||||
err.Detail = msg
|
||||
case 'H':
|
||||
err.Hint = msg
|
||||
case 'P':
|
||||
err.Position = msg
|
||||
case 'p':
|
||||
err.InternalPosition = msg
|
||||
case 'q':
|
||||
err.InternalQuery = msg
|
||||
case 'W':
|
||||
err.Where = msg
|
||||
case 's':
|
||||
err.Schema = msg
|
||||
case 't':
|
||||
err.Table = msg
|
||||
case 'c':
|
||||
err.Column = msg
|
||||
case 'd':
|
||||
err.DataTypeName = msg
|
||||
case 'n':
|
||||
err.Constraint = msg
|
||||
case 'F':
|
||||
err.File = msg
|
||||
case 'L':
|
||||
err.Line = msg
|
||||
case 'R':
|
||||
err.Routine = msg
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Fatal returns true if the Error Severity is fatal.
|
||||
func (err *Error) Fatal() bool {
|
||||
return err.Severity == Efatal
|
||||
}
|
||||
|
||||
// Get implements the legacy PGError interface. New code should use the fields
|
||||
// of the Error struct directly.
|
||||
func (err *Error) Get(k byte) (v string) {
|
||||
switch k {
|
||||
case 'S':
|
||||
return err.Severity
|
||||
case 'C':
|
||||
return string(err.Code)
|
||||
case 'M':
|
||||
return err.Message
|
||||
case 'D':
|
||||
return err.Detail
|
||||
case 'H':
|
||||
return err.Hint
|
||||
case 'P':
|
||||
return err.Position
|
||||
case 'p':
|
||||
return err.InternalPosition
|
||||
case 'q':
|
||||
return err.InternalQuery
|
||||
case 'W':
|
||||
return err.Where
|
||||
case 's':
|
||||
return err.Schema
|
||||
case 't':
|
||||
return err.Table
|
||||
case 'c':
|
||||
return err.Column
|
||||
case 'd':
|
||||
return err.DataTypeName
|
||||
case 'n':
|
||||
return err.Constraint
|
||||
case 'F':
|
||||
return err.File
|
||||
case 'L':
|
||||
return err.Line
|
||||
case 'R':
|
||||
return err.Routine
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (err Error) Error() string {
|
||||
return "pq: " + err.Message
|
||||
}
|
||||
|
||||
// PGError is an interface used by previous versions of pq. It is provided
|
||||
// only to support legacy code. New code should use the Error type.
|
||||
type PGError interface {
|
||||
Error() string
|
||||
Fatal() bool
|
||||
Get(k byte) (v string)
|
||||
}
|
||||
|
||||
func errorf(s string, args ...interface{}) {
|
||||
panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...)))
|
||||
}
|
||||
|
||||
func errRecoverNoErrBadConn(err *error) {
|
||||
e := recover()
|
||||
if e == nil {
|
||||
// Do nothing
|
||||
return
|
||||
}
|
||||
var ok bool
|
||||
*err, ok = e.(error)
|
||||
if !ok {
|
||||
*err = fmt.Errorf("pq: unexpected error: %#v", e)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) errRecover(err *error) {
|
||||
e := recover()
|
||||
switch v := e.(type) {
|
||||
case nil:
|
||||
// Do nothing
|
||||
case runtime.Error:
|
||||
c.bad = true
|
||||
panic(v)
|
||||
case *Error:
|
||||
if v.Fatal() {
|
||||
*err = driver.ErrBadConn
|
||||
} else {
|
||||
*err = v
|
||||
}
|
||||
case *net.OpError:
|
||||
*err = driver.ErrBadConn
|
||||
case error:
|
||||
if v == io.EOF || v.(error).Error() == "remote error: handshake failure" {
|
||||
*err = driver.ErrBadConn
|
||||
} else {
|
||||
*err = v
|
||||
}
|
||||
|
||||
default:
|
||||
c.bad = true
|
||||
panic(fmt.Sprintf("unknown error: %#v", e))
|
||||
}
|
||||
|
||||
// Any time we return ErrBadConn, we need to remember it since *Tx doesn't
|
||||
// mark the connection bad in database/sql.
|
||||
if *err == driver.ErrBadConn {
|
||||
c.bad = true
|
||||
}
|
||||
}
|
782
vendor/github.com/lib/pq/notify.go
generated
vendored
Normal file
782
vendor/github.com/lib/pq/notify.go
generated
vendored
Normal file
@ -0,0 +1,782 @@
|
||||
package pq
|
||||
|
||||
// Package pq is a pure Go Postgres driver for the database/sql package.
|
||||
// This module contains support for Postgres LISTEN/NOTIFY.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Notification represents a single notification from the database.
|
||||
type Notification struct {
|
||||
// Process ID (PID) of the notifying postgres backend.
|
||||
BePid int
|
||||
// Name of the channel the notification was sent on.
|
||||
Channel string
|
||||
// Payload, or the empty string if unspecified.
|
||||
Extra string
|
||||
}
|
||||
|
||||
func recvNotification(r *readBuf) *Notification {
|
||||
bePid := r.int32()
|
||||
channel := r.string()
|
||||
extra := r.string()
|
||||
|
||||
return &Notification{bePid, channel, extra}
|
||||
}
|
||||
|
||||
const (
|
||||
connStateIdle int32 = iota
|
||||
connStateExpectResponse
|
||||
connStateExpectReadyForQuery
|
||||
)
|
||||
|
||||
type message struct {
|
||||
typ byte
|
||||
err error
|
||||
}
|
||||
|
||||
var errListenerConnClosed = errors.New("pq: ListenerConn has been closed")
|
||||
|
||||
// ListenerConn is a low-level interface for waiting for notifications. You
|
||||
// should use Listener instead.
|
||||
type ListenerConn struct {
|
||||
// guards cn and err
|
||||
connectionLock sync.Mutex
|
||||
cn *conn
|
||||
err error
|
||||
|
||||
connState int32
|
||||
|
||||
// the sending goroutine will be holding this lock
|
||||
senderLock sync.Mutex
|
||||
|
||||
notificationChan chan<- *Notification
|
||||
|
||||
replyChan chan message
|
||||
}
|
||||
|
||||
// Creates a new ListenerConn. Use NewListener instead.
|
||||
func NewListenerConn(name string, notificationChan chan<- *Notification) (*ListenerConn, error) {
|
||||
return newDialListenerConn(defaultDialer{}, name, notificationChan)
|
||||
}
|
||||
|
||||
func newDialListenerConn(d Dialer, name string, c chan<- *Notification) (*ListenerConn, error) {
|
||||
cn, err := DialOpen(d, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l := &ListenerConn{
|
||||
cn: cn.(*conn),
|
||||
notificationChan: c,
|
||||
connState: connStateIdle,
|
||||
replyChan: make(chan message, 2),
|
||||
}
|
||||
|
||||
go l.listenerConnMain()
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// We can only allow one goroutine at a time to be running a query on the
|
||||
// connection for various reasons, so the goroutine sending on the connection
|
||||
// must be holding senderLock.
|
||||
//
|
||||
// Returns an error if an unrecoverable error has occurred and the ListenerConn
|
||||
// should be abandoned.
|
||||
func (l *ListenerConn) acquireSenderLock() error {
|
||||
// we must acquire senderLock first to avoid deadlocks; see ExecSimpleQuery
|
||||
l.senderLock.Lock()
|
||||
|
||||
l.connectionLock.Lock()
|
||||
err := l.err
|
||||
l.connectionLock.Unlock()
|
||||
if err != nil {
|
||||
l.senderLock.Unlock()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *ListenerConn) releaseSenderLock() {
|
||||
l.senderLock.Unlock()
|
||||
}
|
||||
|
||||
// setState advances the protocol state to newState. Returns false if moving
|
||||
// to that state from the current state is not allowed.
|
||||
func (l *ListenerConn) setState(newState int32) bool {
|
||||
var expectedState int32
|
||||
|
||||
switch newState {
|
||||
case connStateIdle:
|
||||
expectedState = connStateExpectReadyForQuery
|
||||
case connStateExpectResponse:
|
||||
expectedState = connStateIdle
|
||||
case connStateExpectReadyForQuery:
|
||||
expectedState = connStateExpectResponse
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected listenerConnState %d", newState))
|
||||
}
|
||||
|
||||
return atomic.CompareAndSwapInt32(&l.connState, expectedState, newState)
|
||||
}
|
||||
|
||||
// Main logic is here: receive messages from the postgres backend, forward
|
||||
// notifications and query replies and keep the internal state in sync with the
|
||||
// protocol state. Returns when the connection has been lost, is about to go
|
||||
// away or should be discarded because we couldn't agree on the state with the
|
||||
// server backend.
|
||||
func (l *ListenerConn) listenerConnLoop() (err error) {
|
||||
defer errRecoverNoErrBadConn(&err)
|
||||
|
||||
r := &readBuf{}
|
||||
for {
|
||||
t, err := l.cn.recvMessage(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch t {
|
||||
case 'A':
|
||||
// recvNotification copies all the data so we don't need to worry
|
||||
// about the scratch buffer being overwritten.
|
||||
l.notificationChan <- recvNotification(r)
|
||||
|
||||
case 'T', 'D':
|
||||
// only used by tests; ignore
|
||||
|
||||
case 'E':
|
||||
// We might receive an ErrorResponse even when not in a query; it
|
||||
// is expected that the server will close the connection after
|
||||
// that, but we should make sure that the error we display is the
|
||||
// one from the stray ErrorResponse, not io.ErrUnexpectedEOF.
|
||||
if !l.setState(connStateExpectReadyForQuery) {
|
||||
return parseError(r)
|
||||
}
|
||||
l.replyChan <- message{t, parseError(r)}
|
||||
|
||||
case 'C', 'I':
|
||||
if !l.setState(connStateExpectReadyForQuery) {
|
||||
// protocol out of sync
|
||||
return fmt.Errorf("unexpected CommandComplete")
|
||||
}
|
||||
// ExecSimpleQuery doesn't need to know about this message
|
||||
|
||||
case 'Z':
|
||||
if !l.setState(connStateIdle) {
|
||||
// protocol out of sync
|
||||
return fmt.Errorf("unexpected ReadyForQuery")
|
||||
}
|
||||
l.replyChan <- message{t, nil}
|
||||
|
||||
case 'N', 'S':
|
||||
// ignore
|
||||
default:
|
||||
return fmt.Errorf("unexpected message %q from server in listenerConnLoop", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is the main routine for the goroutine receiving on the database
|
||||
// connection. Most of the main logic is in listenerConnLoop.
|
||||
func (l *ListenerConn) listenerConnMain() {
|
||||
err := l.listenerConnLoop()
|
||||
|
||||
// listenerConnLoop terminated; we're done, but we still have to clean up.
|
||||
// Make sure nobody tries to start any new queries by making sure the err
|
||||
// pointer is set. It is important that we do not overwrite its value; a
|
||||
// connection could be closed by either this goroutine or one sending on
|
||||
// the connection -- whoever closes the connection is assumed to have the
|
||||
// more meaningful error message (as the other one will probably get
|
||||
// net.errClosed), so that goroutine sets the error we expose while the
|
||||
// other error is discarded. If the connection is lost while two
|
||||
// goroutines are operating on the socket, it probably doesn't matter which
|
||||
// error we expose so we don't try to do anything more complex.
|
||||
l.connectionLock.Lock()
|
||||
if l.err == nil {
|
||||
l.err = err
|
||||
}
|
||||
l.cn.Close()
|
||||
l.connectionLock.Unlock()
|
||||
|
||||
// There might be a query in-flight; make sure nobody's waiting for a
|
||||
// response to it, since there's not going to be one.
|
||||
close(l.replyChan)
|
||||
|
||||
// let the listener know we're done
|
||||
close(l.notificationChan)
|
||||
|
||||
// this ListenerConn is done
|
||||
}
|
||||
|
||||
// Send a LISTEN query to the server. See ExecSimpleQuery.
|
||||
func (l *ListenerConn) Listen(channel string) (bool, error) {
|
||||
return l.ExecSimpleQuery("LISTEN " + QuoteIdentifier(channel))
|
||||
}
|
||||
|
||||
// Send an UNLISTEN query to the server. See ExecSimpleQuery.
|
||||
func (l *ListenerConn) Unlisten(channel string) (bool, error) {
|
||||
return l.ExecSimpleQuery("UNLISTEN " + QuoteIdentifier(channel))
|
||||
}
|
||||
|
||||
// Send `UNLISTEN *` to the server. See ExecSimpleQuery.
|
||||
func (l *ListenerConn) UnlistenAll() (bool, error) {
|
||||
return l.ExecSimpleQuery("UNLISTEN *")
|
||||
}
|
||||
|
||||
// Ping the remote server to make sure it's alive. Non-nil error means the
|
||||
// connection has failed and should be abandoned.
|
||||
func (l *ListenerConn) Ping() error {
|
||||
sent, err := l.ExecSimpleQuery("")
|
||||
if !sent {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
// shouldn't happen
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Attempt to send a query on the connection. Returns an error if sending the
|
||||
// query failed, and the caller should initiate closure of this connection.
|
||||
// The caller must be holding senderLock (see acquireSenderLock and
|
||||
// releaseSenderLock).
|
||||
func (l *ListenerConn) sendSimpleQuery(q string) (err error) {
|
||||
defer errRecoverNoErrBadConn(&err)
|
||||
|
||||
// must set connection state before sending the query
|
||||
if !l.setState(connStateExpectResponse) {
|
||||
panic("two queries running at the same time")
|
||||
}
|
||||
|
||||
// Can't use l.cn.writeBuf here because it uses the scratch buffer which
|
||||
// might get overwritten by listenerConnLoop.
|
||||
b := &writeBuf{
|
||||
buf: []byte("Q\x00\x00\x00\x00"),
|
||||
pos: 1,
|
||||
}
|
||||
b.string(q)
|
||||
l.cn.send(b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Execute a "simple query" (i.e. one with no bindable parameters) on the
|
||||
// connection. The possible return values are:
|
||||
// 1) "executed" is true; the query was executed to completion on the
|
||||
// database server. If the query failed, err will be set to the error
|
||||
// returned by the database, otherwise err will be nil.
|
||||
// 2) If "executed" is false, the query could not be executed on the remote
|
||||
// server. err will be non-nil.
|
||||
//
|
||||
// After a call to ExecSimpleQuery has returned an executed=false value, the
|
||||
// connection has either been closed or will be closed shortly thereafter, and
|
||||
// all subsequently executed queries will return an error.
|
||||
func (l *ListenerConn) ExecSimpleQuery(q string) (executed bool, err error) {
|
||||
if err = l.acquireSenderLock(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer l.releaseSenderLock()
|
||||
|
||||
err = l.sendSimpleQuery(q)
|
||||
if err != nil {
|
||||
// We can't know what state the protocol is in, so we need to abandon
|
||||
// this connection.
|
||||
l.connectionLock.Lock()
|
||||
// Set the error pointer if it hasn't been set already; see
|
||||
// listenerConnMain.
|
||||
if l.err == nil {
|
||||
l.err = err
|
||||
}
|
||||
l.connectionLock.Unlock()
|
||||
l.cn.c.Close()
|
||||
return false, err
|
||||
}
|
||||
|
||||
// now we just wait for a reply..
|
||||
for {
|
||||
m, ok := <-l.replyChan
|
||||
if !ok {
|
||||
// We lost the connection to server, don't bother waiting for a
|
||||
// a response. err should have been set already.
|
||||
l.connectionLock.Lock()
|
||||
err := l.err
|
||||
l.connectionLock.Unlock()
|
||||
return false, err
|
||||
}
|
||||
switch m.typ {
|
||||
case 'Z':
|
||||
// sanity check
|
||||
if m.err != nil {
|
||||
panic("m.err != nil")
|
||||
}
|
||||
// done; err might or might not be set
|
||||
return true, err
|
||||
|
||||
case 'E':
|
||||
// sanity check
|
||||
if m.err == nil {
|
||||
panic("m.err == nil")
|
||||
}
|
||||
// server responded with an error; ReadyForQuery to follow
|
||||
err = m.err
|
||||
|
||||
default:
|
||||
return false, fmt.Errorf("unknown response for simple query: %q", m.typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ListenerConn) Close() error {
|
||||
l.connectionLock.Lock()
|
||||
if l.err != nil {
|
||||
l.connectionLock.Unlock()
|
||||
return errListenerConnClosed
|
||||
}
|
||||
l.err = errListenerConnClosed
|
||||
l.connectionLock.Unlock()
|
||||
// We can't send anything on the connection without holding senderLock.
|
||||
// Simply close the net.Conn to wake up everyone operating on it.
|
||||
return l.cn.c.Close()
|
||||
}
|
||||
|
||||
// Err() returns the reason the connection was closed. It is not safe to call
|
||||
// this function until l.Notify has been closed.
|
||||
func (l *ListenerConn) Err() error {
|
||||
return l.err
|
||||
}
|
||||
|
||||
var errListenerClosed = errors.New("pq: Listener has been closed")
|
||||
|
||||
var ErrChannelAlreadyOpen = errors.New("pq: channel is already open")
|
||||
var ErrChannelNotOpen = errors.New("pq: channel is not open")
|
||||
|
||||
type ListenerEventType int
|
||||
|
||||
const (
|
||||
// Emitted only when the database connection has been initially
|
||||
// initialized. err will always be nil.
|
||||
ListenerEventConnected ListenerEventType = iota
|
||||
|
||||
// Emitted after a database connection has been lost, either because of an
|
||||
// error or because Close has been called. err will be set to the reason
|
||||
// the database connection was lost.
|
||||
ListenerEventDisconnected
|
||||
|
||||
// Emitted after a database connection has been re-established after
|
||||
// connection loss. err will always be nil. After this event has been
|
||||
// emitted, a nil pq.Notification is sent on the Listener.Notify channel.
|
||||
ListenerEventReconnected
|
||||
|
||||
// Emitted after a connection to the database was attempted, but failed.
|
||||
// err will be set to an error describing why the connection attempt did
|
||||
// not succeed.
|
||||
ListenerEventConnectionAttemptFailed
|
||||
)
|
||||
|
||||
type EventCallbackType func(event ListenerEventType, err error)
|
||||
|
||||
// Listener provides an interface for listening to notifications from a
|
||||
// PostgreSQL database. For general usage information, see section
|
||||
// "Notifications".
|
||||
//
|
||||
// Listener can safely be used from concurrently running goroutines.
|
||||
type Listener struct {
|
||||
// Channel for receiving notifications from the database. In some cases a
|
||||
// nil value will be sent. See section "Notifications" above.
|
||||
Notify chan *Notification
|
||||
|
||||
name string
|
||||
minReconnectInterval time.Duration
|
||||
maxReconnectInterval time.Duration
|
||||
dialer Dialer
|
||||
eventCallback EventCallbackType
|
||||
|
||||
lock sync.Mutex
|
||||
isClosed bool
|
||||
reconnectCond *sync.Cond
|
||||
cn *ListenerConn
|
||||
connNotificationChan <-chan *Notification
|
||||
channels map[string]struct{}
|
||||
}
|
||||
|
||||
// NewListener creates a new database connection dedicated to LISTEN / NOTIFY.
|
||||
//
|
||||
// name should be set to a connection string to be used to establish the
|
||||
// database connection (see section "Connection String Parameters" above).
|
||||
//
|
||||
// minReconnectInterval controls the duration to wait before trying to
|
||||
// re-establish the database connection after connection loss. After each
|
||||
// consecutive failure this interval is doubled, until maxReconnectInterval is
|
||||
// reached. Successfully completing the connection establishment procedure
|
||||
// resets the interval back to minReconnectInterval.
|
||||
//
|
||||
// The last parameter eventCallback can be set to a function which will be
|
||||
// called by the Listener when the state of the underlying database connection
|
||||
// changes. This callback will be called by the goroutine which dispatches the
|
||||
// notifications over the Notify channel, so you should try to avoid doing
|
||||
// potentially time-consuming operations from the callback.
|
||||
func NewListener(name string,
|
||||
minReconnectInterval time.Duration,
|
||||
maxReconnectInterval time.Duration,
|
||||
eventCallback EventCallbackType) *Listener {
|
||||
return NewDialListener(defaultDialer{}, name, minReconnectInterval, maxReconnectInterval, eventCallback)
|
||||
}
|
||||
|
||||
// NewDialListener is like NewListener but it takes a Dialer.
|
||||
func NewDialListener(d Dialer,
|
||||
name string,
|
||||
minReconnectInterval time.Duration,
|
||||
maxReconnectInterval time.Duration,
|
||||
eventCallback EventCallbackType) *Listener {
|
||||
|
||||
l := &Listener{
|
||||
name: name,
|
||||
minReconnectInterval: minReconnectInterval,
|
||||
maxReconnectInterval: maxReconnectInterval,
|
||||
dialer: d,
|
||||
eventCallback: eventCallback,
|
||||
|
||||
channels: make(map[string]struct{}),
|
||||
|
||||
Notify: make(chan *Notification, 32),
|
||||
}
|
||||
l.reconnectCond = sync.NewCond(&l.lock)
|
||||
|
||||
go l.listenerMain()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// Returns the notification channel for this listener. This is the same
|
||||
// channel as Notify, and will not be recreated during the life time of the
|
||||
// Listener.
|
||||
func (l *Listener) NotificationChannel() <-chan *Notification {
|
||||
return l.Notify
|
||||
}
|
||||
|
||||
// Listen starts listening for notifications on a channel. Calls to this
|
||||
// function will block until an acknowledgement has been received from the
|
||||
// server. Note that Listener automatically re-establishes the connection
|
||||
// after connection loss, so this function may block indefinitely if the
|
||||
// connection can not be re-established.
|
||||
//
|
||||
// Listen will only fail in three conditions:
|
||||
// 1) The channel is already open. The returned error will be
|
||||
// ErrChannelAlreadyOpen.
|
||||
// 2) The query was executed on the remote server, but PostgreSQL returned an
|
||||
// error message in response to the query. The returned error will be a
|
||||
// pq.Error containing the information the server supplied.
|
||||
// 3) Close is called on the Listener before the request could be completed.
|
||||
//
|
||||
// The channel name is case-sensitive.
|
||||
func (l *Listener) Listen(channel string) error {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
if l.isClosed {
|
||||
return errListenerClosed
|
||||
}
|
||||
|
||||
// The server allows you to issue a LISTEN on a channel which is already
|
||||
// open, but it seems useful to be able to detect this case to spot for
|
||||
// mistakes in application logic. If the application genuinely does't
|
||||
// care, it can check the exported error and ignore it.
|
||||
_, exists := l.channels[channel]
|
||||
if exists {
|
||||
return ErrChannelAlreadyOpen
|
||||
}
|
||||
|
||||
if l.cn != nil {
|
||||
// If gotResponse is true but error is set, the query was executed on
|
||||
// the remote server, but resulted in an error. This should be
|
||||
// relatively rare, so it's fine if we just pass the error to our
|
||||
// caller. However, if gotResponse is false, we could not complete the
|
||||
// query on the remote server and our underlying connection is about
|
||||
// to go away, so we only add relname to l.channels, and wait for
|
||||
// resync() to take care of the rest.
|
||||
gotResponse, err := l.cn.Listen(channel)
|
||||
if gotResponse && err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
l.channels[channel] = struct{}{}
|
||||
for l.cn == nil {
|
||||
l.reconnectCond.Wait()
|
||||
// we let go of the mutex for a while
|
||||
if l.isClosed {
|
||||
return errListenerClosed
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlisten removes a channel from the Listener's channel list. Returns
|
||||
// ErrChannelNotOpen if the Listener is not listening on the specified channel.
|
||||
// Returns immediately with no error if there is no connection. Note that you
|
||||
// might still get notifications for this channel even after Unlisten has
|
||||
// returned.
|
||||
//
|
||||
// The channel name is case-sensitive.
|
||||
func (l *Listener) Unlisten(channel string) error {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
if l.isClosed {
|
||||
return errListenerClosed
|
||||
}
|
||||
|
||||
// Similarly to LISTEN, this is not an error in Postgres, but it seems
|
||||
// useful to distinguish from the normal conditions.
|
||||
_, exists := l.channels[channel]
|
||||
if !exists {
|
||||
return ErrChannelNotOpen
|
||||
}
|
||||
|
||||
if l.cn != nil {
|
||||
// Similarly to Listen (see comment in that function), the caller
|
||||
// should only be bothered with an error if it came from the backend as
|
||||
// a response to our query.
|
||||
gotResponse, err := l.cn.Unlisten(channel)
|
||||
if gotResponse && err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Don't bother waiting for resync if there's no connection.
|
||||
delete(l.channels, channel)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnlistenAll removes all channels from the Listener's channel list. Returns
|
||||
// immediately with no error if there is no connection. Note that you might
|
||||
// still get notifications for any of the deleted channels even after
|
||||
// UnlistenAll has returned.
|
||||
func (l *Listener) UnlistenAll() error {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
if l.isClosed {
|
||||
return errListenerClosed
|
||||
}
|
||||
|
||||
if l.cn != nil {
|
||||
// Similarly to Listen (see comment in that function), the caller
|
||||
// should only be bothered with an error if it came from the backend as
|
||||
// a response to our query.
|
||||
gotResponse, err := l.cn.UnlistenAll()
|
||||
if gotResponse && err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Don't bother waiting for resync if there's no connection.
|
||||
l.channels = make(map[string]struct{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ping the remote server to make sure it's alive. Non-nil return value means
|
||||
// that there is no active connection.
|
||||
func (l *Listener) Ping() error {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
if l.isClosed {
|
||||
return errListenerClosed
|
||||
}
|
||||
if l.cn == nil {
|
||||
return errors.New("no connection")
|
||||
}
|
||||
|
||||
return l.cn.Ping()
|
||||
}
|
||||
|
||||
// Clean up after losing the server connection. Returns l.cn.Err(), which
|
||||
// should have the reason the connection was lost.
|
||||
func (l *Listener) disconnectCleanup() error {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
// sanity check; can't look at Err() until the channel has been closed
|
||||
select {
|
||||
case _, ok := <-l.connNotificationChan:
|
||||
if ok {
|
||||
panic("connNotificationChan not closed")
|
||||
}
|
||||
default:
|
||||
panic("connNotificationChan not closed")
|
||||
}
|
||||
|
||||
err := l.cn.Err()
|
||||
l.cn.Close()
|
||||
l.cn = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Synchronize the list of channels we want to be listening on with the server
|
||||
// after the connection has been established.
|
||||
func (l *Listener) resync(cn *ListenerConn, notificationChan <-chan *Notification) error {
|
||||
doneChan := make(chan error)
|
||||
go func() {
|
||||
for channel := range l.channels {
|
||||
// If we got a response, return that error to our caller as it's
|
||||
// going to be more descriptive than cn.Err().
|
||||
gotResponse, err := cn.Listen(channel)
|
||||
if gotResponse && err != nil {
|
||||
doneChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
// If we couldn't reach the server, wait for notificationChan to
|
||||
// close and then return the error message from the connection, as
|
||||
// per ListenerConn's interface.
|
||||
if err != nil {
|
||||
for _ = range notificationChan {
|
||||
}
|
||||
doneChan <- cn.Err()
|
||||
return
|
||||
}
|
||||
}
|
||||
doneChan <- nil
|
||||
}()
|
||||
|
||||
// Ignore notifications while synchronization is going on to avoid
|
||||
// deadlocks. We have to send a nil notification over Notify anyway as
|
||||
// we can't possibly know which notifications (if any) were lost while
|
||||
// the connection was down, so there's no reason to try and process
|
||||
// these messages at all.
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-notificationChan:
|
||||
if !ok {
|
||||
notificationChan = nil
|
||||
}
|
||||
|
||||
case err := <-doneChan:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// caller should NOT be holding l.lock
|
||||
func (l *Listener) closed() bool {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
return l.isClosed
|
||||
}
|
||||
|
||||
func (l *Listener) connect() error {
|
||||
notificationChan := make(chan *Notification, 32)
|
||||
cn, err := newDialListenerConn(l.dialer, l.name, notificationChan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
err = l.resync(cn, notificationChan)
|
||||
if err != nil {
|
||||
cn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
l.cn = cn
|
||||
l.connNotificationChan = notificationChan
|
||||
l.reconnectCond.Broadcast()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close disconnects the Listener from the database and shuts it down.
|
||||
// Subsequent calls to its methods will return an error. Close returns an
|
||||
// error if the connection has already been closed.
|
||||
func (l *Listener) Close() error {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
if l.isClosed {
|
||||
return errListenerClosed
|
||||
}
|
||||
|
||||
if l.cn != nil {
|
||||
l.cn.Close()
|
||||
}
|
||||
l.isClosed = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) emitEvent(event ListenerEventType, err error) {
|
||||
if l.eventCallback != nil {
|
||||
l.eventCallback(event, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Main logic here: maintain a connection to the server when possible, wait
|
||||
// for notifications and emit events.
|
||||
func (l *Listener) listenerConnLoop() {
|
||||
var nextReconnect time.Time
|
||||
|
||||
reconnectInterval := l.minReconnectInterval
|
||||
for {
|
||||
for {
|
||||
err := l.connect()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if l.closed() {
|
||||
return
|
||||
}
|
||||
l.emitEvent(ListenerEventConnectionAttemptFailed, err)
|
||||
|
||||
time.Sleep(reconnectInterval)
|
||||
reconnectInterval *= 2
|
||||
if reconnectInterval > l.maxReconnectInterval {
|
||||
reconnectInterval = l.maxReconnectInterval
|
||||
}
|
||||
}
|
||||
|
||||
if nextReconnect.IsZero() {
|
||||
l.emitEvent(ListenerEventConnected, nil)
|
||||
} else {
|
||||
l.emitEvent(ListenerEventReconnected, nil)
|
||||
l.Notify <- nil
|
||||
}
|
||||
|
||||
reconnectInterval = l.minReconnectInterval
|
||||
nextReconnect = time.Now().Add(reconnectInterval)
|
||||
|
||||
for {
|
||||
notification, ok := <-l.connNotificationChan
|
||||
if !ok {
|
||||
// lost connection, loop again
|
||||
break
|
||||
}
|
||||
l.Notify <- notification
|
||||
}
|
||||
|
||||
err := l.disconnectCleanup()
|
||||
if l.closed() {
|
||||
return
|
||||
}
|
||||
l.emitEvent(ListenerEventDisconnected, err)
|
||||
|
||||
time.Sleep(nextReconnect.Sub(time.Now()))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) listenerMain() {
|
||||
l.listenerConnLoop()
|
||||
close(l.Notify)
|
||||
}
|
6
vendor/github.com/lib/pq/oid/doc.go
generated
vendored
Normal file
6
vendor/github.com/lib/pq/oid/doc.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
// Package oid contains OID constants
|
||||
// as defined by the Postgres server.
|
||||
package oid
|
||||
|
||||
// Oid is a Postgres Object ID.
|
||||
type Oid uint32
|
74
vendor/github.com/lib/pq/oid/gen.go
generated
vendored
Normal file
74
vendor/github.com/lib/pq/oid/gen.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
// +build ignore
|
||||
|
||||
// Generate the table of OID values
|
||||
// Run with 'go run gen.go'.
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
func main() {
|
||||
datname := os.Getenv("PGDATABASE")
|
||||
sslmode := os.Getenv("PGSSLMODE")
|
||||
|
||||
if datname == "" {
|
||||
os.Setenv("PGDATABASE", "pqgotest")
|
||||
}
|
||||
|
||||
if sslmode == "" {
|
||||
os.Setenv("PGSSLMODE", "disable")
|
||||
}
|
||||
|
||||
db, err := sql.Open("postgres", "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cmd := exec.Command("gofmt")
|
||||
cmd.Stderr = os.Stderr
|
||||
w, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
f, err := os.Create("types.go")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cmd.Stdout = f
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Fprintln(w, "// generated by 'go run gen.go'; do not edit")
|
||||
fmt.Fprintln(w, "\npackage oid")
|
||||
fmt.Fprintln(w, "const (")
|
||||
rows, err := db.Query(`
|
||||
SELECT typname, oid
|
||||
FROM pg_type WHERE oid < 10000
|
||||
ORDER BY oid;
|
||||
`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var name string
|
||||
var oid int
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&name, &oid)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Fprintf(w, "T_%s Oid = %d\n", name, oid)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Fprintln(w, ")")
|
||||
w.Close()
|
||||
cmd.Wait()
|
||||
}
|
161
vendor/github.com/lib/pq/oid/types.go
generated
vendored
Normal file
161
vendor/github.com/lib/pq/oid/types.go
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
// generated by 'go run gen.go'; do not edit
|
||||
|
||||
package oid
|
||||
|
||||
const (
|
||||
T_bool Oid = 16
|
||||
T_bytea Oid = 17
|
||||
T_char Oid = 18
|
||||
T_name Oid = 19
|
||||
T_int8 Oid = 20
|
||||
T_int2 Oid = 21
|
||||
T_int2vector Oid = 22
|
||||
T_int4 Oid = 23
|
||||
T_regproc Oid = 24
|
||||
T_text Oid = 25
|
||||
T_oid Oid = 26
|
||||
T_tid Oid = 27
|
||||
T_xid Oid = 28
|
||||
T_cid Oid = 29
|
||||
T_oidvector Oid = 30
|
||||
T_pg_type Oid = 71
|
||||
T_pg_attribute Oid = 75
|
||||
T_pg_proc Oid = 81
|
||||
T_pg_class Oid = 83
|
||||
T_json Oid = 114
|
||||
T_xml Oid = 142
|
||||
T__xml Oid = 143
|
||||
T_pg_node_tree Oid = 194
|
||||
T__json Oid = 199
|
||||
T_smgr Oid = 210
|
||||
T_point Oid = 600
|
||||
T_lseg Oid = 601
|
||||
T_path Oid = 602
|
||||
T_box Oid = 603
|
||||
T_polygon Oid = 604
|
||||
T_line Oid = 628
|
||||
T__line Oid = 629
|
||||
T_cidr Oid = 650
|
||||
T__cidr Oid = 651
|
||||
T_float4 Oid = 700
|
||||
T_float8 Oid = 701
|
||||
T_abstime Oid = 702
|
||||
T_reltime Oid = 703
|
||||
T_tinterval Oid = 704
|
||||
T_unknown Oid = 705
|
||||
T_circle Oid = 718
|
||||
T__circle Oid = 719
|
||||
T_money Oid = 790
|
||||
T__money Oid = 791
|
||||
T_macaddr Oid = 829
|
||||
T_inet Oid = 869
|
||||
T__bool Oid = 1000
|
||||
T__bytea Oid = 1001
|
||||
T__char Oid = 1002
|
||||
T__name Oid = 1003
|
||||
T__int2 Oid = 1005
|
||||
T__int2vector Oid = 1006
|
||||
T__int4 Oid = 1007
|
||||
T__regproc Oid = 1008
|
||||
T__text Oid = 1009
|
||||
T__tid Oid = 1010
|
||||
T__xid Oid = 1011
|
||||
T__cid Oid = 1012
|
||||
T__oidvector Oid = 1013
|
||||
T__bpchar Oid = 1014
|
||||
T__varchar Oid = 1015
|
||||
T__int8 Oid = 1016
|
||||
T__point Oid = 1017
|
||||
T__lseg Oid = 1018
|
||||
T__path Oid = 1019
|
||||
T__box Oid = 1020
|
||||
T__float4 Oid = 1021
|
||||
T__float8 Oid = 1022
|
||||
T__abstime Oid = 1023
|
||||
T__reltime Oid = 1024
|
||||
T__tinterval Oid = 1025
|
||||
T__polygon Oid = 1027
|
||||
T__oid Oid = 1028
|
||||
T_aclitem Oid = 1033
|
||||
T__aclitem Oid = 1034
|
||||
T__macaddr Oid = 1040
|
||||
T__inet Oid = 1041
|
||||
T_bpchar Oid = 1042
|
||||
T_varchar Oid = 1043
|
||||
T_date Oid = 1082
|
||||
T_time Oid = 1083
|
||||
T_timestamp Oid = 1114
|
||||
T__timestamp Oid = 1115
|
||||
T__date Oid = 1182
|
||||
T__time Oid = 1183
|
||||
T_timestamptz Oid = 1184
|
||||
T__timestamptz Oid = 1185
|
||||
T_interval Oid = 1186
|
||||
T__interval Oid = 1187
|
||||
T__numeric Oid = 1231
|
||||
T_pg_database Oid = 1248
|
||||
T__cstring Oid = 1263
|
||||
T_timetz Oid = 1266
|
||||
T__timetz Oid = 1270
|
||||
T_bit Oid = 1560
|
||||
T__bit Oid = 1561
|
||||
T_varbit Oid = 1562
|
||||
T__varbit Oid = 1563
|
||||
T_numeric Oid = 1700
|
||||
T_refcursor Oid = 1790
|
||||
T__refcursor Oid = 2201
|
||||
T_regprocedure Oid = 2202
|
||||
T_regoper Oid = 2203
|
||||
T_regoperator Oid = 2204
|
||||
T_regclass Oid = 2205
|
||||
T_regtype Oid = 2206
|
||||
T__regprocedure Oid = 2207
|
||||
T__regoper Oid = 2208
|
||||
T__regoperator Oid = 2209
|
||||
T__regclass Oid = 2210
|
||||
T__regtype Oid = 2211
|
||||
T_record Oid = 2249
|
||||
T_cstring Oid = 2275
|
||||
T_any Oid = 2276
|
||||
T_anyarray Oid = 2277
|
||||
T_void Oid = 2278
|
||||
T_trigger Oid = 2279
|
||||
T_language_handler Oid = 2280
|
||||
T_internal Oid = 2281
|
||||
T_opaque Oid = 2282
|
||||
T_anyelement Oid = 2283
|
||||
T__record Oid = 2287
|
||||
T_anynonarray Oid = 2776
|
||||
T_pg_authid Oid = 2842
|
||||
T_pg_auth_members Oid = 2843
|
||||
T__txid_snapshot Oid = 2949
|
||||
T_uuid Oid = 2950
|
||||
T__uuid Oid = 2951
|
||||
T_txid_snapshot Oid = 2970
|
||||
T_fdw_handler Oid = 3115
|
||||
T_anyenum Oid = 3500
|
||||
T_tsvector Oid = 3614
|
||||
T_tsquery Oid = 3615
|
||||
T_gtsvector Oid = 3642
|
||||
T__tsvector Oid = 3643
|
||||
T__gtsvector Oid = 3644
|
||||
T__tsquery Oid = 3645
|
||||
T_regconfig Oid = 3734
|
||||
T__regconfig Oid = 3735
|
||||
T_regdictionary Oid = 3769
|
||||
T__regdictionary Oid = 3770
|
||||
T_anyrange Oid = 3831
|
||||
T_event_trigger Oid = 3838
|
||||
T_int4range Oid = 3904
|
||||
T__int4range Oid = 3905
|
||||
T_numrange Oid = 3906
|
||||
T__numrange Oid = 3907
|
||||
T_tsrange Oid = 3908
|
||||
T__tsrange Oid = 3909
|
||||
T_tstzrange Oid = 3910
|
||||
T__tstzrange Oid = 3911
|
||||
T_daterange Oid = 3912
|
||||
T__daterange Oid = 3913
|
||||
T_int8range Oid = 3926
|
||||
T__int8range Oid = 3927
|
||||
)
|
76
vendor/github.com/lib/pq/url.go
generated
vendored
Normal file
76
vendor/github.com/lib/pq/url.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
package pq
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
nurl "net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseURL no longer needs to be used by clients of this library since supplying a URL as a
|
||||
// connection string to sql.Open() is now supported:
|
||||
//
|
||||
// sql.Open("postgres", "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full")
|
||||
//
|
||||
// It remains exported here for backwards-compatibility.
|
||||
//
|
||||
// ParseURL converts a url to a connection string for driver.Open.
|
||||
// Example:
|
||||
//
|
||||
// "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full"
|
||||
//
|
||||
// converts to:
|
||||
//
|
||||
// "user=bob password=secret host=1.2.3.4 port=5432 dbname=mydb sslmode=verify-full"
|
||||
//
|
||||
// A minimal example:
|
||||
//
|
||||
// "postgres://"
|
||||
//
|
||||
// This will be blank, causing driver.Open to use all of the defaults
|
||||
func ParseURL(url string) (string, error) {
|
||||
u, err := nurl.Parse(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if u.Scheme != "postgres" && u.Scheme != "postgresql" {
|
||||
return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme)
|
||||
}
|
||||
|
||||
var kvs []string
|
||||
escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`)
|
||||
accrue := func(k, v string) {
|
||||
if v != "" {
|
||||
kvs = append(kvs, k+"="+escaper.Replace(v))
|
||||
}
|
||||
}
|
||||
|
||||
if u.User != nil {
|
||||
v := u.User.Username()
|
||||
accrue("user", v)
|
||||
|
||||
v, _ = u.User.Password()
|
||||
accrue("password", v)
|
||||
}
|
||||
|
||||
if host, port, err := net.SplitHostPort(u.Host); err != nil {
|
||||
accrue("host", u.Host)
|
||||
} else {
|
||||
accrue("host", host)
|
||||
accrue("port", port)
|
||||
}
|
||||
|
||||
if u.Path != "" {
|
||||
accrue("dbname", u.Path[1:])
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
for k := range q {
|
||||
accrue(k, q.Get(k))
|
||||
}
|
||||
|
||||
sort.Strings(kvs) // Makes testing easier (not a performance concern)
|
||||
return strings.Join(kvs, " "), nil
|
||||
}
|
24
vendor/github.com/lib/pq/user_posix.go
generated
vendored
Normal file
24
vendor/github.com/lib/pq/user_posix.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
// Package pq is a pure Go Postgres driver for the database/sql package.
|
||||
|
||||
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris rumprun
|
||||
|
||||
package pq
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
)
|
||||
|
||||
func userCurrent() (string, error) {
|
||||
u, err := user.Current()
|
||||
if err == nil {
|
||||
return u.Username, nil
|
||||
}
|
||||
|
||||
name := os.Getenv("USER")
|
||||
if name != "" {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
return "", ErrCouldNotDetectUsername
|
||||
}
|
27
vendor/github.com/lib/pq/user_windows.go
generated
vendored
Normal file
27
vendor/github.com/lib/pq/user_windows.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
// Package pq is a pure Go Postgres driver for the database/sql package.
|
||||
package pq
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Perform Windows user name lookup identically to libpq.
|
||||
//
|
||||
// The PostgreSQL code makes use of the legacy Win32 function
|
||||
// GetUserName, and that function has not been imported into stock Go.
|
||||
// GetUserNameEx is available though, the difference being that a
|
||||
// wider range of names are available. To get the output to be the
|
||||
// same as GetUserName, only the base (or last) component of the
|
||||
// result is returned.
|
||||
func userCurrent() (string, error) {
|
||||
pw_name := make([]uint16, 128)
|
||||
pwname_size := uint32(len(pw_name)) - 1
|
||||
err := syscall.GetUserNameEx(syscall.NameSamCompatible, &pw_name[0], &pwname_size)
|
||||
if err != nil {
|
||||
return "", ErrCouldNotDetectUsername
|
||||
}
|
||||
s := syscall.UTF16ToString(pw_name)
|
||||
u := filepath.Base(s)
|
||||
return u, nil
|
||||
}
|
23
vendor/github.com/smartystreets/assertions/LICENSE.md
generated
vendored
Normal file
23
vendor/github.com/smartystreets/assertions/LICENSE.md
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
Copyright (c) 2015 SmartyStreets, LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
NOTE: Various optional and subordinate components carry their own licensing
|
||||
requirements and restrictions. Use of those components is subject to the terms
|
||||
and conditions outlined the respective license of each component.
|
554
vendor/github.com/smartystreets/assertions/README.md
generated
vendored
Normal file
554
vendor/github.com/smartystreets/assertions/README.md
generated
vendored
Normal file
@ -0,0 +1,554 @@
|
||||
# assertions
|
||||
--
|
||||
import "github.com/smartystreets/assertions"
|
||||
|
||||
Package assertions contains the implementations for all assertions which are
|
||||
referenced in goconvey's `convey` package
|
||||
(github.com/smartystreets/goconvey/convey) for use with the So(...) method. They
|
||||
can also be used in traditional Go test functions and even in applicaitons.
|
||||
|
||||
## Usage
|
||||
|
||||
#### func GoConveyMode
|
||||
|
||||
```go
|
||||
func GoConveyMode(yes bool)
|
||||
```
|
||||
GoConveyMode provides control over JSON serialization of failures. When using
|
||||
the assertions in this package from the convey package JSON results are very
|
||||
helpful and can be rendered in a DIFF view. In that case, this function will be
|
||||
called with a true value to enable the JSON serialization. By default, the
|
||||
assertions in this package will not serializer a JSON result, making standalone
|
||||
ussage more convenient.
|
||||
|
||||
#### func ShouldAlmostEqual
|
||||
|
||||
```go
|
||||
func ShouldAlmostEqual(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldAlmostEqual makes sure that two parameters are close enough to being
|
||||
equal. The acceptable delta may be specified with a third argument, or a very
|
||||
small default delta will be used.
|
||||
|
||||
#### func ShouldBeBetween
|
||||
|
||||
```go
|
||||
func ShouldBeBetween(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeBetween receives exactly three parameters: an actual value, a lower
|
||||
bound, and an upper bound. It ensures that the actual value is between both
|
||||
bounds (but not equal to either of them).
|
||||
|
||||
#### func ShouldBeBetweenOrEqual
|
||||
|
||||
```go
|
||||
func ShouldBeBetweenOrEqual(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeBetweenOrEqual receives exactly three parameters: an actual value, a
|
||||
lower bound, and an upper bound. It ensures that the actual value is between
|
||||
both bounds or equal to one of them.
|
||||
|
||||
#### func ShouldBeBlank
|
||||
|
||||
```go
|
||||
func ShouldBeBlank(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeBlank receives exactly 1 string parameter and ensures that it is equal
|
||||
to "".
|
||||
|
||||
#### func ShouldBeChronological
|
||||
|
||||
```go
|
||||
func ShouldBeChronological(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeChronological receives a []time.Time slice and asserts that the are in
|
||||
chronological order starting with the first time.Time as the earliest.
|
||||
|
||||
#### func ShouldBeEmpty
|
||||
|
||||
```go
|
||||
func ShouldBeEmpty(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeEmpty receives a single parameter (actual) and determines whether or not
|
||||
calling len(actual) would return `0`. It obeys the rules specified by the len
|
||||
function for determining length: http://golang.org/pkg/builtin/#len
|
||||
|
||||
#### func ShouldBeFalse
|
||||
|
||||
```go
|
||||
func ShouldBeFalse(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeFalse receives a single parameter and ensures that it is false.
|
||||
|
||||
#### func ShouldBeGreaterThan
|
||||
|
||||
```go
|
||||
func ShouldBeGreaterThan(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeGreaterThan receives exactly two parameters and ensures that the first
|
||||
is greater than the second.
|
||||
|
||||
#### func ShouldBeGreaterThanOrEqualTo
|
||||
|
||||
```go
|
||||
func ShouldBeGreaterThanOrEqualTo(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeGreaterThanOrEqualTo receives exactly two parameters and ensures that
|
||||
the first is greater than or equal to the second.
|
||||
|
||||
#### func ShouldBeIn
|
||||
|
||||
```go
|
||||
func ShouldBeIn(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeIn receives at least 2 parameters. The first is a proposed member of the
|
||||
collection that is passed in either as the second parameter, or of the
|
||||
collection that is comprised of all the remaining parameters. This assertion
|
||||
ensures that the proposed member is in the collection (using ShouldEqual).
|
||||
|
||||
#### func ShouldBeLessThan
|
||||
|
||||
```go
|
||||
func ShouldBeLessThan(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeLessThan receives exactly two parameters and ensures that the first is
|
||||
less than the second.
|
||||
|
||||
#### func ShouldBeLessThanOrEqualTo
|
||||
|
||||
```go
|
||||
func ShouldBeLessThanOrEqualTo(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeLessThan receives exactly two parameters and ensures that the first is
|
||||
less than or equal to the second.
|
||||
|
||||
#### func ShouldBeNil
|
||||
|
||||
```go
|
||||
func ShouldBeNil(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeNil receives a single parameter and ensures that it is nil.
|
||||
|
||||
#### func ShouldBeTrue
|
||||
|
||||
```go
|
||||
func ShouldBeTrue(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeTrue receives a single parameter and ensures that it is true.
|
||||
|
||||
#### func ShouldBeZeroValue
|
||||
|
||||
```go
|
||||
func ShouldBeZeroValue(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeZeroValue receives a single parameter and ensures that it is the Go
|
||||
equivalent of the default value, or "zero" value.
|
||||
|
||||
#### func ShouldContain
|
||||
|
||||
```go
|
||||
func ShouldContain(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldContain receives exactly two parameters. The first is a slice and the
|
||||
second is a proposed member. Membership is determined using ShouldEqual.
|
||||
|
||||
#### func ShouldContainKey
|
||||
|
||||
```go
|
||||
func ShouldContainKey(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldContainKey receives exactly two parameters. The first is a map and the
|
||||
second is a proposed key. Keys are compared with a simple '=='.
|
||||
|
||||
#### func ShouldContainSubstring
|
||||
|
||||
```go
|
||||
func ShouldContainSubstring(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldContainSubstring receives exactly 2 string parameters and ensures that the
|
||||
first contains the second as a substring.
|
||||
|
||||
#### func ShouldEndWith
|
||||
|
||||
```go
|
||||
func ShouldEndWith(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldEndWith receives exactly 2 string parameters and ensures that the first
|
||||
ends with the second.
|
||||
|
||||
#### func ShouldEqual
|
||||
|
||||
```go
|
||||
func ShouldEqual(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldEqual receives exactly two parameters and does an equality check.
|
||||
|
||||
#### func ShouldEqualTrimSpace
|
||||
|
||||
```go
|
||||
func ShouldEqualTrimSpace(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldEqualTrimSpace receives exactly 2 string parameters and ensures that the
|
||||
first is equal to the second after removing all leading and trailing whitespace
|
||||
using strings.TrimSpace(first).
|
||||
|
||||
#### func ShouldEqualWithout
|
||||
|
||||
```go
|
||||
func ShouldEqualWithout(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldEqualWithout receives exactly 3 string parameters and ensures that the
|
||||
first is equal to the second after removing all instances of the third from the
|
||||
first using strings.Replace(first, third, "", -1).
|
||||
|
||||
#### func ShouldHappenAfter
|
||||
|
||||
```go
|
||||
func ShouldHappenAfter(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenAfter receives exactly 2 time.Time arguments and asserts that the
|
||||
first happens after the second.
|
||||
|
||||
#### func ShouldHappenBefore
|
||||
|
||||
```go
|
||||
func ShouldHappenBefore(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenBefore receives exactly 2 time.Time arguments and asserts that the
|
||||
first happens before the second.
|
||||
|
||||
#### func ShouldHappenBetween
|
||||
|
||||
```go
|
||||
func ShouldHappenBetween(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenBetween receives exactly 3 time.Time arguments and asserts that the
|
||||
first happens between (not on) the second and third.
|
||||
|
||||
#### func ShouldHappenOnOrAfter
|
||||
|
||||
```go
|
||||
func ShouldHappenOnOrAfter(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenOnOrAfter receives exactly 2 time.Time arguments and asserts that
|
||||
the first happens on or after the second.
|
||||
|
||||
#### func ShouldHappenOnOrBefore
|
||||
|
||||
```go
|
||||
func ShouldHappenOnOrBefore(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenOnOrBefore receives exactly 2 time.Time arguments and asserts that
|
||||
the first happens on or before the second.
|
||||
|
||||
#### func ShouldHappenOnOrBetween
|
||||
|
||||
```go
|
||||
func ShouldHappenOnOrBetween(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenOnOrBetween receives exactly 3 time.Time arguments and asserts that
|
||||
the first happens between or on the second and third.
|
||||
|
||||
#### func ShouldHappenWithin
|
||||
|
||||
```go
|
||||
func ShouldHappenWithin(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenWithin receives a time.Time, a time.Duration, and a time.Time (3
|
||||
arguments) and asserts that the first time.Time happens within or on the
|
||||
duration specified relative to the other time.Time.
|
||||
|
||||
#### func ShouldHaveLength
|
||||
|
||||
```go
|
||||
func ShouldHaveLength(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHaveLength receives a collection and a positive integer and asserts that
|
||||
the length of the collection is equal to the integer provided.
|
||||
|
||||
#### func ShouldHaveSameTypeAs
|
||||
|
||||
```go
|
||||
func ShouldHaveSameTypeAs(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHaveSameTypeAs receives exactly two parameters and compares their
|
||||
underlying types for equality.
|
||||
|
||||
#### func ShouldImplement
|
||||
|
||||
```go
|
||||
func ShouldImplement(actual interface{}, expectedList ...interface{}) string
|
||||
```
|
||||
ShouldImplement receives exactly two parameters and ensures that the first
|
||||
implements the interface type of the second.
|
||||
|
||||
#### func ShouldNotAlmostEqual
|
||||
|
||||
```go
|
||||
func ShouldNotAlmostEqual(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotAlmostEqual is the inverse of ShouldAlmostEqual
|
||||
|
||||
#### func ShouldNotBeBetween
|
||||
|
||||
```go
|
||||
func ShouldNotBeBetween(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotBeBetween receives exactly three parameters: an actual value, a lower
|
||||
bound, and an upper bound. It ensures that the actual value is NOT between both
|
||||
bounds.
|
||||
|
||||
#### func ShouldNotBeBetweenOrEqual
|
||||
|
||||
```go
|
||||
func ShouldNotBeBetweenOrEqual(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotBeBetweenOrEqual receives exactly three parameters: an actual value, a
|
||||
lower bound, and an upper bound. It ensures that the actual value is nopt
|
||||
between the bounds nor equal to either of them.
|
||||
|
||||
#### func ShouldNotBeBlank
|
||||
|
||||
```go
|
||||
func ShouldNotBeBlank(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotBeBlank receives exactly 1 string parameter and ensures that it is
|
||||
equal to "".
|
||||
|
||||
#### func ShouldNotBeEmpty
|
||||
|
||||
```go
|
||||
func ShouldNotBeEmpty(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotBeEmpty receives a single parameter (actual) and determines whether or
|
||||
not calling len(actual) would return a value greater than zero. It obeys the
|
||||
rules specified by the `len` function for determining length:
|
||||
http://golang.org/pkg/builtin/#len
|
||||
|
||||
#### func ShouldNotBeIn
|
||||
|
||||
```go
|
||||
func ShouldNotBeIn(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotBeIn receives at least 2 parameters. The first is a proposed member of
|
||||
the collection that is passed in either as the second parameter, or of the
|
||||
collection that is comprised of all the remaining parameters. This assertion
|
||||
ensures that the proposed member is NOT in the collection (using ShouldEqual).
|
||||
|
||||
#### func ShouldNotBeNil
|
||||
|
||||
```go
|
||||
func ShouldNotBeNil(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotBeNil receives a single parameter and ensures that it is not nil.
|
||||
|
||||
#### func ShouldNotContain
|
||||
|
||||
```go
|
||||
func ShouldNotContain(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotContain receives exactly two parameters. The first is a slice and the
|
||||
second is a proposed member. Membership is determinied using ShouldEqual.
|
||||
|
||||
#### func ShouldNotContainKey
|
||||
|
||||
```go
|
||||
func ShouldNotContainKey(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotContainKey receives exactly two parameters. The first is a map and the
|
||||
second is a proposed absent key. Keys are compared with a simple '=='.
|
||||
|
||||
#### func ShouldNotContainSubstring
|
||||
|
||||
```go
|
||||
func ShouldNotContainSubstring(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotContainSubstring receives exactly 2 string parameters and ensures that
|
||||
the first does NOT contain the second as a substring.
|
||||
|
||||
#### func ShouldNotEndWith
|
||||
|
||||
```go
|
||||
func ShouldNotEndWith(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldEndWith receives exactly 2 string parameters and ensures that the first
|
||||
does not end with the second.
|
||||
|
||||
#### func ShouldNotEqual
|
||||
|
||||
```go
|
||||
func ShouldNotEqual(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotEqual receives exactly two parameters and does an inequality check.
|
||||
|
||||
#### func ShouldNotHappenOnOrBetween
|
||||
|
||||
```go
|
||||
func ShouldNotHappenOnOrBetween(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotHappenOnOrBetween receives exactly 3 time.Time arguments and asserts
|
||||
that the first does NOT happen between or on the second or third.
|
||||
|
||||
#### func ShouldNotHappenWithin
|
||||
|
||||
```go
|
||||
func ShouldNotHappenWithin(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotHappenWithin receives a time.Time, a time.Duration, and a time.Time (3
|
||||
arguments) and asserts that the first time.Time does NOT happen within or on the
|
||||
duration specified relative to the other time.Time.
|
||||
|
||||
#### func ShouldNotHaveSameTypeAs
|
||||
|
||||
```go
|
||||
func ShouldNotHaveSameTypeAs(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotHaveSameTypeAs receives exactly two parameters and compares their
|
||||
underlying types for inequality.
|
||||
|
||||
#### func ShouldNotImplement
|
||||
|
||||
```go
|
||||
func ShouldNotImplement(actual interface{}, expectedList ...interface{}) string
|
||||
```
|
||||
ShouldNotImplement receives exactly two parameters and ensures that the first
|
||||
does NOT implement the interface type of the second.
|
||||
|
||||
#### func ShouldNotPanic
|
||||
|
||||
```go
|
||||
func ShouldNotPanic(actual interface{}, expected ...interface{}) (message string)
|
||||
```
|
||||
ShouldNotPanic receives a void, niladic function and expects to execute the
|
||||
function without any panic.
|
||||
|
||||
#### func ShouldNotPanicWith
|
||||
|
||||
```go
|
||||
func ShouldNotPanicWith(actual interface{}, expected ...interface{}) (message string)
|
||||
```
|
||||
ShouldNotPanicWith receives a void, niladic function and expects to recover a
|
||||
panic whose content differs from the second argument.
|
||||
|
||||
#### func ShouldNotPointTo
|
||||
|
||||
```go
|
||||
func ShouldNotPointTo(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotPointTo receives exactly two parameters and checks to see that they
|
||||
point to different addresess.
|
||||
|
||||
#### func ShouldNotResemble
|
||||
|
||||
```go
|
||||
func ShouldNotResemble(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotResemble receives exactly two parameters and does an inverse deep equal
|
||||
check (see reflect.DeepEqual)
|
||||
|
||||
#### func ShouldNotStartWith
|
||||
|
||||
```go
|
||||
func ShouldNotStartWith(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotStartWith receives exactly 2 string parameters and ensures that the
|
||||
first does not start with the second.
|
||||
|
||||
#### func ShouldPanic
|
||||
|
||||
```go
|
||||
func ShouldPanic(actual interface{}, expected ...interface{}) (message string)
|
||||
```
|
||||
ShouldPanic receives a void, niladic function and expects to recover a panic.
|
||||
|
||||
#### func ShouldPanicWith
|
||||
|
||||
```go
|
||||
func ShouldPanicWith(actual interface{}, expected ...interface{}) (message string)
|
||||
```
|
||||
ShouldPanicWith receives a void, niladic function and expects to recover a panic
|
||||
with the second argument as the content.
|
||||
|
||||
#### func ShouldPointTo
|
||||
|
||||
```go
|
||||
func ShouldPointTo(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldPointTo receives exactly two parameters and checks to see that they point
|
||||
to the same address.
|
||||
|
||||
#### func ShouldResemble
|
||||
|
||||
```go
|
||||
func ShouldResemble(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldResemble receives exactly two parameters and does a deep equal check (see
|
||||
reflect.DeepEqual)
|
||||
|
||||
#### func ShouldStartWith
|
||||
|
||||
```go
|
||||
func ShouldStartWith(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldStartWith receives exactly 2 string parameters and ensures that the first
|
||||
starts with the second.
|
||||
|
||||
#### func So
|
||||
|
||||
```go
|
||||
func So(actual interface{}, assert assertion, expected ...interface{}) (bool, string)
|
||||
```
|
||||
So is a convenience function (as opposed to an inconvenience function?) for
|
||||
running assertions on arbitrary arguments in any context, be it for testing or
|
||||
even application logging. It allows you to perform assertion-like behavior (and
|
||||
get nicely formatted messages detailing discrepancies) but without the program
|
||||
blowing up or panicking. All that is required is to import this package and call
|
||||
`So` with one of the assertions exported by this package as the second
|
||||
parameter. The first return parameter is a boolean indicating if the assertion
|
||||
was true. The second return parameter is the well-formatted message showing why
|
||||
an assertion was incorrect, or blank if the assertion was correct.
|
||||
|
||||
Example:
|
||||
|
||||
if ok, message := So(x, ShouldBeGreaterThan, y); !ok {
|
||||
log.Println(message)
|
||||
}
|
||||
|
||||
#### type Assertion
|
||||
|
||||
```go
|
||||
type Assertion struct {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### func New
|
||||
|
||||
```go
|
||||
func New(t testingT) *Assertion
|
||||
```
|
||||
New swallows the *testing.T struct and prints failed assertions using t.Error.
|
||||
Example: assertions.New(t).So(1, should.Equal, 1)
|
||||
|
||||
#### func (*Assertion) Failed
|
||||
|
||||
```go
|
||||
func (this *Assertion) Failed() bool
|
||||
```
|
||||
Failed reports whether any calls to So (on this Assertion instance) have failed.
|
||||
|
||||
#### func (*Assertion) So
|
||||
|
||||
```go
|
||||
func (this *Assertion) So(actual interface{}, assert assertion, expected ...interface{}) bool
|
||||
```
|
||||
So calls the standalone So function and additionally, calls t.Error in failure
|
||||
scenarios.
|
||||
|
||||
#### type Serializer
|
||||
|
||||
```go
|
||||
type Serializer interface {
|
||||
// contains filtered or unexported methods
|
||||
}
|
||||
```
|
3
vendor/github.com/smartystreets/assertions/assertions.goconvey
generated
vendored
Normal file
3
vendor/github.com/smartystreets/assertions/assertions.goconvey
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
#ignore
|
||||
-timeout=1s
|
||||
-coverpkg=github.com/smartystreets/assertions,github.com/smartystreets/assertions/internal/oglematchers
|
250
vendor/github.com/smartystreets/assertions/collections.go
generated
vendored
Normal file
250
vendor/github.com/smartystreets/assertions/collections.go
generated
vendored
Normal file
@ -0,0 +1,250 @@
|
||||
package assertions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/smartystreets/assertions/internal/oglematchers"
|
||||
)
|
||||
|
||||
// ShouldContain receives exactly two parameters. The first is a slice and the
|
||||
// second is a proposed member. Membership is determined using ShouldEqual.
|
||||
func ShouldContain(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
if matchError := oglematchers.Contains(expected[0]).Matches(actual); matchError != nil {
|
||||
typeName := reflect.TypeOf(actual)
|
||||
|
||||
if fmt.Sprintf("%v", matchError) == "which is not a slice or array" {
|
||||
return fmt.Sprintf(shouldHaveBeenAValidCollection, typeName)
|
||||
}
|
||||
return fmt.Sprintf(shouldHaveContained, typeName, expected[0])
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotContain receives exactly two parameters. The first is a slice and the
|
||||
// second is a proposed member. Membership is determinied using ShouldEqual.
|
||||
func ShouldNotContain(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
typeName := reflect.TypeOf(actual)
|
||||
|
||||
if matchError := oglematchers.Contains(expected[0]).Matches(actual); matchError != nil {
|
||||
if fmt.Sprintf("%v", matchError) == "which is not a slice or array" {
|
||||
return fmt.Sprintf(shouldHaveBeenAValidCollection, typeName)
|
||||
}
|
||||
return success
|
||||
}
|
||||
return fmt.Sprintf(shouldNotHaveContained, typeName, expected[0])
|
||||
}
|
||||
|
||||
// ShouldContainKey receives exactly two parameters. The first is a map and the
|
||||
// second is a proposed key. Keys are compared with a simple '=='.
|
||||
func ShouldContainKey(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
keys, isMap := mapKeys(actual)
|
||||
if !isMap {
|
||||
return fmt.Sprintf(shouldHaveBeenAValidMap, reflect.TypeOf(actual))
|
||||
}
|
||||
|
||||
if !keyFound(keys, expected[0]) {
|
||||
return fmt.Sprintf(shouldHaveContainedKey, reflect.TypeOf(actual), expected)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// ShouldNotContainKey receives exactly two parameters. The first is a map and the
|
||||
// second is a proposed absent key. Keys are compared with a simple '=='.
|
||||
func ShouldNotContainKey(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
keys, isMap := mapKeys(actual)
|
||||
if !isMap {
|
||||
return fmt.Sprintf(shouldHaveBeenAValidMap, reflect.TypeOf(actual))
|
||||
}
|
||||
|
||||
if keyFound(keys, expected[0]) {
|
||||
return fmt.Sprintf(shouldNotHaveContainedKey, reflect.TypeOf(actual), expected)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func mapKeys(m interface{}) ([]reflect.Value, bool) {
|
||||
value := reflect.ValueOf(m)
|
||||
if value.Kind() != reflect.Map {
|
||||
return nil, false
|
||||
}
|
||||
return value.MapKeys(), true
|
||||
}
|
||||
func keyFound(keys []reflect.Value, expectedKey interface{}) bool {
|
||||
found := false
|
||||
for _, key := range keys {
|
||||
if key.Interface() == expectedKey {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
// ShouldBeIn receives at least 2 parameters. The first is a proposed member of the collection
|
||||
// that is passed in either as the second parameter, or of the collection that is comprised
|
||||
// of all the remaining parameters. This assertion ensures that the proposed member is in
|
||||
// the collection (using ShouldEqual).
|
||||
func ShouldBeIn(actual interface{}, expected ...interface{}) string {
|
||||
if fail := atLeast(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
if len(expected) == 1 {
|
||||
return shouldBeIn(actual, expected[0])
|
||||
}
|
||||
return shouldBeIn(actual, expected)
|
||||
}
|
||||
func shouldBeIn(actual interface{}, expected interface{}) string {
|
||||
if matchError := oglematchers.Contains(actual).Matches(expected); matchError != nil {
|
||||
return fmt.Sprintf(shouldHaveBeenIn, actual, reflect.TypeOf(expected))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotBeIn receives at least 2 parameters. The first is a proposed member of the collection
|
||||
// that is passed in either as the second parameter, or of the collection that is comprised
|
||||
// of all the remaining parameters. This assertion ensures that the proposed member is NOT in
|
||||
// the collection (using ShouldEqual).
|
||||
func ShouldNotBeIn(actual interface{}, expected ...interface{}) string {
|
||||
if fail := atLeast(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
if len(expected) == 1 {
|
||||
return shouldNotBeIn(actual, expected[0])
|
||||
}
|
||||
return shouldNotBeIn(actual, expected)
|
||||
}
|
||||
func shouldNotBeIn(actual interface{}, expected interface{}) string {
|
||||
if matchError := oglematchers.Contains(actual).Matches(expected); matchError == nil {
|
||||
return fmt.Sprintf(shouldNotHaveBeenIn, actual, reflect.TypeOf(expected))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeEmpty receives a single parameter (actual) and determines whether or not
|
||||
// calling len(actual) would return `0`. It obeys the rules specified by the len
|
||||
// function for determining length: http://golang.org/pkg/builtin/#len
|
||||
func ShouldBeEmpty(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
if actual == nil {
|
||||
return success
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(actual)
|
||||
switch value.Kind() {
|
||||
case reflect.Slice:
|
||||
if value.Len() == 0 {
|
||||
return success
|
||||
}
|
||||
case reflect.Chan:
|
||||
if value.Len() == 0 {
|
||||
return success
|
||||
}
|
||||
case reflect.Map:
|
||||
if value.Len() == 0 {
|
||||
return success
|
||||
}
|
||||
case reflect.String:
|
||||
if value.Len() == 0 {
|
||||
return success
|
||||
}
|
||||
case reflect.Ptr:
|
||||
elem := value.Elem()
|
||||
kind := elem.Kind()
|
||||
if (kind == reflect.Slice || kind == reflect.Array) && elem.Len() == 0 {
|
||||
return success
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf(shouldHaveBeenEmpty, actual)
|
||||
}
|
||||
|
||||
// ShouldNotBeEmpty receives a single parameter (actual) and determines whether or not
|
||||
// calling len(actual) would return a value greater than zero. It obeys the rules
|
||||
// specified by the `len` function for determining length: http://golang.org/pkg/builtin/#len
|
||||
func ShouldNotBeEmpty(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
if empty := ShouldBeEmpty(actual, expected...); empty != success {
|
||||
return success
|
||||
}
|
||||
return fmt.Sprintf(shouldNotHaveBeenEmpty, actual)
|
||||
}
|
||||
|
||||
// ShouldHaveLength receives 2 parameters. The first is a collection to check
|
||||
// the length of, the second being the expected length. It obeys the rules
|
||||
// specified by the len function for determining length:
|
||||
// http://golang.org/pkg/builtin/#len
|
||||
func ShouldHaveLength(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
var expectedLen int64
|
||||
lenValue := reflect.ValueOf(expected[0])
|
||||
switch lenValue.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
expectedLen = lenValue.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
expectedLen = int64(lenValue.Uint())
|
||||
default:
|
||||
return fmt.Sprintf(shouldHaveBeenAValidInteger, reflect.TypeOf(expected[0]))
|
||||
}
|
||||
|
||||
if expectedLen < 0 {
|
||||
return fmt.Sprintf(shouldHaveBeenAValidLength, expected[0])
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(actual)
|
||||
switch value.Kind() {
|
||||
case reflect.Slice:
|
||||
if int64(value.Len()) == expectedLen {
|
||||
return success
|
||||
}
|
||||
case reflect.Chan:
|
||||
if int64(value.Len()) == expectedLen {
|
||||
return success
|
||||
}
|
||||
case reflect.Map:
|
||||
if int64(value.Len()) == expectedLen {
|
||||
return success
|
||||
}
|
||||
case reflect.String:
|
||||
if int64(value.Len()) == expectedLen {
|
||||
return success
|
||||
}
|
||||
case reflect.Ptr:
|
||||
elem := value.Elem()
|
||||
kind := elem.Kind()
|
||||
if (kind == reflect.Slice || kind == reflect.Array) && int64(elem.Len()) == expectedLen {
|
||||
return success
|
||||
}
|
||||
default:
|
||||
return fmt.Sprintf(shouldHaveBeenAValidCollection, reflect.TypeOf(actual))
|
||||
}
|
||||
|
||||
return fmt.Sprintf(shouldHaveHadLength, actual, expectedLen)
|
||||
}
|
99
vendor/github.com/smartystreets/assertions/doc.go
generated
vendored
Normal file
99
vendor/github.com/smartystreets/assertions/doc.go
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
// Package assertions contains the implementations for all assertions which
|
||||
// are referenced in goconvey's `convey` package
|
||||
// (github.com/smartystreets/goconvey/convey) for use with the So(...) method.
|
||||
// They can also be used in traditional Go test functions and even in
|
||||
// applicaitons.
|
||||
package assertions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// By default we use a no-op serializer. The actual Serializer provides a JSON
|
||||
// representation of failure results on selected assertions so the goconvey
|
||||
// web UI can display a convenient diff.
|
||||
var serializer Serializer = new(noopSerializer)
|
||||
|
||||
// GoConveyMode provides control over JSON serialization of failures. When
|
||||
// using the assertions in this package from the convey package JSON results
|
||||
// are very helpful and can be rendered in a DIFF view. In that case, this function
|
||||
// will be called with a true value to enable the JSON serialization. By default,
|
||||
// the assertions in this package will not serializer a JSON result, making
|
||||
// standalone ussage more convenient.
|
||||
func GoConveyMode(yes bool) {
|
||||
if yes {
|
||||
serializer = newSerializer()
|
||||
} else {
|
||||
serializer = new(noopSerializer)
|
||||
}
|
||||
}
|
||||
|
||||
type testingT interface {
|
||||
Error(args ...interface{})
|
||||
}
|
||||
|
||||
type Assertion struct {
|
||||
t testingT
|
||||
failed bool
|
||||
}
|
||||
|
||||
// New swallows the *testing.T struct and prints failed assertions using t.Error.
|
||||
// Example: assertions.New(t).So(1, should.Equal, 1)
|
||||
func New(t testingT) *Assertion {
|
||||
return &Assertion{t: t}
|
||||
}
|
||||
|
||||
// Failed reports whether any calls to So (on this Assertion instance) have failed.
|
||||
func (this *Assertion) Failed() bool {
|
||||
return this.failed
|
||||
}
|
||||
|
||||
// So calls the standalone So function and additionally, calls t.Error in failure scenarios.
|
||||
func (this *Assertion) So(actual interface{}, assert assertion, expected ...interface{}) bool {
|
||||
ok, result := So(actual, assert, expected...)
|
||||
if !ok {
|
||||
this.failed = true
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
this.t.Error(fmt.Sprintf("\n%s:%d\n%s", file, line, result))
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// So is a convenience function (as opposed to an inconvenience function?)
|
||||
// for running assertions on arbitrary arguments in any context, be it for testing or even
|
||||
// application logging. It allows you to perform assertion-like behavior (and get nicely
|
||||
// formatted messages detailing discrepancies) but without the program blowing up or panicking.
|
||||
// All that is required is to import this package and call `So` with one of the assertions
|
||||
// exported by this package as the second parameter.
|
||||
// The first return parameter is a boolean indicating if the assertion was true. The second
|
||||
// return parameter is the well-formatted message showing why an assertion was incorrect, or
|
||||
// blank if the assertion was correct.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// if ok, message := So(x, ShouldBeGreaterThan, y); !ok {
|
||||
// log.Println(message)
|
||||
// }
|
||||
//
|
||||
func So(actual interface{}, assert assertion, expected ...interface{}) (bool, string) {
|
||||
if result := so(actual, assert, expected...); len(result) == 0 {
|
||||
return true, result
|
||||
} else {
|
||||
return false, result
|
||||
}
|
||||
}
|
||||
|
||||
// so is like So, except that it only returns the string message, which is blank if the
|
||||
// assertion passed. Used to facilitate testing.
|
||||
func so(actual interface{}, assert func(interface{}, ...interface{}) string, expected ...interface{}) string {
|
||||
return assert(actual, expected...)
|
||||
}
|
||||
|
||||
// assertion is an alias for a function with a signature that the So()
|
||||
// function can handle. Any future or custom assertions should conform to this
|
||||
// method signature. The return value should be an empty string if the assertion
|
||||
// passes and a well-formed failure message if not.
|
||||
type assertion func(actual interface{}, expected ...interface{}) string
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
286
vendor/github.com/smartystreets/assertions/equality.go
generated
vendored
Normal file
286
vendor/github.com/smartystreets/assertions/equality.go
generated
vendored
Normal file
@ -0,0 +1,286 @@
|
||||
package assertions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/smartystreets/assertions/internal/oglematchers"
|
||||
)
|
||||
|
||||
// default acceptable delta for ShouldAlmostEqual
|
||||
const defaultDelta = 0.0000000001
|
||||
|
||||
// ShouldEqual receives exactly two parameters and does an equality check.
|
||||
func ShouldEqual(actual interface{}, expected ...interface{}) string {
|
||||
if message := need(1, expected); message != success {
|
||||
return message
|
||||
}
|
||||
return shouldEqual(actual, expected[0])
|
||||
}
|
||||
func shouldEqual(actual, expected interface{}) (message string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
message = serializer.serialize(expected, actual, fmt.Sprintf(shouldHaveBeenEqual, expected, actual))
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if matchError := oglematchers.Equals(expected).Matches(actual); matchError != nil {
|
||||
expectedSyntax := fmt.Sprintf("%v", expected)
|
||||
actualSyntax := fmt.Sprintf("%v", actual)
|
||||
if expectedSyntax == actualSyntax && reflect.TypeOf(expected) != reflect.TypeOf(actual) {
|
||||
message = fmt.Sprintf(shouldHaveBeenEqualTypeMismatch, expected, expected, actual, actual)
|
||||
} else {
|
||||
message = fmt.Sprintf(shouldHaveBeenEqual, expected, actual)
|
||||
}
|
||||
message = serializer.serialize(expected, actual, message)
|
||||
return
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotEqual receives exactly two parameters and does an inequality check.
|
||||
func ShouldNotEqual(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
} else if ShouldEqual(actual, expected[0]) == success {
|
||||
return fmt.Sprintf(shouldNotHaveBeenEqual, actual, expected[0])
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldAlmostEqual makes sure that two parameters are close enough to being equal.
|
||||
// The acceptable delta may be specified with a third argument,
|
||||
// or a very small default delta will be used.
|
||||
func ShouldAlmostEqual(actual interface{}, expected ...interface{}) string {
|
||||
actualFloat, expectedFloat, deltaFloat, err := cleanAlmostEqualInput(actual, expected...)
|
||||
|
||||
if err != "" {
|
||||
return err
|
||||
}
|
||||
|
||||
if math.Abs(actualFloat-expectedFloat) <= deltaFloat {
|
||||
return success
|
||||
} else {
|
||||
return fmt.Sprintf(shouldHaveBeenAlmostEqual, actualFloat, expectedFloat)
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldNotAlmostEqual is the inverse of ShouldAlmostEqual
|
||||
func ShouldNotAlmostEqual(actual interface{}, expected ...interface{}) string {
|
||||
actualFloat, expectedFloat, deltaFloat, err := cleanAlmostEqualInput(actual, expected...)
|
||||
|
||||
if err != "" {
|
||||
return err
|
||||
}
|
||||
|
||||
if math.Abs(actualFloat-expectedFloat) > deltaFloat {
|
||||
return success
|
||||
} else {
|
||||
return fmt.Sprintf(shouldHaveNotBeenAlmostEqual, actualFloat, expectedFloat)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanAlmostEqualInput(actual interface{}, expected ...interface{}) (float64, float64, float64, string) {
|
||||
deltaFloat := 0.0000000001
|
||||
|
||||
if len(expected) == 0 {
|
||||
return 0.0, 0.0, 0.0, "This assertion requires exactly one comparison value and an optional delta (you provided neither)"
|
||||
} else if len(expected) == 2 {
|
||||
delta, err := getFloat(expected[1])
|
||||
|
||||
if err != nil {
|
||||
return 0.0, 0.0, 0.0, "delta must be a numerical type"
|
||||
}
|
||||
|
||||
deltaFloat = delta
|
||||
} else if len(expected) > 2 {
|
||||
return 0.0, 0.0, 0.0, "This assertion requires exactly one comparison value and an optional delta (you provided more values)"
|
||||
}
|
||||
|
||||
actualFloat, err := getFloat(actual)
|
||||
|
||||
if err != nil {
|
||||
return 0.0, 0.0, 0.0, err.Error()
|
||||
}
|
||||
|
||||
expectedFloat, err := getFloat(expected[0])
|
||||
|
||||
if err != nil {
|
||||
return 0.0, 0.0, 0.0, err.Error()
|
||||
}
|
||||
|
||||
return actualFloat, expectedFloat, deltaFloat, ""
|
||||
}
|
||||
|
||||
// returns the float value of any real number, or error if it is not a numerical type
|
||||
func getFloat(num interface{}) (float64, error) {
|
||||
numValue := reflect.ValueOf(num)
|
||||
numKind := numValue.Kind()
|
||||
|
||||
if numKind == reflect.Int ||
|
||||
numKind == reflect.Int8 ||
|
||||
numKind == reflect.Int16 ||
|
||||
numKind == reflect.Int32 ||
|
||||
numKind == reflect.Int64 {
|
||||
return float64(numValue.Int()), nil
|
||||
} else if numKind == reflect.Uint ||
|
||||
numKind == reflect.Uint8 ||
|
||||
numKind == reflect.Uint16 ||
|
||||
numKind == reflect.Uint32 ||
|
||||
numKind == reflect.Uint64 {
|
||||
return float64(numValue.Uint()), nil
|
||||
} else if numKind == reflect.Float32 ||
|
||||
numKind == reflect.Float64 {
|
||||
return numValue.Float(), nil
|
||||
} else {
|
||||
return 0.0, errors.New("must be a numerical type, but was " + numKind.String())
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldResemble receives exactly two parameters and does a deep equal check (see reflect.DeepEqual)
|
||||
func ShouldResemble(actual interface{}, expected ...interface{}) string {
|
||||
if message := need(1, expected); message != success {
|
||||
return message
|
||||
}
|
||||
|
||||
if matchError := oglematchers.DeepEquals(expected[0]).Matches(actual); matchError != nil {
|
||||
expectedSyntax := fmt.Sprintf("%#v", expected[0])
|
||||
actualSyntax := fmt.Sprintf("%#v", actual)
|
||||
var message string
|
||||
if expectedSyntax == actualSyntax {
|
||||
message = fmt.Sprintf(shouldHaveResembledTypeMismatch, expected[0], expected[0], actual, actual)
|
||||
} else {
|
||||
message = fmt.Sprintf(shouldHaveResembled, expected[0], actual)
|
||||
}
|
||||
return serializer.serializeDetailed(expected[0], actual, message)
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotResemble receives exactly two parameters and does an inverse deep equal check (see reflect.DeepEqual)
|
||||
func ShouldNotResemble(actual interface{}, expected ...interface{}) string {
|
||||
if message := need(1, expected); message != success {
|
||||
return message
|
||||
} else if ShouldResemble(actual, expected[0]) == success {
|
||||
return fmt.Sprintf(shouldNotHaveResembled, actual, expected[0])
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldPointTo receives exactly two parameters and checks to see that they point to the same address.
|
||||
func ShouldPointTo(actual interface{}, expected ...interface{}) string {
|
||||
if message := need(1, expected); message != success {
|
||||
return message
|
||||
}
|
||||
return shouldPointTo(actual, expected[0])
|
||||
|
||||
}
|
||||
func shouldPointTo(actual, expected interface{}) string {
|
||||
actualValue := reflect.ValueOf(actual)
|
||||
expectedValue := reflect.ValueOf(expected)
|
||||
|
||||
if ShouldNotBeNil(actual) != success {
|
||||
return fmt.Sprintf(shouldHaveBeenNonNilPointer, "first", "nil")
|
||||
} else if ShouldNotBeNil(expected) != success {
|
||||
return fmt.Sprintf(shouldHaveBeenNonNilPointer, "second", "nil")
|
||||
} else if actualValue.Kind() != reflect.Ptr {
|
||||
return fmt.Sprintf(shouldHaveBeenNonNilPointer, "first", "not")
|
||||
} else if expectedValue.Kind() != reflect.Ptr {
|
||||
return fmt.Sprintf(shouldHaveBeenNonNilPointer, "second", "not")
|
||||
} else if ShouldEqual(actualValue.Pointer(), expectedValue.Pointer()) != success {
|
||||
actualAddress := reflect.ValueOf(actual).Pointer()
|
||||
expectedAddress := reflect.ValueOf(expected).Pointer()
|
||||
return serializer.serialize(expectedAddress, actualAddress, fmt.Sprintf(shouldHavePointedTo,
|
||||
actual, actualAddress,
|
||||
expected, expectedAddress))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotPointTo receives exactly two parameters and checks to see that they point to different addresess.
|
||||
func ShouldNotPointTo(actual interface{}, expected ...interface{}) string {
|
||||
if message := need(1, expected); message != success {
|
||||
return message
|
||||
}
|
||||
compare := ShouldPointTo(actual, expected[0])
|
||||
if strings.HasPrefix(compare, shouldBePointers) {
|
||||
return compare
|
||||
} else if compare == success {
|
||||
return fmt.Sprintf(shouldNotHavePointedTo, actual, expected[0], reflect.ValueOf(actual).Pointer())
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeNil receives a single parameter and ensures that it is nil.
|
||||
func ShouldBeNil(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
} else if actual == nil {
|
||||
return success
|
||||
} else if interfaceHasNilValue(actual) {
|
||||
return success
|
||||
}
|
||||
return fmt.Sprintf(shouldHaveBeenNil, actual)
|
||||
}
|
||||
func interfaceHasNilValue(actual interface{}) bool {
|
||||
value := reflect.ValueOf(actual)
|
||||
kind := value.Kind()
|
||||
nilable := kind == reflect.Slice ||
|
||||
kind == reflect.Chan ||
|
||||
kind == reflect.Func ||
|
||||
kind == reflect.Ptr ||
|
||||
kind == reflect.Map
|
||||
|
||||
// Careful: reflect.Value.IsNil() will panic unless it's an interface, chan, map, func, slice, or ptr
|
||||
// Reference: http://golang.org/pkg/reflect/#Value.IsNil
|
||||
return nilable && value.IsNil()
|
||||
}
|
||||
|
||||
// ShouldNotBeNil receives a single parameter and ensures that it is not nil.
|
||||
func ShouldNotBeNil(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
} else if ShouldBeNil(actual) == success {
|
||||
return fmt.Sprintf(shouldNotHaveBeenNil, actual)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeTrue receives a single parameter and ensures that it is true.
|
||||
func ShouldBeTrue(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
} else if actual != true {
|
||||
return fmt.Sprintf(shouldHaveBeenTrue, actual)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeFalse receives a single parameter and ensures that it is false.
|
||||
func ShouldBeFalse(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
} else if actual != false {
|
||||
return fmt.Sprintf(shouldHaveBeenFalse, actual)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeZeroValue receives a single parameter and ensures that it is
|
||||
// the Go equivalent of the default value, or "zero" value.
|
||||
func ShouldBeZeroValue(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
zeroVal := reflect.Zero(reflect.TypeOf(actual)).Interface()
|
||||
if !reflect.DeepEqual(zeroVal, actual) {
|
||||
return serializer.serialize(zeroVal, actual, fmt.Sprintf(shouldHaveBeenZeroValue, actual))
|
||||
}
|
||||
return success
|
||||
}
|
23
vendor/github.com/smartystreets/assertions/filter.go
generated
vendored
Normal file
23
vendor/github.com/smartystreets/assertions/filter.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
package assertions
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
success = ""
|
||||
needExactValues = "This assertion requires exactly %d comparison values (you provided %d)."
|
||||
needNonEmptyCollection = "This assertion requires at least 1 comparison value (you provided 0)."
|
||||
)
|
||||
|
||||
func need(needed int, expected []interface{}) string {
|
||||
if len(expected) != needed {
|
||||
return fmt.Sprintf(needExactValues, needed, len(expected))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
func atLeast(minimum int, expected []interface{}) string {
|
||||
if len(expected) < 1 {
|
||||
return needNonEmptyCollection
|
||||
}
|
||||
return success
|
||||
}
|
202
vendor/github.com/smartystreets/assertions/internal/oglematchers/LICENSE
generated
vendored
Normal file
202
vendor/github.com/smartystreets/assertions/internal/oglematchers/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user