mirror of
https://github.com/kevin-DL/revel-cmd.git
synced 2026-01-11 18:54:31 +00:00
Initial rewrite of revel/cmd to provide vendor support and enhanced CLI options
This commit is contained in:
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (C) 2012-2016 The Revel Framework Authors.
|
Copyright (C) 2012-2018 The Revel Framework Authors.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
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
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// App contains the configuration for running a Revel app. (Not for the app itself)
|
// App contains the configuration for running a Revel app. (Not for the app itself)
|
||||||
@@ -22,16 +24,17 @@ type App struct {
|
|||||||
BinaryPath string // Path to the app executable
|
BinaryPath string // Path to the app executable
|
||||||
Port int // Port to pass as a command line argument.
|
Port int // Port to pass as a command line argument.
|
||||||
cmd AppCmd // The last cmd returned.
|
cmd AppCmd // The last cmd returned.
|
||||||
|
Paths *model.RevelContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApp returns app instance with binary path in it
|
// NewApp returns app instance with binary path in it
|
||||||
func NewApp(binPath string) *App {
|
func NewApp(binPath string, paths *model.RevelContainer) *App {
|
||||||
return &App{BinaryPath: binPath}
|
return &App{BinaryPath: binPath, Paths: paths, Port: paths.HTTPPort}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cmd returns a command to run the app server using the current configuration.
|
// Cmd returns a command to run the app server using the current configuration.
|
||||||
func (a *App) Cmd() AppCmd {
|
func (a *App) Cmd(runMode string) AppCmd {
|
||||||
a.cmd = NewAppCmd(a.BinaryPath, a.Port)
|
a.cmd = NewAppCmd(a.BinaryPath, a.Port, runMode, a.Paths)
|
||||||
return a.cmd
|
return a.cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,22 +50,22 @@ type AppCmd struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewAppCmd returns the AppCmd with parameters initialized for running app
|
// NewAppCmd returns the AppCmd with parameters initialized for running app
|
||||||
func NewAppCmd(binPath string, port int) AppCmd {
|
func NewAppCmd(binPath string, port int, runMode string, paths *model.RevelContainer) AppCmd {
|
||||||
cmd := exec.Command(binPath,
|
cmd := exec.Command(binPath,
|
||||||
fmt.Sprintf("-port=%d", port),
|
fmt.Sprintf("-port=%d", port),
|
||||||
fmt.Sprintf("-importPath=%s", revel.ImportPath),
|
fmt.Sprintf("-importPath=%s", paths.ImportPath),
|
||||||
fmt.Sprintf("-runMode=%s", revel.RunMode))
|
fmt.Sprintf("-runMode=%s", runMode))
|
||||||
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||||
return AppCmd{cmd}
|
return AppCmd{cmd}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the app server, and wait until it is ready to serve requests.
|
// Start the app server, and wait until it is ready to serve requests.
|
||||||
func (cmd AppCmd) Start() error {
|
func (cmd AppCmd) Start(c *model.CommandConfig) error {
|
||||||
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool)}
|
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool),c}
|
||||||
cmd.Stdout = listeningWriter
|
cmd.Stdout = listeningWriter
|
||||||
revel.RevelLog.Debug("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
||||||
if err := cmd.Cmd.Start(); err != nil {
|
if err := cmd.Cmd.Start(); err != nil {
|
||||||
revel.RevelLog.Fatal("Error running:", "error", err)
|
utils.Logger.Fatal("Error running:", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@@ -70,7 +73,7 @@ func (cmd AppCmd) Start() error {
|
|||||||
return errors.New("revel/harness: app died")
|
return errors.New("revel/harness: app died")
|
||||||
|
|
||||||
case <-time.After(30 * time.Second):
|
case <-time.After(30 * time.Second):
|
||||||
revel.RevelLog.Debug("Killing revel server process did not respond after wait timeout", "processid", cmd.Process.Pid)
|
log.Println("Killing revel server process did not respond after wait timeout", "processid", cmd.Process.Pid)
|
||||||
cmd.Kill()
|
cmd.Kill()
|
||||||
return errors.New("revel/harness: app timed out")
|
return errors.New("revel/harness: app timed out")
|
||||||
|
|
||||||
@@ -84,19 +87,19 @@ func (cmd AppCmd) Start() error {
|
|||||||
|
|
||||||
// Run the app server inline. Never returns.
|
// Run the app server inline. Never returns.
|
||||||
func (cmd AppCmd) Run() {
|
func (cmd AppCmd) Run() {
|
||||||
revel.RevelLog.Debug("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
log.Println("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
||||||
if err := cmd.Cmd.Run(); err != nil {
|
if err := cmd.Cmd.Run(); err != nil {
|
||||||
revel.RevelLog.Fatal("Error running:", "error", err)
|
utils.Logger.Fatal("Error running:", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kill terminates the app server if it's running.
|
// Kill terminates the app server if it's running.
|
||||||
func (cmd AppCmd) Kill() {
|
func (cmd AppCmd) Kill() {
|
||||||
if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) {
|
if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) {
|
||||||
revel.RevelLog.Debug("Killing revel server pid", "pid", cmd.Process.Pid)
|
utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid)
|
||||||
err := cmd.Process.Kill()
|
err := cmd.Process.Kill()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Fatal("Failed to kill revel server:", "error", err)
|
utils.Logger.Fatal("Failed to kill revel server:", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,18 +114,25 @@ func (cmd AppCmd) waitChan() <-chan struct{} {
|
|||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
// A io.Writer that copies to the destination, and listens for "Listening on.."
|
// A io.Writer that copies to the destination, and listens for "Revel engine is listening on.."
|
||||||
// in the stream. (Which tells us when the revel server has finished starting up)
|
// in the stream. (Which tells us when the revel server has finished starting up)
|
||||||
// This is super ghetto, but by far the simplest thing that should work.
|
// This is super ghetto, but by far the simplest thing that should work.
|
||||||
type startupListeningWriter struct {
|
type startupListeningWriter struct {
|
||||||
dest io.Writer
|
dest io.Writer
|
||||||
notifyReady chan bool
|
notifyReady chan bool
|
||||||
|
c *model.CommandConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *startupListeningWriter) Write(p []byte) (n int, err error) {
|
func (w *startupListeningWriter) Write(p []byte) (int, error) {
|
||||||
if w.notifyReady != nil && bytes.Contains(p, []byte("Listening")) {
|
if w.notifyReady != nil && bytes.Contains(p, []byte("Revel engine is listening on")) {
|
||||||
w.notifyReady <- true
|
w.notifyReady <- true
|
||||||
w.notifyReady = nil
|
w.notifyReady = nil
|
||||||
}
|
}
|
||||||
|
if w.c.HistoricMode {
|
||||||
|
if w.notifyReady != nil && bytes.Contains(p, []byte("Listening on")) {
|
||||||
|
w.notifyReady <- true
|
||||||
|
w.notifyReady = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
return w.dest.Write(p)
|
return w.dest.Write(p)
|
||||||
}
|
}
|
||||||
|
|||||||
227
harness/build.go
227
harness/build.go
@@ -13,18 +13,19 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
"time"
|
"time"
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/parser"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var importErrorPattern = regexp.MustCompile("cannot find package \"([^\"]+)\"")
|
var importErrorPattern = regexp.MustCompile("cannot find package \"([^\"]+)\"")
|
||||||
|
|
||||||
type ByString []*TypeInfo
|
type ByString []*model.TypeInfo
|
||||||
|
|
||||||
func (c ByString) Len() int { return len(c) }
|
func (c ByString) Len() int { return len(c) }
|
||||||
func (c ByString) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
func (c ByString) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||||
@@ -35,17 +36,17 @@ func (c ByString) Less(i, j int) bool { return c[i].String() < c[j].String() }
|
|||||||
// 2. Run the appropriate "go build" command.
|
// 2. Run the appropriate "go build" command.
|
||||||
// Requires that revel.Init has been called previously.
|
// Requires that revel.Init has been called previously.
|
||||||
// Returns the path to the built binary, and an error if there was a problem building it.
|
// Returns the path to the built binary, and an error if there was a problem building it.
|
||||||
func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
func Build(c *model.CommandConfig, paths *model.RevelContainer, buildFlags ...string) (app *App, compileError *utils.Error) {
|
||||||
// First, clear the generated files (to avoid them messing with ProcessSource).
|
// First, clear the generated files (to avoid them messing with ProcessSource).
|
||||||
cleanSource("tmp", "routes")
|
cleanSource(paths, "tmp", "routes")
|
||||||
|
|
||||||
sourceInfo, compileError := ProcessSource(revel.CodePaths)
|
sourceInfo, compileError := parser.ProcessSource(paths)
|
||||||
if compileError != nil {
|
if compileError != nil {
|
||||||
return nil, compileError
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the db.import to the import paths.
|
// Add the db.import to the import paths.
|
||||||
if dbImportPath, found := revel.Config.String("db.import"); found {
|
if dbImportPath, found := paths.Config.String("db.import"); found {
|
||||||
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, strings.Split(dbImportPath, ",")...)
|
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, strings.Split(dbImportPath, ",")...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,28 +61,28 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
|||||||
"ImportPaths": calcImportAliases(sourceInfo),
|
"ImportPaths": calcImportAliases(sourceInfo),
|
||||||
"TestSuites": sourceInfo.TestSuites(),
|
"TestSuites": sourceInfo.TestSuites(),
|
||||||
}
|
}
|
||||||
genSource("tmp", "main.go", RevelMainTemplate, templateArgs)
|
genSource(paths, "tmp", "main.go", RevelMainTemplate, templateArgs)
|
||||||
genSource("routes", "routes.go", RevelRoutesTemplate, templateArgs)
|
genSource(paths, "routes", "routes.go", RevelRoutesTemplate, templateArgs)
|
||||||
|
|
||||||
// Read build config.
|
// Read build config.
|
||||||
buildTags := revel.Config.StringDefault("build.tags", "")
|
buildTags := paths.Config.StringDefault("build.tags", "")
|
||||||
|
|
||||||
// Build the user program (all code under app).
|
// Build the user program (all code under app).
|
||||||
// It relies on the user having "go" installed.
|
// It relies on the user having "go" installed.
|
||||||
goPath, err := exec.LookPath("go")
|
goPath, err := exec.LookPath("go")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Fatalf("Go executable not found in PATH.")
|
utils.Logger.Fatal("Go executable not found in PATH.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect if deps tool should be used (is there a vendor folder ?)
|
// Detect if deps tool should be used (is there a vendor folder ?)
|
||||||
useVendor := revel.DirExists(filepath.Join(revel.BasePath, "vendor"))
|
useVendor := utils.DirExists(filepath.Join(paths.BasePath, "vendor"))
|
||||||
basePath := revel.BasePath
|
basePath := paths.BasePath
|
||||||
for !useVendor {
|
for !useVendor {
|
||||||
basePath = filepath.Dir(basePath)
|
basePath = filepath.Dir(basePath)
|
||||||
found := false
|
found := false
|
||||||
// Check to see if we are still in the GOPATH
|
// Check to see if we are still in the GOPATH
|
||||||
for _, path := range filepath.SplitList(build.Default.GOPATH) {
|
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
|
||||||
if strings.HasPrefix(basePath, path) {
|
if strings.HasPrefix(basePath, gopath) {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -89,31 +90,31 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
|||||||
if !found {
|
if !found {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
useVendor = revel.DirExists(filepath.Join(basePath, "vendor"))
|
useVendor = utils.DirExists(filepath.Join(basePath, "vendor"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var depPath string
|
var depPath string
|
||||||
if useVendor {
|
if useVendor {
|
||||||
revel.RevelLog.Info("Vendor folder detected, scanning for deps in path")
|
utils.Logger.Info("Vendor folder detected, scanning for deps in path")
|
||||||
depPath, err = exec.LookPath("dep")
|
depPath, err = exec.LookPath("dep")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Do not halt build unless a new package needs to be imported
|
// Do not halt build unless a new package needs to be imported
|
||||||
revel.RevelLog.Warn("Build: `dep` executable not found in PATH, but vendor folder detected." +
|
utils.Logger.Warn("Build: `dep` executable not found in PATH, but vendor folder detected." +
|
||||||
"Packages can only be added automatically to the vendor folder using the `dep` tool. " +
|
"Packages can only be added automatically to the vendor folder using the `dep` tool. " +
|
||||||
"You can install the `dep` tool by doing a `go get -u github.com/golang/dep/cmd/dep`")
|
"You can install the `dep` tool by doing a `go get -u github.com/golang/dep/cmd/dep`")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
revel.RevelLog.Info("No vendor folder detected, not using dependency manager to import files")
|
utils.Logger.Info("No vendor folder detected, not using dependency manager to import files")
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg, err := build.Default.Import(revel.ImportPath, "", build.FindOnly)
|
pkg, err := build.Default.Import(paths.ImportPath, "", build.FindOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Fatal("Failure importing", "path", revel.ImportPath)
|
utils.Logger.Fatal("Failure importing", "path", paths.ImportPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Binary path is a combination of $GOBIN/revel.d directory, app's import path and its name.
|
// Binary path is a combination of $GOBIN/revel.d directory, app's import path and its name.
|
||||||
binName := filepath.Join(pkg.BinDir, "revel.d", revel.ImportPath, filepath.Base(revel.BasePath))
|
binName := filepath.Join(pkg.BinDir, "revel.d", paths.ImportPath, filepath.Base(paths.BasePath))
|
||||||
|
|
||||||
// Change binary path for Windows build
|
// Change binary path for Windows build
|
||||||
goos := runtime.GOOS
|
goos := runtime.GOOS
|
||||||
@@ -125,47 +126,90 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gotten := make(map[string]struct{})
|
gotten := make(map[string]struct{})
|
||||||
|
contains := func (s []string, e string) bool {
|
||||||
|
for _, a := range s {
|
||||||
|
if a == e {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
appVersion := getAppVersion()
|
appVersion := getAppVersion(paths)
|
||||||
|
|
||||||
buildTime := time.Now().UTC().Format(time.RFC3339)
|
buildTime := time.Now().UTC().Format(time.RFC3339)
|
||||||
versionLinkerFlags := fmt.Sprintf("-X %s/app.AppVersion=%s -X %s/app.BuildTime=%s",
|
versionLinkerFlags := fmt.Sprintf("-X %s/app.AppVersion=%s -X %s/app.BuildTime=%s",
|
||||||
revel.ImportPath, appVersion, revel.ImportPath, buildTime)
|
paths.ImportPath, appVersion, paths.ImportPath, buildTime)
|
||||||
|
|
||||||
flags := []string{
|
// Append any build flags specified, they will override existing flags
|
||||||
|
flags := []string{}
|
||||||
|
if len(c.BuildFlags)==0 {
|
||||||
|
flags = []string{
|
||||||
"build",
|
"build",
|
||||||
"-i",
|
"-i",
|
||||||
"-ldflags", versionLinkerFlags,
|
"-ldflags", versionLinkerFlags,
|
||||||
"-tags", buildTags,
|
"-tags", buildTags,
|
||||||
"-o", binName}
|
"-o", binName}
|
||||||
|
} else {
|
||||||
|
if !contains(c.BuildFlags,"build") {
|
||||||
|
flags = []string{"build"}
|
||||||
|
}
|
||||||
|
flags = append(flags,c.BuildFlags...)
|
||||||
|
if !contains(flags, "-ldflags") {
|
||||||
|
flags = append(flags,"-ldflags", versionLinkerFlags)
|
||||||
|
}
|
||||||
|
if !contains(flags, "-tags") {
|
||||||
|
flags = append(flags,"-tags", buildTags)
|
||||||
|
}
|
||||||
|
if !contains(flags, "-o") {
|
||||||
|
flags = append(flags,"-o", binName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Add in build flags
|
// Add in build flags
|
||||||
flags = append(flags, buildFlags...)
|
flags = append(flags, buildFlags...)
|
||||||
|
|
||||||
// This is Go main path
|
// This is Go main path
|
||||||
|
gopath := c.GoPath
|
||||||
|
for _, o := range paths.ModulePathMap {
|
||||||
|
gopath += string(filepath.ListSeparator) + o
|
||||||
|
}
|
||||||
|
|
||||||
// Note: It's not applicable for filepath.* usage
|
// Note: It's not applicable for filepath.* usage
|
||||||
flags = append(flags, path.Join(revel.ImportPath, "app", "tmp"))
|
flags = append(flags, path.Join(paths.ImportPath, "app", "tmp"))
|
||||||
|
|
||||||
buildCmd := exec.Command(goPath, flags...)
|
buildCmd := exec.Command(goPath, flags...)
|
||||||
revel.RevelLog.Debug("Exec:", "args", buildCmd.Args)
|
buildCmd.Env = append(os.Environ(),
|
||||||
|
"GOPATH="+gopath,
|
||||||
|
)
|
||||||
|
utils.Logger.Info("Exec:", "args", buildCmd.Args)
|
||||||
output, err := buildCmd.CombinedOutput()
|
output, err := buildCmd.CombinedOutput()
|
||||||
|
|
||||||
// If the build succeeded, we're done.
|
// If the build succeeded, we're done.
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return NewApp(binName), nil
|
return NewApp(binName, paths), nil
|
||||||
}
|
}
|
||||||
revel.RevelLog.Error(string(output))
|
|
||||||
|
// Since there was an error, capture the output in case we need to report it
|
||||||
|
stOutput := string(output)
|
||||||
|
|
||||||
// See if it was an import error that we can go get.
|
// See if it was an import error that we can go get.
|
||||||
matches := importErrorPattern.FindAllStringSubmatch(string(output), -1)
|
matches := importErrorPattern.FindAllStringSubmatch(stOutput, -1)
|
||||||
|
utils.Logger.Info("Build failed checking for missing imports", "message", stOutput, "missing_imports", len(matches))
|
||||||
if matches == nil {
|
if matches == nil {
|
||||||
return nil, newCompileError(output)
|
utils.Logger.Info("Build failed no missing imports", "message", stOutput)
|
||||||
|
return nil, newCompileError(paths, output)
|
||||||
}
|
}
|
||||||
|
utils.Logger.Warn("Detected missing packages, importing them", "packages", len(matches))
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
// Ensure we haven't already tried to go get it.
|
// Ensure we haven't already tried to go get it.
|
||||||
pkgName := match[1]
|
pkgName := match[1]
|
||||||
|
utils.Logger.Info("Trying to import ", "package", pkgName)
|
||||||
if _, alreadyTried := gotten[pkgName]; alreadyTried {
|
if _, alreadyTried := gotten[pkgName]; alreadyTried {
|
||||||
return nil, newCompileError(output)
|
utils.Logger.Error("Failed to import ", "package", pkgName)
|
||||||
|
return nil, newCompileError(paths, output)
|
||||||
}
|
}
|
||||||
gotten[pkgName] = struct{}{}
|
gotten[pkgName] = struct{}{}
|
||||||
|
|
||||||
@@ -174,23 +218,26 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
|||||||
var getCmd *exec.Cmd
|
var getCmd *exec.Cmd
|
||||||
if useVendor {
|
if useVendor {
|
||||||
if depPath == "" {
|
if depPath == "" {
|
||||||
revel.RevelLog.Error("Build: Vendor folder found, but the `dep` tool was not found, " +
|
utils.Logger.Warn("Build: Vendor folder found, but the `dep` tool was not found, " +
|
||||||
"if you use a different vendoring (package management) tool please add the following packages by hand, " +
|
"if you use a different vendoring (package management) tool please add the following packages by hand, " +
|
||||||
"or install the `dep` tool into your gopath by doing a `go get -u github.com/golang/dep/cmd/dep`. " +
|
"or install the `dep` tool into your gopath by doing a `go get -u github.com/golang/dep/cmd/dep`. " +
|
||||||
"For more information and usage of the tool please see http://github.com/golang/dep")
|
"For more information and usage of the tool please see http://github.com/golang/dep")
|
||||||
for _, pkg := range matches {
|
for _, pkg := range matches {
|
||||||
revel.RevelLog.Error("Missing package", "package", pkg[1])
|
utils.Logger.Warn("Missing package", "package", pkg[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getCmd = exec.Command(depPath, "ensure", "-add", pkgName)
|
getCmd = exec.Command(depPath, "ensure", "-add", pkgName)
|
||||||
|
getCmd.Dir = paths.AppPath
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
getCmd = exec.Command(goPath, "get", pkgName)
|
getCmd = exec.Command(goPath, "get", pkgName)
|
||||||
}
|
}
|
||||||
revel.RevelLog.Debug("Exec:", "args", getCmd.Args)
|
utils.Logger.Info("Exec:", "args", getCmd.Args)
|
||||||
getOutput, err := getCmd.CombinedOutput()
|
getOutput, err := getCmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Error(string(getOutput))
|
utils.Logger.Error("Build failed", "message", stOutput)
|
||||||
return nil, newCompileError(output)
|
utils.Logger.Error("Failed to fetch the output", string(getOutput))
|
||||||
|
return nil, newCompileError(paths, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +245,7 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove this unreachable code and document it
|
// TODO remove this unreachable code and document it
|
||||||
revel.RevelLog.Fatalf("Not reachable")
|
utils.Logger.Fatal("Not reachable")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +255,7 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
|||||||
// variable
|
// variable
|
||||||
// - Read the output of "git describe" if the source is in a git repository
|
// - Read the output of "git describe" if the source is in a git repository
|
||||||
// If no version can be determined, an empty string is returned.
|
// If no version can be determined, an empty string is returned.
|
||||||
func getAppVersion() string {
|
func getAppVersion(paths *model.RevelContainer) string {
|
||||||
if version := os.Getenv("APP_VERSION"); version != "" {
|
if version := os.Getenv("APP_VERSION"); version != "" {
|
||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
@@ -216,17 +263,17 @@ func getAppVersion() string {
|
|||||||
// Check for the git binary
|
// Check for the git binary
|
||||||
if gitPath, err := exec.LookPath("git"); err == nil {
|
if gitPath, err := exec.LookPath("git"); err == nil {
|
||||||
// Check for the .git directory
|
// Check for the .git directory
|
||||||
gitDir := filepath.Join(revel.BasePath, ".git")
|
gitDir := filepath.Join(paths.BasePath, ".git")
|
||||||
info, err := os.Stat(gitDir)
|
info, err := os.Stat(gitDir)
|
||||||
if (err != nil && os.IsNotExist(err)) || !info.IsDir() {
|
if (err != nil && os.IsNotExist(err)) || !info.IsDir() {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
gitCmd := exec.Command(gitPath, "--git-dir="+gitDir, "--work-tree="+revel.BasePath, "describe", "--always", "--dirty")
|
gitCmd := exec.Command(gitPath, "--git-dir="+gitDir, "--work-tree="+paths.BasePath, "describe", "--always", "--dirty")
|
||||||
revel.RevelLog.Debug("Exec:", "args", gitCmd.Args)
|
utils.Logger.Info("Exec:", "args", gitCmd.Args)
|
||||||
output, err := gitCmd.Output()
|
output, err := gitCmd.Output()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Warn("Cannot determine git repository version:", "error", err)
|
utils.Logger.Error("Cannot determine git repository version:", "error", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,19 +283,19 @@ func getAppVersion() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanSource(dirs ...string) {
|
func cleanSource(paths *model.RevelContainer, dirs ...string) {
|
||||||
for _, dir := range dirs {
|
for _, dir := range dirs {
|
||||||
cleanDir(dir)
|
cleanDir(paths, dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanDir(dir string) {
|
func cleanDir(paths *model.RevelContainer, dir string) {
|
||||||
revel.RevelLog.Info("Cleaning dir " + dir)
|
utils.Logger.Info("Cleaning dir ", "dir", dir)
|
||||||
tmpPath := filepath.Join(revel.AppPath, dir)
|
tmpPath := filepath.Join(paths.AppPath, dir)
|
||||||
f, err := os.Open(tmpPath)
|
f, err := os.Open(tmpPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
revel.RevelLog.Error("Failed to clean dir:", "error", err)
|
utils.Logger.Error("Failed to clean dir:", "error", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -258,7 +305,7 @@ func cleanDir(dir string) {
|
|||||||
infos, err := f.Readdir(0)
|
infos, err := f.Readdir(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
revel.RevelLog.Error("Failed to clean dir:", "error", err)
|
utils.Logger.Fatal("Failed to clean dir:", "error", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, info := range infos {
|
for _, info := range infos {
|
||||||
@@ -266,12 +313,12 @@ func cleanDir(dir string) {
|
|||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
err := os.RemoveAll(pathName)
|
err := os.RemoveAll(pathName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Error("Failed to remove dir:", "error", err)
|
utils.Logger.Fatal("Failed to remove dir:", "error", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err := os.Remove(pathName)
|
err := os.Remove(pathName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Error("Failed to remove file:", "error", err)
|
utils.Logger.Fatal("Failed to remove file:", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,39 +328,21 @@ func cleanDir(dir string) {
|
|||||||
|
|
||||||
// genSource renders the given template to produce source code, which it writes
|
// genSource renders the given template to produce source code, which it writes
|
||||||
// to the given directory and file.
|
// to the given directory and file.
|
||||||
func genSource(dir, filename, templateSource string, args map[string]interface{}) {
|
func genSource(paths *model.RevelContainer, dir, filename, templateSource string, args map[string]interface{}) {
|
||||||
sourceCode := revel.ExecuteTemplate(
|
cleanSource(paths, dir)
|
||||||
template.Must(template.New("").Parse(templateSource)),
|
|
||||||
args)
|
|
||||||
|
|
||||||
// Create a fresh dir.
|
err := utils.MustGenerateTemplate(filepath.Join(paths.AppPath, dir, filename), templateSource, args)
|
||||||
cleanSource(dir)
|
|
||||||
tmpPath := filepath.Join(revel.AppPath, dir)
|
|
||||||
err := os.Mkdir(tmpPath, 0777)
|
|
||||||
if err != nil && !os.IsExist(err) {
|
|
||||||
revel.RevelLog.Fatalf("Failed to make '%v' directory: %v", dir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the file
|
|
||||||
file, err := os.Create(filepath.Join(tmpPath, filename))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Fatalf("Failed to create file: %v", err)
|
utils.Logger.Fatal("Failed to generate template for source file", "error", err)
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = file.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _, err = file.WriteString(sourceCode); err != nil {
|
|
||||||
revel.RevelLog.Fatalf("Failed to write to file: %v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Looks through all the method args and returns a set of unique import paths
|
// Looks through all the method args and returns a set of unique import paths
|
||||||
// that cover all the method arg types.
|
// that cover all the method arg types.
|
||||||
// Additionally, assign package aliases when necessary to resolve ambiguity.
|
// Additionally, assign package aliases when necessary to resolve ambiguity.
|
||||||
func calcImportAliases(src *SourceInfo) map[string]string {
|
func calcImportAliases(src *model.SourceInfo) map[string]string {
|
||||||
aliases := make(map[string]string)
|
aliases := make(map[string]string)
|
||||||
typeArrays := [][]*TypeInfo{src.ControllerSpecs(), src.TestSuites()}
|
typeArrays := [][]*model.TypeInfo{src.ControllerSpecs(), src.TestSuites()}
|
||||||
for _, specs := range typeArrays {
|
for _, specs := range typeArrays {
|
||||||
for _, spec := range specs {
|
for _, spec := range specs {
|
||||||
addAlias(aliases, spec.ImportPath, spec.PackageName)
|
addAlias(aliases, spec.ImportPath, spec.PackageName)
|
||||||
@@ -340,6 +369,7 @@ func calcImportAliases(src *SourceInfo) map[string]string {
|
|||||||
return aliases
|
return aliases
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds an alias to the map of alias names
|
||||||
func addAlias(aliases map[string]string, importPath, pkgName string) {
|
func addAlias(aliases map[string]string, importPath, pkgName string) {
|
||||||
alias, ok := aliases[importPath]
|
alias, ok := aliases[importPath]
|
||||||
if ok {
|
if ok {
|
||||||
@@ -349,6 +379,7 @@ func addAlias(aliases map[string]string, importPath, pkgName string) {
|
|||||||
aliases[importPath] = alias
|
aliases[importPath] = alias
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generates a package alias
|
||||||
func makePackageAlias(aliases map[string]string, pkgName string) string {
|
func makePackageAlias(aliases map[string]string, pkgName string) string {
|
||||||
i := 0
|
i := 0
|
||||||
alias := pkgName
|
alias := pkgName
|
||||||
@@ -359,6 +390,7 @@ func makePackageAlias(aliases map[string]string, pkgName string) string {
|
|||||||
return alias
|
return alias
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if this value is in the map
|
||||||
func containsValue(m map[string]string, val string) bool {
|
func containsValue(m map[string]string, val string) bool {
|
||||||
for _, v := range m {
|
for _, v := range m {
|
||||||
if v == val {
|
if v == val {
|
||||||
@@ -370,15 +402,15 @@ func containsValue(m map[string]string, val string) bool {
|
|||||||
|
|
||||||
// Parse the output of the "go build" command.
|
// Parse the output of the "go build" command.
|
||||||
// Return a detailed Error.
|
// Return a detailed Error.
|
||||||
func newCompileError(output []byte) *revel.Error {
|
func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error {
|
||||||
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
|
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
|
||||||
FindSubmatch(output)
|
FindSubmatch(output)
|
||||||
if errorMatch == nil {
|
if errorMatch == nil {
|
||||||
errorMatch = regexp.MustCompile(`(?m)^(.*?)\:(\d+)\:\s(.*?)$`).FindSubmatch(output)
|
errorMatch = regexp.MustCompile(`(?m)^(.*?):(\d+):\s(.*?)$`).FindSubmatch(output)
|
||||||
|
|
||||||
if errorMatch == nil {
|
if errorMatch == nil {
|
||||||
revel.RevelLog.Error("Failed to parse build errors", "error", string(output))
|
utils.Logger.Error("Failed to parse build errors", "error", string(output))
|
||||||
return &revel.Error{
|
return &utils.Error{
|
||||||
SourceType: "Go code",
|
SourceType: "Go code",
|
||||||
Title: "Go Compilation Error",
|
Title: "Go Compilation Error",
|
||||||
Description: "See console for build error.",
|
Description: "See console for build error.",
|
||||||
@@ -387,16 +419,30 @@ func newCompileError(output []byte) *revel.Error {
|
|||||||
|
|
||||||
errorMatch = append(errorMatch, errorMatch[3])
|
errorMatch = append(errorMatch, errorMatch[3])
|
||||||
|
|
||||||
revel.RevelLog.Error("Build errors", "errors", string(output))
|
utils.Logger.Error("Build errors", "errors", string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
findInPaths := func(relFilename string) string {
|
||||||
|
// Extract the paths from the gopaths, and search for file there first
|
||||||
|
gopaths := filepath.SplitList(build.Default.GOPATH)
|
||||||
|
for _, gp := range gopaths {
|
||||||
|
newPath := filepath.Join(gp, relFilename)
|
||||||
|
if utils.Exists(newPath) {
|
||||||
|
return newPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newPath, _ := filepath.Abs(relFilename)
|
||||||
|
utils.Logger.Warn("Could not find in GO path", "file", relFilename)
|
||||||
|
return newPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the source for the offending file.
|
// Read the source for the offending file.
|
||||||
var (
|
var (
|
||||||
relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go"
|
relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go"
|
||||||
absFilename, _ = filepath.Abs(relFilename)
|
absFilename = findInPaths(relFilename)
|
||||||
line, _ = strconv.Atoi(string(errorMatch[2]))
|
line, _ = strconv.Atoi(string(errorMatch[2]))
|
||||||
description = string(errorMatch[4])
|
description = string(errorMatch[4])
|
||||||
compileError = &revel.Error{
|
compileError = &utils.Error{
|
||||||
SourceType: "Go code",
|
SourceType: "Go code",
|
||||||
Title: "Go Compilation Error",
|
Title: "Go Compilation Error",
|
||||||
Path: relFilename,
|
Path: relFilename,
|
||||||
@@ -405,16 +451,16 @@ func newCompileError(output []byte) *revel.Error {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
errorLink := revel.Config.StringDefault("error.link", "")
|
errorLink := paths.Config.StringDefault("error.link", "")
|
||||||
|
|
||||||
if errorLink != "" {
|
if errorLink != "" {
|
||||||
compileError.SetLink(errorLink)
|
compileError.SetLink(errorLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileStr, err := revel.ReadLines(absFilename)
|
fileStr, err := utils.ReadLines(absFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
compileError.MetaError = absFilename + ": " + err.Error()
|
compileError.MetaError = absFilename + ": " + err.Error()
|
||||||
revel.RevelLog.Error(compileError.MetaError)
|
utils.Logger.Error("Unable to readlines "+compileError.MetaError, "error", err)
|
||||||
return compileError
|
return compileError
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,6 +470,9 @@ func newCompileError(output []byte) *revel.Error {
|
|||||||
|
|
||||||
// RevelMainTemplate template for app/tmp/main.go
|
// RevelMainTemplate template for app/tmp/main.go
|
||||||
const RevelMainTemplate = `// GENERATED CODE - DO NOT EDIT
|
const RevelMainTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||||
|
// This file is the main file for Revel.
|
||||||
|
// It registers all the controllers and provides details for the Revel server engine to
|
||||||
|
// properly inject parameters directly into the action endpoints.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -480,6 +529,8 @@ func main() {
|
|||||||
|
|
||||||
// RevelRoutesTemplate template for app/conf/routes
|
// RevelRoutesTemplate template for app/conf/routes
|
||||||
const RevelRoutesTemplate = `// GENERATED CODE - DO NOT EDIT
|
const RevelRoutesTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||||
|
// This file provides a way of creating URL's based on all the actions
|
||||||
|
// found in all the controllers.
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import "github.com/revel/revel"
|
import "github.com/revel/revel"
|
||||||
|
|||||||
@@ -28,8 +28,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"github.com/revel/cmd/watcher"
|
||||||
"sync"
|
"sync"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -41,20 +45,75 @@ var (
|
|||||||
// Harness reverse proxies requests to the application server.
|
// Harness reverse proxies requests to the application server.
|
||||||
// It builds / runs / rebuilds / restarts the server when code is changed.
|
// It builds / runs / rebuilds / restarts the server when code is changed.
|
||||||
type Harness struct {
|
type Harness struct {
|
||||||
app *App
|
app *App // The application
|
||||||
serverHost string
|
useProxy bool // True if proxy is in use
|
||||||
port int
|
serverHost string // The proxy server host
|
||||||
proxy *httputil.ReverseProxy
|
port int // The proxy serber port
|
||||||
watcher *revel.Watcher
|
proxy *httputil.ReverseProxy // The proxy
|
||||||
mutex *sync.Mutex
|
watcher *watcher.Watcher // The file watched
|
||||||
|
mutex *sync.Mutex // A mutex to prevent concurrent updates
|
||||||
|
paths *model.RevelContainer // The Revel container
|
||||||
|
config *model.CommandConfig // The configuration
|
||||||
|
runMode string // The runmode the harness is running in
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderError(iw http.ResponseWriter, ir *http.Request, err error) {
|
func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err error) {
|
||||||
context := revel.NewGoContext(nil)
|
// Render error here
|
||||||
context.Request.SetRequest(ir)
|
// Grab the template from three places
|
||||||
context.Response.SetResponse(iw)
|
// 1) Application/views/errors
|
||||||
c := revel.NewController(context)
|
// 2) revel_home/views/errors
|
||||||
c.RenderError(err).Apply(c.Request, c.Response)
|
// 3) views/errors
|
||||||
|
templateSet := template.New("__root__")
|
||||||
|
seekViewOnPath:=func(view string) (path string) {
|
||||||
|
path = filepath.Join(h.paths.ViewsPath, "errors", view)
|
||||||
|
if !utils.Exists(path) {
|
||||||
|
path = filepath.Join(h.paths.RevelPath, "templates", "errors", view)
|
||||||
|
}
|
||||||
|
println(path)
|
||||||
|
data,err := ioutil.ReadFile(path)
|
||||||
|
if err!=nil {
|
||||||
|
utils.Logger.Error("Unable to read template file", path)
|
||||||
|
}
|
||||||
|
_,err = templateSet.New("errors/"+view).Parse(string(data))
|
||||||
|
if err!=nil {
|
||||||
|
utils.Logger.Error("Unable to parse template file", path)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target := []string{seekViewOnPath("500.html"),seekViewOnPath("500-dev.html")}
|
||||||
|
if !utils.Exists(target[0]) {
|
||||||
|
fmt.Fprint(iw, "An error occurred %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var revelError *utils.Error
|
||||||
|
switch e := err.(type) {
|
||||||
|
case *utils.Error:
|
||||||
|
revelError = e
|
||||||
|
case error:
|
||||||
|
revelError = &utils.Error{
|
||||||
|
Title: "Server Error",
|
||||||
|
Description: e.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if revelError == nil {
|
||||||
|
panic("no error provided")
|
||||||
|
}
|
||||||
|
viewArgs := map[string]interface{}{}
|
||||||
|
viewArgs["RunMode"] = h.paths.RunMode
|
||||||
|
viewArgs["DevMode"] = h.paths.DevMode
|
||||||
|
viewArgs["Error"] = revelError
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Render the template from the file
|
||||||
|
err = templateSet.ExecuteTemplate(iw,"errors/500.html",viewArgs)
|
||||||
|
if err!=nil {
|
||||||
|
utils.Logger.Error("Failed to execute","error",err)
|
||||||
|
}
|
||||||
|
fmt.Println("template ",templateSet.Templates()[0].Name(), templateSet.Templates()[1].Name())
|
||||||
|
//utils.MustRenderTemplateToStream(iw,target, viewArgs)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP handles all requests.
|
// ServeHTTP handles all requests.
|
||||||
@@ -68,11 +127,12 @@ func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Flush any change events and rebuild app if necessary.
|
// Flush any change events and rebuild app if necessary.
|
||||||
// Render an error page if the rebuild / restart failed.
|
// Render an error page if the rebuild / restart failed.
|
||||||
err := h.watcher.Notify()
|
err := h.watcher.Notify()
|
||||||
|
println("Serving ", err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// In a thread safe manner update the flag so that a request for
|
// In a thread safe manner update the flag so that a request for
|
||||||
// /favicon.ico does not trigger a rebuild
|
// /favicon.ico does not trigger a rebuild
|
||||||
atomic.CompareAndSwapInt32(&lastRequestHadError, 0, 1)
|
atomic.CompareAndSwapInt32(&lastRequestHadError, 0, 1)
|
||||||
renderError(w, r, err)
|
h.renderError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +143,7 @@ func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Reverse proxy the request.
|
// Reverse proxy the request.
|
||||||
// (Need special code for websockets, courtesy of bradfitz)
|
// (Need special code for websockets, courtesy of bradfitz)
|
||||||
if strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
|
if strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
|
||||||
proxyWebsocket(w, r, h.serverHost)
|
h.proxyWebsocket(w, r, h.serverHost)
|
||||||
} else {
|
} else {
|
||||||
h.proxy.ServeHTTP(w, r)
|
h.proxy.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
@@ -91,24 +151,26 @@ func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// NewHarness method returns a reverse proxy that forwards requests
|
// NewHarness method returns a reverse proxy that forwards requests
|
||||||
// to the given port.
|
// to the given port.
|
||||||
func NewHarness() *Harness {
|
func NewHarness(c *model.CommandConfig, paths *model.RevelContainer, runMode string, noProxy bool) *Harness {
|
||||||
// Get a template loader to render errors.
|
// Get a template loader to render errors.
|
||||||
// Prefer the app's views/errors directory, and fall back to the stock error pages.
|
// Prefer the app's views/errors directory, and fall back to the stock error pages.
|
||||||
revel.MainTemplateLoader = revel.NewTemplateLoader(
|
//revel.MainTemplateLoader = revel.NewTemplateLoader(
|
||||||
[]string{filepath.Join(revel.RevelPath, "templates")})
|
// []string{filepath.Join(revel.RevelPath, "templates")})
|
||||||
if err := revel.MainTemplateLoader.Refresh(); err != nil {
|
//if err := revel.MainTemplateLoader.Refresh(); err != nil {
|
||||||
revel.RevelLog.Error("Template loader error", "error", err)
|
// revel.RevelLog.Error("Template loader error", "error", err)
|
||||||
}
|
//}
|
||||||
|
|
||||||
addr := revel.HTTPAddr
|
addr := paths.HTTPAddr
|
||||||
port := revel.Config.IntDefault("harness.port", 0)
|
port := paths.Config.IntDefault("harness.port", 0)
|
||||||
scheme := "http"
|
scheme := "http"
|
||||||
if revel.HTTPSsl {
|
if paths.HTTPSsl {
|
||||||
scheme = "https"
|
scheme = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the server is running on the wildcard address, use "localhost"
|
// If the server is running on the wildcard address, use "localhost"
|
||||||
if addr == "" {
|
if addr == "" {
|
||||||
|
utils.Logger.Warn("No http.addr specified in the app.conf listening on localhost interface only. " +
|
||||||
|
"This will not allow external access to your application")
|
||||||
addr = "localhost"
|
addr = "localhost"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,9 +185,14 @@ func NewHarness() *Harness {
|
|||||||
serverHost: serverURL.String()[len(scheme+"://"):],
|
serverHost: serverURL.String()[len(scheme+"://"):],
|
||||||
proxy: httputil.NewSingleHostReverseProxy(serverURL),
|
proxy: httputil.NewSingleHostReverseProxy(serverURL),
|
||||||
mutex: &sync.Mutex{},
|
mutex: &sync.Mutex{},
|
||||||
|
paths: paths,
|
||||||
|
useProxy: !noProxy,
|
||||||
|
config: c,
|
||||||
|
runMode: runMode,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if revel.HTTPSsl {
|
if paths.HTTPSsl {
|
||||||
serverHarness.proxy.Transport = &http.Transport{
|
serverHarness.proxy.Transport = &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
}
|
}
|
||||||
@@ -134,8 +201,13 @@ func NewHarness() *Harness {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Refresh method rebuilds the Revel application and run it on the given port.
|
// Refresh method rebuilds the Revel application and run it on the given port.
|
||||||
func (h *Harness) Refresh() (err *revel.Error) {
|
// called by the watcher
|
||||||
|
func (h *Harness) Refresh() (err *utils.Error) {
|
||||||
// Allow only one thread to rebuild the process
|
// Allow only one thread to rebuild the process
|
||||||
|
// If multiple requests to rebuild are queued only the last one is executed on
|
||||||
|
// So before a build is started we wait for a second to determine if
|
||||||
|
// more requests for a build are triggered.
|
||||||
|
// Once no more requests are triggered the build will be processed
|
||||||
h.mutex.Lock()
|
h.mutex.Lock()
|
||||||
defer h.mutex.Unlock()
|
defer h.mutex.Unlock()
|
||||||
|
|
||||||
@@ -143,19 +215,25 @@ func (h *Harness) Refresh() (err *revel.Error) {
|
|||||||
h.app.Kill()
|
h.app.Kill()
|
||||||
}
|
}
|
||||||
|
|
||||||
revel.RevelLog.Debug("Rebuild Called")
|
utils.Logger.Info("Rebuild Called")
|
||||||
h.app, err = Build()
|
h.app, err = Build(h.config, h.paths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
utils.Logger.Error("Build detected an error", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if h.useProxy {
|
||||||
h.app.Port = h.port
|
h.app.Port = h.port
|
||||||
if err2 := h.app.Cmd().Start(); err2 != nil {
|
if err2 := h.app.Cmd(h.runMode).Start(h.config); err2 != nil {
|
||||||
return &revel.Error{
|
utils.Logger.Error("Could not start application", "error", err2)
|
||||||
|
return &utils.Error{
|
||||||
Title: "App failed to start up",
|
Title: "App failed to start up",
|
||||||
Description: err2.Error(),
|
Description: err2.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
h.app = nil
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -163,7 +241,7 @@ func (h *Harness) Refresh() (err *revel.Error) {
|
|||||||
// WatchDir method returns false to file matches with doNotWatch
|
// WatchDir method returns false to file matches with doNotWatch
|
||||||
// otheriwse true
|
// otheriwse true
|
||||||
func (h *Harness) WatchDir(info os.FileInfo) bool {
|
func (h *Harness) WatchDir(info os.FileInfo) bool {
|
||||||
return !revel.ContainsString(doNotWatch, info.Name())
|
return !utils.ContainsString(doNotWatch, info.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
// WatchFile method returns true given filename HasSuffix of ".go"
|
// WatchFile method returns true given filename HasSuffix of ".go"
|
||||||
@@ -176,34 +254,37 @@ func (h *Harness) WatchFile(filename string) bool {
|
|||||||
// server, which it runs and rebuilds as necessary.
|
// server, which it runs and rebuilds as necessary.
|
||||||
func (h *Harness) Run() {
|
func (h *Harness) Run() {
|
||||||
var paths []string
|
var paths []string
|
||||||
if revel.Config.BoolDefault("watch.gopath", false) {
|
if h.paths.Config.BoolDefault("watch.gopath", false) {
|
||||||
gopaths := filepath.SplitList(build.Default.GOPATH)
|
gopaths := filepath.SplitList(build.Default.GOPATH)
|
||||||
paths = append(paths, gopaths...)
|
paths = append(paths, gopaths...)
|
||||||
}
|
}
|
||||||
paths = append(paths, revel.CodePaths...)
|
paths = append(paths, h.paths.CodePaths...)
|
||||||
h.watcher = revel.NewWatcher()
|
h.watcher = watcher.NewWatcher(h.paths, false)
|
||||||
h.watcher.Listen(h, paths...)
|
h.watcher.Listen(h, paths...)
|
||||||
h.watcher.Notify()
|
h.watcher.Notify()
|
||||||
|
|
||||||
|
if h.useProxy {
|
||||||
go func() {
|
go func() {
|
||||||
addr := fmt.Sprintf("%s:%d", revel.HTTPAddr, revel.HTTPPort)
|
addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort)
|
||||||
revel.RevelLog.Infof("Listening on %s", addr)
|
utils.Logger.Infof("Proxy server is listening on %s", addr)
|
||||||
|
println("Proxy server is listening on ", addr)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if revel.HTTPSsl {
|
if h.paths.HTTPSsl {
|
||||||
err = http.ListenAndServeTLS(
|
err = http.ListenAndServeTLS(
|
||||||
addr,
|
addr,
|
||||||
revel.HTTPSslCert,
|
h.paths.HTTPSslCert,
|
||||||
revel.HTTPSslKey,
|
h.paths.HTTPSslKey,
|
||||||
h)
|
h)
|
||||||
} else {
|
} else {
|
||||||
err = http.ListenAndServe(addr, h)
|
err = http.ListenAndServe(addr, h)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Error("Failed to start reverse proxy:", "error", err)
|
utils.Logger.Error("Failed to start reverse proxy:", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
// Kill the app on signal.
|
// Kill the app on signal.
|
||||||
ch := make(chan os.Signal)
|
ch := make(chan os.Signal)
|
||||||
signal.Notify(ch, os.Interrupt, os.Kill)
|
signal.Notify(ch, os.Interrupt, os.Kill)
|
||||||
@@ -218,25 +299,25 @@ func (h *Harness) Run() {
|
|||||||
func getFreePort() (port int) {
|
func getFreePort() (port int) {
|
||||||
conn, err := net.Listen("tcp", ":0")
|
conn, err := net.Listen("tcp", ":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Fatal("Unable to fetch a freee port address", "error", err)
|
utils.Logger.Fatal("Unable to fetch a freee port address", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
port = conn.Addr().(*net.TCPAddr).Port
|
port = conn.Addr().(*net.TCPAddr).Port
|
||||||
err = conn.Close()
|
err = conn.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Fatal("Unable to close port", "error", err)
|
utils.Logger.Fatal("Unable to close port", "error", err)
|
||||||
}
|
}
|
||||||
return port
|
return port
|
||||||
}
|
}
|
||||||
|
|
||||||
// proxyWebsocket copies data between websocket client and server until one side
|
// proxyWebsocket copies data between websocket client and server until one side
|
||||||
// closes the connection. (ReverseProxy doesn't work with websocket requests.)
|
// closes the connection. (ReverseProxy doesn't work with websocket requests.)
|
||||||
func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
|
func (h *Harness) proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
|
||||||
var (
|
var (
|
||||||
d net.Conn
|
d net.Conn
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if revel.HTTPSsl {
|
if h.paths.HTTPSsl {
|
||||||
// since this proxy isn't used in production,
|
// since this proxy isn't used in production,
|
||||||
// it's OK to set InsecureSkipVerify to true
|
// it's OK to set InsecureSkipVerify to true
|
||||||
// no need to add another configuration option.
|
// no need to add another configuration option.
|
||||||
@@ -246,7 +327,7 @@ func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error contacting backend server.", 500)
|
http.Error(w, "Error contacting backend server.", 500)
|
||||||
revel.RevelLog.Error("Error dialing websocket backend ", "host", host, "error", err)
|
utils.Logger.Error("Error dialing websocket backend ", "host", host, "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hj, ok := w.(http.Hijacker)
|
hj, ok := w.(http.Hijacker)
|
||||||
@@ -256,21 +337,21 @@ func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
|
|||||||
}
|
}
|
||||||
nc, _, err := hj.Hijack()
|
nc, _, err := hj.Hijack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Error("Hijack error", "error", err)
|
utils.Logger.Error("Hijack error", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err = nc.Close(); err != nil {
|
if err = nc.Close(); err != nil {
|
||||||
revel.RevelLog.Error("Connection close error", "error", err)
|
utils.Logger.Error("Connection close error", "error", err)
|
||||||
}
|
}
|
||||||
if err = d.Close(); err != nil {
|
if err = d.Close(); err != nil {
|
||||||
revel.RevelLog.Error("Dial close error", "error", err)
|
utils.Logger.Error("Dial close error", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = r.Write(d)
|
err = r.Write(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Error("Error copying request to target", "error", err)
|
utils.Logger.Error("Error copying request to target", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
logger/doc.go
Normal file
11
logger/doc.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
Package logger contains filters and handles for the logging utilities in Revel.
|
||||||
|
These facilities all currently use the logging library called log15 at
|
||||||
|
https://github.com/inconshreveable/log15
|
||||||
|
|
||||||
|
Wrappers for the handlers are written here to provide a kind of isolation layer for Revel
|
||||||
|
in case sometime in the future we would like to switch to another source to implement logging
|
||||||
|
|
||||||
|
*/
|
||||||
|
package logger
|
||||||
206
logger/format.go
Normal file
206
logger/format.go
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/revel/log15"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
timeFormat = "2006-01-02T15:04:05-0700"
|
||||||
|
termTimeFormat = "2006/01/02 15:04:05"
|
||||||
|
termSmallTimeFormat = "15:04:05"
|
||||||
|
floatFormat = 'f'
|
||||||
|
errorKey = "REVEL_ERROR"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Name the log level
|
||||||
|
toRevel = map[log15.Lvl]string{log15.LvlDebug: "DEBUG",
|
||||||
|
log15.LvlInfo: "INFO", log15.LvlWarn: "WARN", log15.LvlError: "ERROR", log15.LvlCrit: "CRIT"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Outputs to the terminal in a format like below
|
||||||
|
// INFO 09:11:32 server-engine.go:169: Request Stats
|
||||||
|
func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
|
||||||
|
dateFormat := termTimeFormat
|
||||||
|
if smallDate {
|
||||||
|
dateFormat = termSmallTimeFormat
|
||||||
|
}
|
||||||
|
return log15.FormatFunc(func(r *log15.Record) []byte {
|
||||||
|
// Bash coloring http://misc.flogisoft.com/bash/tip_colors_and_formatting
|
||||||
|
var color = 0
|
||||||
|
switch r.Lvl {
|
||||||
|
case log15.LvlCrit:
|
||||||
|
// Magenta
|
||||||
|
color = 35
|
||||||
|
case log15.LvlError:
|
||||||
|
// Red
|
||||||
|
color = 31
|
||||||
|
case log15.LvlWarn:
|
||||||
|
// Yellow
|
||||||
|
color = 33
|
||||||
|
case log15.LvlInfo:
|
||||||
|
// Green
|
||||||
|
color = 32
|
||||||
|
case log15.LvlDebug:
|
||||||
|
// Cyan
|
||||||
|
color = 36
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
caller := findInContext("caller", r.Ctx)
|
||||||
|
module := findInContext("module", r.Ctx)
|
||||||
|
if noColor == false && color > 0 {
|
||||||
|
if len(module) > 0 {
|
||||||
|
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %6s %13s: %-40s ", color, toRevel[r.Lvl], r.Time.Format(dateFormat), module, caller, r.Msg)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %13s: %-40s ", color, toRevel[r.Lvl], r.Time.Format(dateFormat), caller, r.Msg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(b, "%-5s %s %6s %13s: %-40s", toRevel[r.Lvl], r.Time.Format(dateFormat), module, caller, r.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(r.Ctx); i += 2 {
|
||||||
|
if i != 0 {
|
||||||
|
b.WriteByte(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
k, ok := r.Ctx[i].(string)
|
||||||
|
if k == "caller" || k == "fn" || k == "module" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v := formatLogfmtValue(r.Ctx[i+1])
|
||||||
|
if !ok {
|
||||||
|
k, v = errorKey, formatLogfmtValue(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: we should probably check that all of your key bytes aren't invalid
|
||||||
|
if noColor == false && color > 0 {
|
||||||
|
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m=%s", color, k, v)
|
||||||
|
} else {
|
||||||
|
b.WriteString(k)
|
||||||
|
b.WriteByte('=')
|
||||||
|
b.WriteString(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteByte('\n')
|
||||||
|
|
||||||
|
return b.Bytes()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func findInContext(key string, ctx []interface{}) string {
|
||||||
|
for i := 0; i < len(ctx); i += 2 {
|
||||||
|
k := ctx[i].(string)
|
||||||
|
if key == k {
|
||||||
|
return formatLogfmtValue(ctx[i+1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatValue formats a value for serialization
|
||||||
|
func formatLogfmtValue(value interface{}) string {
|
||||||
|
if value == nil {
|
||||||
|
return "nil"
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, ok := value.(time.Time); ok {
|
||||||
|
// Performance optimization: No need for escaping since the provided
|
||||||
|
// timeFormat doesn't have any escape characters, and escaping is
|
||||||
|
// expensive.
|
||||||
|
return t.Format(termTimeFormat)
|
||||||
|
}
|
||||||
|
value = formatShared(value)
|
||||||
|
switch v := value.(type) {
|
||||||
|
case bool:
|
||||||
|
return strconv.FormatBool(v)
|
||||||
|
case float32:
|
||||||
|
return strconv.FormatFloat(float64(v), floatFormat, 3, 64)
|
||||||
|
case float64:
|
||||||
|
return strconv.FormatFloat(v, floatFormat, 7, 64)
|
||||||
|
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||||
|
return fmt.Sprintf("%d", value)
|
||||||
|
case string:
|
||||||
|
return escapeString(v)
|
||||||
|
default:
|
||||||
|
return escapeString(fmt.Sprintf("%+v", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func formatShared(value interface{}) (result interface{}) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||||
|
result = "nil"
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case time.Time:
|
||||||
|
return v.Format(timeFormat)
|
||||||
|
|
||||||
|
case error:
|
||||||
|
return v.Error()
|
||||||
|
|
||||||
|
case fmt.Stringer:
|
||||||
|
return v.String()
|
||||||
|
|
||||||
|
default:
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringBufPool = sync.Pool{
|
||||||
|
New: func() interface{} { return new(bytes.Buffer) },
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeString(s string) string {
|
||||||
|
needsQuotes := false
|
||||||
|
needsEscape := false
|
||||||
|
for _, r := range s {
|
||||||
|
if r <= ' ' || r == '=' || r == '"' {
|
||||||
|
needsQuotes = true
|
||||||
|
}
|
||||||
|
if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' {
|
||||||
|
needsEscape = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if needsEscape == false && needsQuotes == false {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
e := stringBufPool.Get().(*bytes.Buffer)
|
||||||
|
e.WriteByte('"')
|
||||||
|
for _, r := range s {
|
||||||
|
switch r {
|
||||||
|
case '\\', '"':
|
||||||
|
e.WriteByte('\\')
|
||||||
|
e.WriteByte(byte(r))
|
||||||
|
case '\n':
|
||||||
|
e.WriteString("\\n")
|
||||||
|
case '\r':
|
||||||
|
e.WriteString("\\r")
|
||||||
|
case '\t':
|
||||||
|
e.WriteString("\\t")
|
||||||
|
default:
|
||||||
|
e.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.WriteByte('"')
|
||||||
|
var ret string
|
||||||
|
if needsQuotes {
|
||||||
|
ret = e.String()
|
||||||
|
} else {
|
||||||
|
ret = string(e.Bytes()[1 : e.Len()-1])
|
||||||
|
}
|
||||||
|
e.Reset()
|
||||||
|
stringBufPool.Put(e)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
350
logger/handlers.go
Normal file
350
logger/handlers.go
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
colorable "github.com/mattn/go-colorable"
|
||||||
|
"github.com/revel/log15"
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filters out records which do not match the level
|
||||||
|
// Uses the `log15.FilterHandler` to perform this task
|
||||||
|
func LevelHandler(lvl LogLevel, h LogHandler) LogHandler {
|
||||||
|
l15Lvl := log15.Lvl(lvl)
|
||||||
|
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
|
||||||
|
return r.Lvl == l15Lvl
|
||||||
|
}, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filters out records which do not match the level
|
||||||
|
// Uses the `log15.FilterHandler` to perform this task
|
||||||
|
func MinLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
|
||||||
|
l15Lvl := log15.Lvl(lvl)
|
||||||
|
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
|
||||||
|
return r.Lvl <= l15Lvl
|
||||||
|
}, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filters out records which match the level
|
||||||
|
// Uses the `log15.FilterHandler` to perform this task
|
||||||
|
func NotLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
|
||||||
|
l15Lvl := log15.Lvl(lvl)
|
||||||
|
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
|
||||||
|
return r.Lvl != l15Lvl
|
||||||
|
}, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds in a context called `caller` to the record (contains file name and line number like `foo.go:12`)
|
||||||
|
// Uses the `log15.CallerFileHandler` to perform this task
|
||||||
|
func CallerFileHandler(h LogHandler) LogHandler {
|
||||||
|
return log15.CallerFileHandler(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds in a context called `caller` to the record (contains file name and line number like `foo.go:12`)
|
||||||
|
// Uses the `log15.CallerFuncHandler` to perform this task
|
||||||
|
func CallerFuncHandler(h LogHandler) LogHandler {
|
||||||
|
return log15.CallerFuncHandler(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filters out records which match the key value pair
|
||||||
|
// Uses the `log15.MatchFilterHandler` to perform this task
|
||||||
|
func MatchHandler(key string, value interface{}, h LogHandler) LogHandler {
|
||||||
|
return log15.MatchFilterHandler(key, value, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If match then A handler is called otherwise B handler is called
|
||||||
|
func MatchAbHandler(key string, value interface{}, a, b LogHandler) LogHandler {
|
||||||
|
return log15.FuncHandler(func(r *log15.Record) error {
|
||||||
|
for i := 0; i < len(r.Ctx); i += 2 {
|
||||||
|
if r.Ctx[i] == key {
|
||||||
|
if r.Ctx[i+1] == value {
|
||||||
|
if a != nil {
|
||||||
|
return a.Log(r)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b != nil {
|
||||||
|
return b.Log(r)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// The nil handler is used if logging for a specific request needs to be turned off
|
||||||
|
func NilHandler() LogHandler {
|
||||||
|
return log15.FuncHandler(func(r *log15.Record) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match all values in map to log
|
||||||
|
func MatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler {
|
||||||
|
return matchMapHandler(matchMap, false, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match !(Match all values in map to log) The inverse of MatchMapHandler
|
||||||
|
func NotMatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler {
|
||||||
|
return matchMapHandler(matchMap, true, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rather then chaining multiple filter handlers, process all here
|
||||||
|
func matchMapHandler(matchMap map[string]interface{}, inverse bool, a LogHandler) LogHandler {
|
||||||
|
return log15.FuncHandler(func(r *log15.Record) error {
|
||||||
|
checkMap := map[string]bool{}
|
||||||
|
// Copy the map to a bool
|
||||||
|
for i := 0; i < len(r.Ctx); i += 2 {
|
||||||
|
if value, found := matchMap[r.Ctx[i].(string)]; found && value == r.Ctx[i+1] {
|
||||||
|
checkMap[r.Ctx[i].(string)] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(checkMap) == len(matchMap) {
|
||||||
|
if !inverse {
|
||||||
|
return a.Log(r)
|
||||||
|
}
|
||||||
|
} else if inverse {
|
||||||
|
return a.Log(r)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filters out records which do not match the key value pair
|
||||||
|
// Uses the `log15.FilterHandler` to perform this task
|
||||||
|
func NotMatchHandler(key string, value interface{}, h LogHandler) LogHandler {
|
||||||
|
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
|
||||||
|
switch key {
|
||||||
|
case r.KeyNames.Lvl:
|
||||||
|
return r.Lvl != value
|
||||||
|
case r.KeyNames.Time:
|
||||||
|
return r.Time != value
|
||||||
|
case r.KeyNames.Msg:
|
||||||
|
return r.Msg != value
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(r.Ctx); i += 2 {
|
||||||
|
if r.Ctx[i] == key {
|
||||||
|
return r.Ctx[i+1] == value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MultiHandler(hs ...LogHandler) LogHandler {
|
||||||
|
// Convert the log handlers to log15.Handlers
|
||||||
|
handlers := []log15.Handler{}
|
||||||
|
for _, h := range hs {
|
||||||
|
if h != nil {
|
||||||
|
handlers = append(handlers, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return log15.MultiHandler(handlers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outputs the records to the passed in stream
|
||||||
|
// Uses the `log15.StreamHandler` to perform this task
|
||||||
|
func StreamHandler(wr io.Writer, fmtr LogFormat) LogHandler {
|
||||||
|
return log15.StreamHandler(wr, fmtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter handler, this is the only
|
||||||
|
// Uses the `log15.FilterHandler` to perform this task
|
||||||
|
func FilterHandler(fn func(r *log15.Record) bool, h LogHandler) LogHandler {
|
||||||
|
return log15.FilterHandler(fn, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListLogHandler struct {
|
||||||
|
handlers []LogHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListLogHandler(h1, h2 LogHandler) *ListLogHandler {
|
||||||
|
ll := &ListLogHandler{handlers: []LogHandler{h1, h2}}
|
||||||
|
return ll
|
||||||
|
}
|
||||||
|
func (ll *ListLogHandler) Log(r *log15.Record) (err error) {
|
||||||
|
for _, handler := range ll.handlers {
|
||||||
|
if err == nil {
|
||||||
|
err = handler.Log(r)
|
||||||
|
} else {
|
||||||
|
handler.Log(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (ll *ListLogHandler) Add(h LogHandler) {
|
||||||
|
if h != nil {
|
||||||
|
ll.handlers = append(ll.handlers, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (ll *ListLogHandler) Del(h LogHandler) {
|
||||||
|
if h != nil {
|
||||||
|
for i, handler := range ll.handlers {
|
||||||
|
if handler == h {
|
||||||
|
ll.handlers = append(ll.handlers[:i], ll.handlers[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompositeMultiHandler struct {
|
||||||
|
DebugHandler LogHandler
|
||||||
|
InfoHandler LogHandler
|
||||||
|
WarnHandler LogHandler
|
||||||
|
ErrorHandler LogHandler
|
||||||
|
CriticalHandler LogHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompositeMultiHandler() (*CompositeMultiHandler, LogHandler) {
|
||||||
|
cw := &CompositeMultiHandler{}
|
||||||
|
return cw, cw
|
||||||
|
}
|
||||||
|
func (h *CompositeMultiHandler) Log(r *log15.Record) (err error) {
|
||||||
|
|
||||||
|
var handler LogHandler
|
||||||
|
switch r.Lvl {
|
||||||
|
case log15.LvlInfo:
|
||||||
|
handler = h.InfoHandler
|
||||||
|
case log15.LvlDebug:
|
||||||
|
handler = h.DebugHandler
|
||||||
|
case log15.LvlWarn:
|
||||||
|
handler = h.WarnHandler
|
||||||
|
case log15.LvlError:
|
||||||
|
handler = h.ErrorHandler
|
||||||
|
case log15.LvlCrit:
|
||||||
|
handler = h.CriticalHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Embed the caller function in the context
|
||||||
|
if handler != nil {
|
||||||
|
handler.Log(r)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (h *CompositeMultiHandler) SetHandler(handler LogHandler, replace bool, level LogLevel) {
|
||||||
|
if handler == nil {
|
||||||
|
// Ignore empty handler
|
||||||
|
return
|
||||||
|
}
|
||||||
|
source := &h.DebugHandler
|
||||||
|
switch level {
|
||||||
|
case LvlDebug:
|
||||||
|
source = &h.DebugHandler
|
||||||
|
case LvlInfo:
|
||||||
|
source = &h.InfoHandler
|
||||||
|
case LvlWarn:
|
||||||
|
source = &h.WarnHandler
|
||||||
|
case LvlError:
|
||||||
|
source = &h.ErrorHandler
|
||||||
|
case LvlCrit:
|
||||||
|
source = &h.CriticalHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
if !replace && *source != nil {
|
||||||
|
// If this already was a list add a new logger to it
|
||||||
|
if ll, found := (*source).(*ListLogHandler); found {
|
||||||
|
ll.Add(handler)
|
||||||
|
} else {
|
||||||
|
*source = NewListLogHandler(*source, handler)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*source = handler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompositeMultiHandler) SetHandlers(handler LogHandler, options *LogOptions) {
|
||||||
|
if len(options.Levels) == 0 {
|
||||||
|
options.Levels = LvlAllList
|
||||||
|
}
|
||||||
|
// Set all levels
|
||||||
|
for _, lvl := range options.Levels {
|
||||||
|
h.SetHandler(handler, options.ReplaceExistingHandler, lvl)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func (h *CompositeMultiHandler) SetJson(writer io.Writer, options *LogOptions) {
|
||||||
|
handler := CallerFileHandler(StreamHandler(writer, log15.JsonFormatEx(
|
||||||
|
options.GetBoolDefault("pretty", false),
|
||||||
|
options.GetBoolDefault("lineSeparated", true),
|
||||||
|
)))
|
||||||
|
if options.HandlerWrap != nil {
|
||||||
|
handler = options.HandlerWrap.SetChild(handler)
|
||||||
|
}
|
||||||
|
h.SetHandlers(handler, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use built in rolling function
|
||||||
|
func (h *CompositeMultiHandler) SetJsonFile(filePath string, options *LogOptions) {
|
||||||
|
writer := &lumberjack.Logger{
|
||||||
|
Filename: filePath,
|
||||||
|
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
|
||||||
|
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days
|
||||||
|
MaxBackups: options.GetIntDefault("maxBackups", 7),
|
||||||
|
Compress: options.GetBoolDefault("compress", true),
|
||||||
|
}
|
||||||
|
h.SetJson(writer, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompositeMultiHandler) SetTerminal(writer io.Writer, options *LogOptions) {
|
||||||
|
streamHandler := StreamHandler(
|
||||||
|
writer,
|
||||||
|
TerminalFormatHandler(
|
||||||
|
options.GetBoolDefault("noColor", false),
|
||||||
|
options.GetBoolDefault("smallDate", true)))
|
||||||
|
|
||||||
|
if os.Stdout == writer {
|
||||||
|
streamHandler = StreamHandler(
|
||||||
|
colorable.NewColorableStdout(),
|
||||||
|
TerminalFormatHandler(
|
||||||
|
options.GetBoolDefault("noColor", false),
|
||||||
|
options.GetBoolDefault("smallDate", true)))
|
||||||
|
} else if os.Stderr == writer {
|
||||||
|
streamHandler = StreamHandler(
|
||||||
|
colorable.NewColorableStderr(),
|
||||||
|
TerminalFormatHandler(
|
||||||
|
options.GetBoolDefault("noColor", false),
|
||||||
|
options.GetBoolDefault("smallDate", true)))
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := CallerFileHandler(streamHandler)
|
||||||
|
if options.HandlerWrap != nil {
|
||||||
|
handler = options.HandlerWrap.SetChild(handler)
|
||||||
|
}
|
||||||
|
h.SetHandlers(handler, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use built in rolling function
|
||||||
|
func (h *CompositeMultiHandler) SetTerminalFile(filePath string, options *LogOptions) {
|
||||||
|
writer := &lumberjack.Logger{
|
||||||
|
Filename: filePath,
|
||||||
|
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
|
||||||
|
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days
|
||||||
|
MaxBackups: options.GetIntDefault("maxBackups", 7),
|
||||||
|
Compress: options.GetBoolDefault("compress", true),
|
||||||
|
}
|
||||||
|
h.SetTerminal(writer, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompositeMultiHandler) Disable(levels ...LogLevel) {
|
||||||
|
if len(levels) == 0 {
|
||||||
|
levels = LvlAllList
|
||||||
|
}
|
||||||
|
for _, level := range levels {
|
||||||
|
switch level {
|
||||||
|
case LvlDebug:
|
||||||
|
h.DebugHandler = nil
|
||||||
|
case LvlInfo:
|
||||||
|
h.InfoHandler = nil
|
||||||
|
case LvlWarn:
|
||||||
|
h.WarnHandler = nil
|
||||||
|
case LvlError:
|
||||||
|
h.ErrorHandler = nil
|
||||||
|
case LvlCrit:
|
||||||
|
h.CriticalHandler = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
656
logger/log.go.old
Normal file
656
logger/log.go.old
Normal file
@@ -0,0 +1,656 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
|
||||||
|
// LoggedError is wrapper to differentiate logged panics from unexpected ones.
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"sync"
|
||||||
|
"go.uber.org/zap/buffer"
|
||||||
|
"time"
|
||||||
|
"encoding/base64"
|
||||||
|
"unicode/utf8"
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
MultiLogger interface {
|
||||||
|
//log15.Logger
|
||||||
|
//// New returns a new Logger that has this logger's context plus the given context
|
||||||
|
New(ctx ...interface{}) MultiLogger
|
||||||
|
//
|
||||||
|
// The encoders job is to encode the
|
||||||
|
SetHandler(h LogHandler)
|
||||||
|
SetStackDepth(int) MultiLogger
|
||||||
|
//
|
||||||
|
//// Log a message at the given level with context key/value pairs
|
||||||
|
Debug(msg string, ctx ...interface{})
|
||||||
|
Debugf(msg string, params ...interface{})
|
||||||
|
Info(msg string, ctx ...interface{})
|
||||||
|
Infof(msg string, params ...interface{})
|
||||||
|
Warn(msg string, ctx ...interface{})
|
||||||
|
Warnf(msg string, params ...interface{})
|
||||||
|
Error(msg string, ctx ...interface{})
|
||||||
|
Errorf(msg string, params ...interface{})
|
||||||
|
Crit(msg string, ctx ...interface{})
|
||||||
|
Critf(msg string, params ...interface{})
|
||||||
|
|
||||||
|
//// Logs a message as an Crit and exits
|
||||||
|
Fatal(msg string, ctx ...interface{})
|
||||||
|
Fatalf(msg string, params ...interface{})
|
||||||
|
//// Logs a message as an Crit and panics
|
||||||
|
Panic(msg string, ctx ...interface{})
|
||||||
|
Panicf(msg string, params ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// The log han
|
||||||
|
LogHandler interface {
|
||||||
|
Encode(Record) ([]byte, error)
|
||||||
|
GetLevel() Level
|
||||||
|
GetWriter() io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Record
|
||||||
|
Record struct {
|
||||||
|
Level Level
|
||||||
|
Time time.Time
|
||||||
|
LoggerName string
|
||||||
|
Message string
|
||||||
|
Caller EntryCaller
|
||||||
|
Stack string
|
||||||
|
Context []Field
|
||||||
|
}
|
||||||
|
|
||||||
|
// The fields passed in
|
||||||
|
Field interface {
|
||||||
|
GetKey() string
|
||||||
|
GetValueAsString() string
|
||||||
|
GetValue() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryCaller interface {
|
||||||
|
IsDefined() bool
|
||||||
|
GetPC() uintptr
|
||||||
|
GetFile() string
|
||||||
|
GetLine() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called only if the logger needs
|
||||||
|
ResolveLaterLogger func() interface{}
|
||||||
|
|
||||||
|
FieldType int
|
||||||
|
Level int
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
zapLogger struct {
|
||||||
|
logger *zap.SugaredLogger
|
||||||
|
coreList []*zapcore.Core
|
||||||
|
}
|
||||||
|
zapField struct {
|
||||||
|
Key string
|
||||||
|
Type FieldType
|
||||||
|
Integer int64
|
||||||
|
String string
|
||||||
|
Interface interface{}
|
||||||
|
}
|
||||||
|
zapEntryCaller struct {
|
||||||
|
Defined bool
|
||||||
|
PC uintptr
|
||||||
|
File string
|
||||||
|
Line int
|
||||||
|
}
|
||||||
|
zapEncoder struct {
|
||||||
|
lh LogHandler
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func newLogger(addCaller bool) MultiLogger {
|
||||||
|
logger := zap.New(nil).WithOptions(zap.AddCaller())
|
||||||
|
l := &zapLogger{logger:logger.Sugar()}
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is up to the handler to determine the synchronization to the output
|
||||||
|
// streams
|
||||||
|
func (z *zapLogger) SetHandler(lh LogHandler) {
|
||||||
|
// Swap out the logger when a new handler is attached
|
||||||
|
encoder := &zapEncoder{lh}
|
||||||
|
levelHandler := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||||
|
return lvl >= zapcore.Level(lh.GetLevel())
|
||||||
|
})
|
||||||
|
logger := zap.New(zapcore.NewCore(encoder, nil, levelHandler)).WithOptions(zap.AddCaller())
|
||||||
|
Logger.With("foo","bar").Desugar().Core()
|
||||||
|
}
|
||||||
|
|
||||||
|
var Logger *zap.SugaredLogger
|
||||||
|
|
||||||
|
func InitLogger(logLevel zapcore.Level) {
|
||||||
|
config :=zap.NewDevelopmentEncoderConfig()
|
||||||
|
config.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||||
|
|
||||||
|
consoleEncoder := NewConsoleEncoder(config)
|
||||||
|
lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||||
|
return lvl >= logLevel
|
||||||
|
})
|
||||||
|
|
||||||
|
consoleDebugging := zapcore.Lock(os.Stdout)
|
||||||
|
core := zapcore.NewTee(
|
||||||
|
zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
|
||||||
|
)
|
||||||
|
logger := zap.New(core).WithOptions(zap.AddCaller())
|
||||||
|
Logger = logger.Sugar()
|
||||||
|
}
|
||||||
|
type LoggedError struct{ error }
|
||||||
|
|
||||||
|
func NewLoggedError(err error) *LoggedError {
|
||||||
|
return &LoggedError{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Errorf(format string, args ...interface{}) {
|
||||||
|
// Ensure the user's command prompt starts on the next line.
|
||||||
|
if !strings.HasSuffix(format, "\n") {
|
||||||
|
format += "\n"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, format, args...)
|
||||||
|
panic(format) // Panic instead of os.Exit so that deferred will run.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// This is all for the Console logger - a little wordy but it works
|
||||||
|
|
||||||
|
var _sliceEncoderPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &sliceArrayEncoder{elems: make([]interface{}, 0, 2)}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSliceEncoder() *sliceArrayEncoder {
|
||||||
|
return _sliceEncoderPool.Get().(*sliceArrayEncoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func putSliceEncoder(e *sliceArrayEncoder) {
|
||||||
|
e.elems = e.elems[:0]
|
||||||
|
_sliceEncoderPool.Put(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
type consoleEncoder struct {
|
||||||
|
*zapcore.EncoderConfig
|
||||||
|
openNamespaces int
|
||||||
|
buf *buffer.Buffer
|
||||||
|
reflectBuf *buffer.Buffer
|
||||||
|
reflectEnc *json.Encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_pool = buffer.NewPool()
|
||||||
|
// Get retrieves a buffer from the pool, creating one if necessary.
|
||||||
|
Get = _pool.Get
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// NewConsoleEncoder creates an encoder whose output is designed for human -
|
||||||
|
// rather than machine - consumption. It serializes the core log entry data
|
||||||
|
// (message, level, timestamp, etc.) in a plain-text format and leaves the
|
||||||
|
// structured context as JSON.
|
||||||
|
//
|
||||||
|
// Note that although the console encoder doesn't use the keys specified in the
|
||||||
|
// encoder configuration, it will omit any element whose key is set to the empty
|
||||||
|
// string.
|
||||||
|
func NewConsoleEncoder(cfg zapcore.EncoderConfig) zapcore.Encoder {
|
||||||
|
ec := &consoleEncoder{buf : Get(), reflectBuf: Get()}
|
||||||
|
ec.EncoderConfig = &cfg
|
||||||
|
return ec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c consoleEncoder) Clone() zapcore.Encoder {
|
||||||
|
return &consoleEncoder{buf : Get(), reflectBuf: Get()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c consoleEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
|
||||||
|
line := Get()
|
||||||
|
|
||||||
|
var color = 0
|
||||||
|
switch ent.Level {
|
||||||
|
case zap.PanicLevel:
|
||||||
|
// Magenta
|
||||||
|
color = 35
|
||||||
|
case zap.ErrorLevel:
|
||||||
|
// Red
|
||||||
|
color = 31
|
||||||
|
case zap.WarnLevel:
|
||||||
|
// Yellow
|
||||||
|
color = 33
|
||||||
|
case zap.InfoLevel:
|
||||||
|
// Green
|
||||||
|
color = 32
|
||||||
|
case zap.DebugLevel:
|
||||||
|
// Cyan
|
||||||
|
color = 36
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't want the entry's metadata to be quoted and escaped (if it's
|
||||||
|
// encoded as strings), which means that we can't use the JSON encoder. The
|
||||||
|
// simplest option is to use the memory encoder and fmt.Fprint.
|
||||||
|
//
|
||||||
|
// If this ever becomes a performance bottleneck, we can implement
|
||||||
|
// ArrayEncoder for our plain-text format.
|
||||||
|
arr := getSliceEncoder()
|
||||||
|
if c.LevelKey != "" && c.EncodeLevel != nil {
|
||||||
|
arr.AppendString(fmt.Sprintf("\x1b[%dm%-5s\x1b[0m",color,ent.Level.CapitalString()))
|
||||||
|
}
|
||||||
|
if ent.LoggerName != "" && c.NameKey != "" {
|
||||||
|
nameEncoder := c.EncodeName
|
||||||
|
|
||||||
|
if nameEncoder == nil {
|
||||||
|
// Fall back to FullNameEncoder for backward compatibility.
|
||||||
|
nameEncoder = zapcore.FullNameEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
nameEncoder(ent.LoggerName, arr)
|
||||||
|
}
|
||||||
|
if c.TimeKey != "" && c.EncodeTime != nil {
|
||||||
|
arr.AppendString(ent.Time.Format("15:04:05"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ent.Caller.Defined && c.CallerKey != "" && c.EncodeCaller != nil {
|
||||||
|
c.EncodeCaller(ent.Caller, arr)
|
||||||
|
}
|
||||||
|
for i := range arr.elems {
|
||||||
|
if i > 0 {
|
||||||
|
line.AppendByte(' ')
|
||||||
|
}
|
||||||
|
fmt.Fprint(line, arr.elems[i])
|
||||||
|
}
|
||||||
|
putSliceEncoder(arr)
|
||||||
|
|
||||||
|
// Add the message itself.
|
||||||
|
if c.MessageKey != "" {
|
||||||
|
c.addTabIfNecessary(line)
|
||||||
|
line.AppendString(ent.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any structured context.
|
||||||
|
c.writeContext(line, fields)
|
||||||
|
|
||||||
|
// If there's no stacktrace key, honor that; this allows users to force
|
||||||
|
// single-line output.
|
||||||
|
if ent.Stack != "" && c.StacktraceKey != "" {
|
||||||
|
line.AppendByte('\n')
|
||||||
|
line.AppendString(ent.Stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.LineEnding != "" {
|
||||||
|
line.AppendString(c.LineEnding)
|
||||||
|
} else {
|
||||||
|
line.AppendString(zapcore.DefaultLineEnding)
|
||||||
|
}
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c consoleEncoder) writeContext(line *buffer.Buffer, extra []zapcore.Field) {
|
||||||
|
context := c.Clone().(*consoleEncoder)
|
||||||
|
defer context.buf.Free()
|
||||||
|
//
|
||||||
|
addFields(context, extra)
|
||||||
|
context.closeOpenNamespaces()
|
||||||
|
if context.buf.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//
|
||||||
|
line.Write(context.buf.Bytes())
|
||||||
|
}
|
||||||
|
func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) {
|
||||||
|
for i := range fields {
|
||||||
|
fields[i].AddTo(enc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (c consoleEncoder) addTabIfNecessary(line *buffer.Buffer) {
|
||||||
|
if line.Len() > 0 {
|
||||||
|
line.AppendByte('\t')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AddArray(key string, arr zapcore.ArrayMarshaler) error {
|
||||||
|
enc.addKey(key)
|
||||||
|
return enc.AppendArray(arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AddObject(key string, obj zapcore.ObjectMarshaler) error {
|
||||||
|
enc.addKey(key)
|
||||||
|
return enc.AppendObject(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AddBinary(key string, val []byte) {
|
||||||
|
enc.AddString(key, base64.StdEncoding.EncodeToString(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AddByteString(key string, val []byte) {
|
||||||
|
enc.addKey(key)
|
||||||
|
enc.AppendByteString(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AddBool(key string, val bool) {
|
||||||
|
enc.addKey(key)
|
||||||
|
enc.AppendBool(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AddComplex128(key string, val complex128) {
|
||||||
|
enc.addKey(key)
|
||||||
|
enc.AppendComplex128(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AddDuration(key string, val time.Duration) {
|
||||||
|
enc.addKey(key)
|
||||||
|
enc.AppendDuration(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AddFloat64(key string, val float64) {
|
||||||
|
enc.addKey(key)
|
||||||
|
enc.AppendFloat64(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AddInt64(key string, val int64) {
|
||||||
|
enc.addKey(key)
|
||||||
|
enc.AppendInt64(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AddReflected(key string, obj interface{}) error {
|
||||||
|
enc.resetReflectBuf()
|
||||||
|
err := enc.reflectEnc.Encode(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
enc.reflectBuf.TrimNewline()
|
||||||
|
enc.addKey(key)
|
||||||
|
_, err = enc.buf.Write(enc.reflectBuf.Bytes())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) OpenNamespace(key string) {
|
||||||
|
enc.addKey(key)
|
||||||
|
enc.buf.AppendByte('{')
|
||||||
|
enc.openNamespaces++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AddString(key, val string) {
|
||||||
|
enc.addKey(key)
|
||||||
|
enc.AppendString(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AddTime(key string, val time.Time) {
|
||||||
|
enc.addKey(key)
|
||||||
|
enc.AppendTime(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AddUint64(key string, val uint64) {
|
||||||
|
enc.addKey(key)
|
||||||
|
enc.AppendUint64(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) addKey(key string) {
|
||||||
|
// Print key in different color
|
||||||
|
enc.buf.AppendString(fmt.Sprintf(" \x1b[%dm%s\x1b[0m",36,key))
|
||||||
|
|
||||||
|
enc.buf.AppendByte('=')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AppendArray(arr zapcore.ArrayMarshaler) error {
|
||||||
|
enc.buf.AppendByte('[')
|
||||||
|
err := arr.MarshalLogArray(enc)
|
||||||
|
enc.buf.AppendByte(']')
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AppendObject(obj zapcore.ObjectMarshaler) error {
|
||||||
|
enc.buf.AppendByte('{')
|
||||||
|
err := obj.MarshalLogObject(enc)
|
||||||
|
enc.buf.AppendByte('}')
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AppendBool(val bool) {
|
||||||
|
enc.buf.AppendBool(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AppendByteString(val []byte) {
|
||||||
|
enc.buf.AppendByte('"')
|
||||||
|
enc.safeAddByteString(val)
|
||||||
|
enc.buf.AppendByte('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AppendComplex128(val complex128) {
|
||||||
|
// Cast to a platform-independent, fixed-size type.
|
||||||
|
r, i := float64(real(val)), float64(imag(val))
|
||||||
|
enc.buf.AppendByte('"')
|
||||||
|
// Because we're always in a quoted string, we can use strconv without
|
||||||
|
// special-casing NaN and +/-Inf.
|
||||||
|
enc.buf.AppendFloat(r, 64)
|
||||||
|
enc.buf.AppendByte('+')
|
||||||
|
enc.buf.AppendFloat(i, 64)
|
||||||
|
enc.buf.AppendByte('i')
|
||||||
|
enc.buf.AppendByte('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AppendDuration(val time.Duration) {
|
||||||
|
cur := enc.buf.Len()
|
||||||
|
enc.EncodeDuration(val, enc)
|
||||||
|
if cur == enc.buf.Len() {
|
||||||
|
// User-supplied EncodeDuration is a no-op. Fall back to nanoseconds to keep
|
||||||
|
// JSON valid.
|
||||||
|
enc.AppendInt64(int64(val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AppendInt64(val int64) {
|
||||||
|
enc.buf.AppendInt(val)
|
||||||
|
}
|
||||||
|
func (enc *consoleEncoder) resetReflectBuf() {
|
||||||
|
if enc.reflectBuf == nil {
|
||||||
|
enc.reflectBuf = Get()
|
||||||
|
enc.reflectEnc = json.NewEncoder(enc.reflectBuf)
|
||||||
|
} else {
|
||||||
|
enc.reflectBuf.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (enc *consoleEncoder) AppendReflected(val interface{}) error {
|
||||||
|
enc.resetReflectBuf()
|
||||||
|
err := enc.reflectEnc.Encode(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
enc.reflectBuf.TrimNewline()
|
||||||
|
_, err = enc.buf.Write(enc.reflectBuf.Bytes())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AppendString(val string) {
|
||||||
|
enc.safeAddString(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AppendTime(val time.Time) {
|
||||||
|
cur := enc.buf.Len()
|
||||||
|
enc.EncodeTime(val, enc)
|
||||||
|
if cur == enc.buf.Len() {
|
||||||
|
// User-supplied EncodeTime is a no-op. Fall back to nanos since epoch to keep
|
||||||
|
// output JSON valid.
|
||||||
|
enc.AppendInt64(val.UnixNano())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) AppendUint64(val uint64) {
|
||||||
|
enc.buf.AppendUint(val)
|
||||||
|
}
|
||||||
|
func (enc *consoleEncoder) appendFloat(val float64, bitSize int) {
|
||||||
|
switch {
|
||||||
|
case math.IsNaN(val):
|
||||||
|
enc.buf.AppendString(`"NaN"`)
|
||||||
|
case math.IsInf(val, 1):
|
||||||
|
enc.buf.AppendString(`"+Inf"`)
|
||||||
|
case math.IsInf(val, -1):
|
||||||
|
enc.buf.AppendString(`"-Inf"`)
|
||||||
|
default:
|
||||||
|
enc.buf.AppendFloat(val, bitSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeAddString JSON-escapes a string and appends it to the internal buffer.
|
||||||
|
// Unlike the standard library's encoder, it doesn't attempt to protect the
|
||||||
|
// user from browser vulnerabilities or JSONP-related problems.
|
||||||
|
func (enc *consoleEncoder) safeAddString(s string) {
|
||||||
|
for i := 0; i < len(s); {
|
||||||
|
if enc.tryAddRuneSelf(s[i]) {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r, size := utf8.DecodeRuneInString(s[i:])
|
||||||
|
if enc.tryAddRuneError(r, size) {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
enc.buf.AppendString(s[i : i+size])
|
||||||
|
i += size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeAddByteString is no-alloc equivalent of safeAddString(string(s)) for s []byte.
|
||||||
|
func (enc *consoleEncoder) safeAddByteString(s []byte) {
|
||||||
|
for i := 0; i < len(s); {
|
||||||
|
if enc.tryAddRuneSelf(s[i]) {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r, size := utf8.DecodeRune(s[i:])
|
||||||
|
if enc.tryAddRuneError(r, size) {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
enc.buf.Write(s[i : i+size])
|
||||||
|
i += size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryAddRuneSelf appends b if it is valid UTF-8 character represented in a single byte.
|
||||||
|
func (enc *consoleEncoder) tryAddRuneSelf(b byte) bool {
|
||||||
|
if b >= utf8.RuneSelf {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if 0x20 <= b && b != '\\' && b != '"' {
|
||||||
|
enc.buf.AppendByte(b)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch b {
|
||||||
|
case '\\', '"':
|
||||||
|
enc.buf.AppendByte('\\')
|
||||||
|
enc.buf.AppendByte(b)
|
||||||
|
case '\n':
|
||||||
|
enc.buf.AppendByte('\\')
|
||||||
|
enc.buf.AppendByte('n')
|
||||||
|
case '\r':
|
||||||
|
enc.buf.AppendByte('\\')
|
||||||
|
enc.buf.AppendByte('r')
|
||||||
|
case '\t':
|
||||||
|
enc.buf.AppendByte('\\')
|
||||||
|
enc.buf.AppendByte('t')
|
||||||
|
default:
|
||||||
|
// Encode bytes < 0x20, except for the escape sequences above.
|
||||||
|
enc.buf.AppendString(`\u00`)
|
||||||
|
enc.buf.AppendByte(_hex[b>>4])
|
||||||
|
enc.buf.AppendByte(_hex[b&0xF])
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (enc *consoleEncoder) closeOpenNamespaces() {
|
||||||
|
for i := 0; i < enc.openNamespaces; i++ {
|
||||||
|
enc.buf.AppendByte('}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (enc *consoleEncoder) tryAddRuneError(r rune, size int) bool {
|
||||||
|
if r == utf8.RuneError && size == 1 {
|
||||||
|
enc.buf.AppendString(`\ufffd`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (enc *consoleEncoder) AddComplex64(k string, v complex64) { enc.AddComplex128(k, complex128(v)) }
|
||||||
|
func (enc *consoleEncoder) AddFloat32(k string, v float32) { enc.AddFloat64(k, float64(v)) }
|
||||||
|
func (enc *consoleEncoder) AddInt(k string, v int) { enc.AddInt64(k, int64(v)) }
|
||||||
|
func (enc *consoleEncoder) AddInt32(k string, v int32) { enc.AddInt64(k, int64(v)) }
|
||||||
|
func (enc *consoleEncoder) AddInt16(k string, v int16) { enc.AddInt64(k, int64(v)) }
|
||||||
|
func (enc *consoleEncoder) AddInt8(k string, v int8) { enc.AddInt64(k, int64(v)) }
|
||||||
|
func (enc *consoleEncoder) AddUint(k string, v uint) { enc.AddUint64(k, uint64(v)) }
|
||||||
|
func (enc *consoleEncoder) AddUint32(k string, v uint32) { enc.AddUint64(k, uint64(v)) }
|
||||||
|
func (enc *consoleEncoder) AddUint16(k string, v uint16) { enc.AddUint64(k, uint64(v)) }
|
||||||
|
func (enc *consoleEncoder) AddUint8(k string, v uint8) { enc.AddUint64(k, uint64(v)) }
|
||||||
|
func (enc *consoleEncoder) AddUintptr(k string, v uintptr) { enc.AddUint64(k, uint64(v)) }
|
||||||
|
func (enc *consoleEncoder) AppendComplex64(v complex64) { enc.AppendComplex128(complex128(v)) }
|
||||||
|
func (enc *consoleEncoder) AppendFloat64(v float64) { enc.appendFloat(v, 64) }
|
||||||
|
func (enc *consoleEncoder) AppendFloat32(v float32) { enc.appendFloat(float64(v), 32) }
|
||||||
|
func (enc *consoleEncoder) AppendInt(v int) { enc.AppendInt64(int64(v)) }
|
||||||
|
func (enc *consoleEncoder) AppendInt32(v int32) { enc.AppendInt64(int64(v)) }
|
||||||
|
func (enc *consoleEncoder) AppendInt16(v int16) { enc.AppendInt64(int64(v)) }
|
||||||
|
func (enc *consoleEncoder) AppendInt8(v int8) { enc.AppendInt64(int64(v)) }
|
||||||
|
func (enc *consoleEncoder) AppendUint(v uint) { enc.AppendUint64(uint64(v)) }
|
||||||
|
func (enc *consoleEncoder) AppendUint32(v uint32) { enc.AppendUint64(uint64(v)) }
|
||||||
|
func (enc *consoleEncoder) AppendUint16(v uint16) { enc.AppendUint64(uint64(v)) }
|
||||||
|
func (enc *consoleEncoder) AppendUint8(v uint8) { enc.AppendUint64(uint64(v)) }
|
||||||
|
func (enc *consoleEncoder) AppendUintptr(v uintptr) { enc.AppendUint64(uint64(v)) }
|
||||||
|
|
||||||
|
const _hex = "0123456789abcdef"
|
||||||
|
|
||||||
|
type sliceArrayEncoder struct {
|
||||||
|
elems []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sliceArrayEncoder) AppendArray(v zapcore.ArrayMarshaler) error {
|
||||||
|
enc := &sliceArrayEncoder{}
|
||||||
|
err := v.MarshalLogArray(enc)
|
||||||
|
s.elems = append(s.elems, enc.elems)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sliceArrayEncoder) AppendObject(v zapcore.ObjectMarshaler) error {
|
||||||
|
m := zapcore.NewMapObjectEncoder()
|
||||||
|
err := v.MarshalLogObject(m)
|
||||||
|
s.elems = append(s.elems, m.Fields)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sliceArrayEncoder) AppendReflected(v interface{}) error {
|
||||||
|
s.elems = append(s.elems, v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sliceArrayEncoder) AppendBool(v bool) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendByteString(v []byte) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendComplex128(v complex128) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendComplex64(v complex64) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendDuration(v time.Duration) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendFloat64(v float64) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendFloat32(v float32) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendInt(v int) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendInt64(v int64) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendInt32(v int32) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendInt16(v int16) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendInt8(v int8) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendString(v string) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendTime(v time.Time) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendUint(v uint) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendUint64(v uint64) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendUint32(v uint32) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendUint16(v uint16) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendUint8(v uint8) { s.elems = append(s.elems, v) }
|
||||||
|
func (s *sliceArrayEncoder) AppendUintptr(v uintptr) { s.elems = append(s.elems, v) }
|
||||||
227
logger/logger.go
Normal file
227
logger/logger.go
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/revel/config"
|
||||||
|
"github.com/revel/log15"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The LogHandler defines the interface to handle the log records
|
||||||
|
type (
|
||||||
|
// The Multilogger reduces the number of exposed defined logging variables,
|
||||||
|
// and allows the output to be easily refined
|
||||||
|
MultiLogger interface {
|
||||||
|
//log15.Logger
|
||||||
|
//// New returns a new Logger that has this logger's context plus the given context
|
||||||
|
New(ctx ...interface{}) MultiLogger
|
||||||
|
//
|
||||||
|
//// SetHandler updates the logger to write records to the specified handler.
|
||||||
|
SetHandler(h LogHandler)
|
||||||
|
SetStackDepth(int) MultiLogger
|
||||||
|
//
|
||||||
|
//// Log a message at the given level with context key/value pairs
|
||||||
|
Debug(msg string, ctx ...interface{})
|
||||||
|
Debugf(msg string, params ...interface{})
|
||||||
|
Info(msg string, ctx ...interface{})
|
||||||
|
Infof(msg string, params ...interface{})
|
||||||
|
Warn(msg string, ctx ...interface{})
|
||||||
|
Warnf(msg string, params ...interface{})
|
||||||
|
Error(msg string, ctx ...interface{})
|
||||||
|
Errorf(msg string, params ...interface{})
|
||||||
|
Crit(msg string, ctx ...interface{})
|
||||||
|
Critf(msg string, params ...interface{})
|
||||||
|
|
||||||
|
//// Logs a message as an Crit and exits
|
||||||
|
Fatal(msg string, ctx ...interface{})
|
||||||
|
Fatalf(msg string, params ...interface{})
|
||||||
|
//// Logs a message as an Crit and panics
|
||||||
|
Panic(msg string, ctx ...interface{})
|
||||||
|
Panicf(msg string, params ...interface{})
|
||||||
|
}
|
||||||
|
LogHandler interface {
|
||||||
|
log15.Handler
|
||||||
|
}
|
||||||
|
LogStackHandler interface {
|
||||||
|
LogHandler
|
||||||
|
GetStack() int
|
||||||
|
}
|
||||||
|
ParentLogHandler interface {
|
||||||
|
SetChild(handler LogHandler) LogHandler
|
||||||
|
}
|
||||||
|
LogFormat interface {
|
||||||
|
log15.Format
|
||||||
|
}
|
||||||
|
|
||||||
|
LogLevel log15.Lvl
|
||||||
|
RevelLogger struct {
|
||||||
|
log15.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for the callback to LogFunctionMap
|
||||||
|
LogOptions struct {
|
||||||
|
Ctx *config.Context
|
||||||
|
ReplaceExistingHandler bool
|
||||||
|
HandlerWrap ParentLogHandler
|
||||||
|
Levels []LogLevel
|
||||||
|
ExtendedOptions map[string]interface{}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LvlDebug = LogLevel(log15.LvlDebug)
|
||||||
|
LvlInfo = LogLevel(log15.LvlInfo)
|
||||||
|
LvlWarn = LogLevel(log15.LvlWarn)
|
||||||
|
LvlError = LogLevel(log15.LvlError)
|
||||||
|
LvlCrit = LogLevel(log15.LvlCrit)
|
||||||
|
)
|
||||||
|
|
||||||
|
// A list of all the log levels
|
||||||
|
var LvlAllList = []LogLevel{LvlDebug, LvlInfo, LvlWarn, LvlError, LvlCrit}
|
||||||
|
|
||||||
|
// The log function map can be added to, so that you can specify your own logging mechanism
|
||||||
|
var LogFunctionMap = map[string]func(*CompositeMultiHandler, *LogOptions){
|
||||||
|
// Do nothing - set the logger off
|
||||||
|
"off": func(c *CompositeMultiHandler, logOptions *LogOptions) {
|
||||||
|
// Only drop the results if there is a parent handler defined
|
||||||
|
if logOptions.HandlerWrap != nil {
|
||||||
|
for _, l := range logOptions.Levels {
|
||||||
|
c.SetHandler(logOptions.HandlerWrap.SetChild(NilHandler()), logOptions.ReplaceExistingHandler, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Do nothing - set the logger off
|
||||||
|
"": func(*CompositeMultiHandler, *LogOptions) {},
|
||||||
|
// Set the levels to stdout, replace existing
|
||||||
|
"stdout": func(c *CompositeMultiHandler, logOptions *LogOptions) {
|
||||||
|
if logOptions.Ctx != nil {
|
||||||
|
logOptions.SetExtendedOptions(
|
||||||
|
"noColor", !logOptions.Ctx.BoolDefault("log.colorize", true),
|
||||||
|
"smallDate", logOptions.Ctx.BoolDefault("log.smallDate", true))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SetTerminal(os.Stdout, logOptions)
|
||||||
|
},
|
||||||
|
// Set the levels to stderr output to terminal
|
||||||
|
"stderr": func(c *CompositeMultiHandler, logOptions *LogOptions) {
|
||||||
|
c.SetTerminal(os.Stderr, logOptions)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the systems default logger
|
||||||
|
// Default logs will be captured and handled by revel at level info
|
||||||
|
func SetDefaultLog(fromLog MultiLogger) {
|
||||||
|
log.SetOutput(loggerRewrite{Logger: fromLog, Level: log15.LvlInfo, hideDeprecated: true})
|
||||||
|
// No need to show date and time, that will be logged with revel
|
||||||
|
log.SetFlags(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatted debug call
|
||||||
|
func (rl *RevelLogger) Debugf(msg string, param ...interface{}) {
|
||||||
|
rl.Debug(fmt.Sprintf(msg, param...))
|
||||||
|
}
|
||||||
|
// Formatted info call
|
||||||
|
func (rl *RevelLogger) Infof(msg string, param ...interface{}) {
|
||||||
|
rl.Info(fmt.Sprintf(msg, param...))
|
||||||
|
}
|
||||||
|
func (rl *RevelLogger) Warnf(msg string, param ...interface{}) {
|
||||||
|
rl.Warn(fmt.Sprintf(msg, param...))
|
||||||
|
}
|
||||||
|
func (rl *RevelLogger) Errorf(msg string, param ...interface{}) {
|
||||||
|
rl.Error(fmt.Sprintf(msg, param...))
|
||||||
|
}
|
||||||
|
func (rl *RevelLogger) Critf(msg string, param ...interface{}) {
|
||||||
|
rl.Crit(fmt.Sprintf(msg, param...))
|
||||||
|
}
|
||||||
|
func (rl *RevelLogger) Fatalf(msg string, param ...interface{}) {
|
||||||
|
rl.Crit(fmt.Sprintf(msg, param...))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
func (rl *RevelLogger) Panicf(msg string, param ...interface{}) {
|
||||||
|
rl.Crit(fmt.Sprintf(msg, param...))
|
||||||
|
panic(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *RevelLogger) Fatal(msg string, ctx ...interface{}) {
|
||||||
|
rl.Crit(msg, ctx...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *RevelLogger) Panic(msg string, ctx ...interface{}) {
|
||||||
|
rl.Crit(msg, ctx...)
|
||||||
|
panic(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override log15 method
|
||||||
|
func (rl *RevelLogger) New(ctx ...interface{}) MultiLogger {
|
||||||
|
old := &RevelLogger{Logger: rl.Logger.New(ctx...)}
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the stack level to check for the caller
|
||||||
|
func (rl *RevelLogger) SetStackDepth(amount int) MultiLogger {
|
||||||
|
rl.Logger.SetStackDepth(amount) // Ignore the logger returned
|
||||||
|
return rl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new logger
|
||||||
|
func New(ctx ...interface{}) MultiLogger {
|
||||||
|
r := &RevelLogger{Logger: log15.New(ctx...)}
|
||||||
|
r.SetStackDepth(1)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the handler in the Logger
|
||||||
|
func (rl *RevelLogger) SetHandler(h LogHandler) {
|
||||||
|
rl.Logger.SetHandler(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
type parentLogHandler struct {
|
||||||
|
setChild func(handler LogHandler) LogHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewParentLogHandler(callBack func(child LogHandler) LogHandler) ParentLogHandler {
|
||||||
|
return &parentLogHandler{callBack}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parentLogHandler) SetChild(child LogHandler) LogHandler {
|
||||||
|
return p.setChild(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new log options
|
||||||
|
func NewLogOptions(cfg *config.Context, replaceHandler bool, phandler ParentLogHandler, lvl ...LogLevel) (logOptions *LogOptions) {
|
||||||
|
logOptions = &LogOptions{
|
||||||
|
Ctx: cfg,
|
||||||
|
ReplaceExistingHandler: replaceHandler,
|
||||||
|
HandlerWrap: phandler,
|
||||||
|
Levels: lvl,
|
||||||
|
ExtendedOptions: map[string]interface{}{},
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes options will be an even number and have a string, value syntax
|
||||||
|
func (l *LogOptions) SetExtendedOptions(options ...interface{}) {
|
||||||
|
for x := 0; x < len(options); x += 2 {
|
||||||
|
l.ExtendedOptions[options[x].(string)] = options[x+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (l *LogOptions) GetStringDefault(option, value string) string {
|
||||||
|
if v, found := l.ExtendedOptions[option]; found {
|
||||||
|
return v.(string)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
func (l *LogOptions) GetIntDefault(option string, value int) int {
|
||||||
|
if v, found := l.ExtendedOptions[option]; found {
|
||||||
|
return v.(int)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
func (l *LogOptions) GetBoolDefault(option string, value bool) bool {
|
||||||
|
if v, found := l.ExtendedOptions[option]; found {
|
||||||
|
return v.(bool)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
277
logger/utils.go
Normal file
277
logger/utils.go
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopkg.in/stack.v0"
|
||||||
|
"github.com/revel/config"
|
||||||
|
"github.com/revel/log15"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Utility package to make existing logging backwards compatible
|
||||||
|
var (
|
||||||
|
// Convert the string to LogLevel
|
||||||
|
toLevel = map[string]LogLevel{"debug": LogLevel(log15.LvlDebug),
|
||||||
|
"info": LogLevel(log15.LvlInfo), "request": LogLevel(log15.LvlInfo), "warn": LogLevel(log15.LvlWarn),
|
||||||
|
"error": LogLevel(log15.LvlError), "crit": LogLevel(log15.LvlCrit),
|
||||||
|
"trace": LogLevel(log15.LvlDebug), // TODO trace is deprecated, replaced by debug
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetLogger(name string, logger MultiLogger) (l *log.Logger) {
|
||||||
|
switch name {
|
||||||
|
case "trace": // TODO trace is deprecated, replaced by debug
|
||||||
|
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlDebug}, "", 0)
|
||||||
|
case "debug":
|
||||||
|
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlDebug}, "", 0)
|
||||||
|
case "info":
|
||||||
|
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlInfo}, "", 0)
|
||||||
|
case "warn":
|
||||||
|
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlWarn}, "", 0)
|
||||||
|
case "error":
|
||||||
|
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlError}, "", 0)
|
||||||
|
case "request":
|
||||||
|
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlInfo}, "", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return l
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all handlers based on the Config (if available)
|
||||||
|
func InitializeFromConfig(basePath string, config *config.Context) (c *CompositeMultiHandler) {
|
||||||
|
// If running in test mode suppress anything that is not an error
|
||||||
|
if config!=nil && config.BoolDefault("testModeFlag",false) {
|
||||||
|
config.SetOption("log.info.output","none")
|
||||||
|
config.SetOption("log.debug.output","none")
|
||||||
|
config.SetOption("log.warn.output","none")
|
||||||
|
config.SetOption("log.error.output","stderr")
|
||||||
|
config.SetOption("log.crit.output","stderr")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// If the configuration has an all option we can skip some
|
||||||
|
c, _ = NewCompositeMultiHandler()
|
||||||
|
|
||||||
|
// Filters are assigned first, non filtered items override filters
|
||||||
|
initAllLog(c, basePath, config)
|
||||||
|
initLogLevels(c, basePath, config)
|
||||||
|
if c.CriticalHandler == nil && c.ErrorHandler != nil {
|
||||||
|
c.CriticalHandler = c.ErrorHandler
|
||||||
|
}
|
||||||
|
initFilterLog(c, basePath, config)
|
||||||
|
if c.CriticalHandler == nil && c.ErrorHandler != nil {
|
||||||
|
c.CriticalHandler = c.ErrorHandler
|
||||||
|
}
|
||||||
|
initRequestLog(c, basePath, config)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init the log.all configuration options
|
||||||
|
func initAllLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
|
||||||
|
if config != nil {
|
||||||
|
extraLogFlag := config.BoolDefault("specialUseFlag", false)
|
||||||
|
if output, found := config.String("log.all.output"); found {
|
||||||
|
// Set all output for the specified handler
|
||||||
|
if extraLogFlag {
|
||||||
|
log.Printf("Adding standard handler for levels to >%s< ", output)
|
||||||
|
}
|
||||||
|
initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, LvlAllList...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init the filter options
|
||||||
|
// log.all.filter ....
|
||||||
|
// log.error.filter ....
|
||||||
|
func initFilterLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
|
||||||
|
if config != nil {
|
||||||
|
extraLogFlag := config.BoolDefault("specialUseFlag", false)
|
||||||
|
|
||||||
|
// The commands to use
|
||||||
|
logFilterList := []struct {
|
||||||
|
LogPrefix, LogSuffix string
|
||||||
|
parentHandler func(map[string]interface{}) ParentLogHandler
|
||||||
|
}{{
|
||||||
|
"log.", ".filter",
|
||||||
|
func(keyMap map[string]interface{}) ParentLogHandler {
|
||||||
|
return NewParentLogHandler(func(child LogHandler) LogHandler {
|
||||||
|
return MatchMapHandler(keyMap, child)
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
"log.", ".nfilter",
|
||||||
|
func(keyMap map[string]interface{}) ParentLogHandler {
|
||||||
|
return NewParentLogHandler(func(child LogHandler) LogHandler {
|
||||||
|
return NotMatchMapHandler(keyMap, child)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, logFilter := range logFilterList {
|
||||||
|
// Init for all filters
|
||||||
|
for _, name := range []string{"all", "debug", "info", "warn", "error", "crit",
|
||||||
|
"trace", // TODO trace is deprecated
|
||||||
|
} {
|
||||||
|
optionList := config.Options(logFilter.LogPrefix + name + logFilter.LogSuffix)
|
||||||
|
for _, option := range optionList {
|
||||||
|
splitOptions := strings.Split(option, ".")
|
||||||
|
keyMap := map[string]interface{}{}
|
||||||
|
for x := 3; x < len(splitOptions); x += 2 {
|
||||||
|
keyMap[splitOptions[x]] = splitOptions[x+1]
|
||||||
|
}
|
||||||
|
phandler := logFilter.parentHandler(keyMap)
|
||||||
|
if extraLogFlag {
|
||||||
|
log.Printf("Adding key map handler %s %s output %s", option, name, config.StringDefault(option, ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "all" {
|
||||||
|
initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler))
|
||||||
|
} else {
|
||||||
|
initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler, toLevel[name]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init the log.error, log.warn etc configuration options
|
||||||
|
func initLogLevels(c *CompositeMultiHandler, basePath string, config *config.Context) {
|
||||||
|
for _, name := range []string{"debug", "info", "warn", "error", "crit",
|
||||||
|
"trace", // TODO trace is deprecated
|
||||||
|
} {
|
||||||
|
if config != nil {
|
||||||
|
extraLogFlag := config.BoolDefault("specialUseFlag", false)
|
||||||
|
output, found := config.String("log." + name + ".output")
|
||||||
|
if found {
|
||||||
|
if extraLogFlag {
|
||||||
|
log.Printf("Adding standard handler %s output %s", name, output)
|
||||||
|
}
|
||||||
|
initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, toLevel[name]))
|
||||||
|
}
|
||||||
|
// Gets the list of options with said prefix
|
||||||
|
} else {
|
||||||
|
initHandlerFor(c, "stderr", basePath, NewLogOptions(config, true, nil, toLevel[name]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init the request log options
|
||||||
|
func initRequestLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
|
||||||
|
// Request logging to a separate output handler
|
||||||
|
// This takes the InfoHandlers and adds a MatchAbHandler handler to it to direct
|
||||||
|
// context with the word "section=requestlog" to that handler.
|
||||||
|
// Note if request logging is not enabled the MatchAbHandler will not be added and the
|
||||||
|
// request log messages will be sent out the INFO handler
|
||||||
|
outputRequest := "stdout"
|
||||||
|
if config != nil {
|
||||||
|
outputRequest = config.StringDefault("log.request.output", "")
|
||||||
|
}
|
||||||
|
oldInfo := c.InfoHandler
|
||||||
|
c.InfoHandler = nil
|
||||||
|
if outputRequest != "" {
|
||||||
|
initHandlerFor(c, outputRequest, basePath, NewLogOptions(config, false, nil, LvlInfo))
|
||||||
|
}
|
||||||
|
if c.InfoHandler != nil || oldInfo != nil {
|
||||||
|
if c.InfoHandler == nil {
|
||||||
|
c.InfoHandler = oldInfo
|
||||||
|
} else {
|
||||||
|
c.InfoHandler = MatchAbHandler("section", "requestlog", c.InfoHandler, oldInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a handler for the level using the output string
|
||||||
|
// Accept formats for output string are
|
||||||
|
// LogFunctionMap[value] callback function
|
||||||
|
// `stdout` `stderr` `full/file/path/to/location/app.log` `full/file/path/to/location/app.json`
|
||||||
|
func initHandlerFor(c *CompositeMultiHandler, output, basePath string, options *LogOptions) {
|
||||||
|
if options.Ctx != nil {
|
||||||
|
options.SetExtendedOptions(
|
||||||
|
"noColor", !options.Ctx.BoolDefault("log.colorize", true),
|
||||||
|
"smallDate", options.Ctx.BoolDefault("log.smallDate", true),
|
||||||
|
"maxSize", options.Ctx.IntDefault("log.maxsize", 1024*10),
|
||||||
|
"maxAge", options.Ctx.IntDefault("log.maxage", 14),
|
||||||
|
"maxBackups", options.Ctx.IntDefault("log.maxbackups", 14),
|
||||||
|
"compressBackups", !options.Ctx.BoolDefault("log.compressBackups", true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
output = strings.TrimSpace(output)
|
||||||
|
if funcHandler, found := LogFunctionMap[output]; found {
|
||||||
|
funcHandler(c, options)
|
||||||
|
} else {
|
||||||
|
switch output {
|
||||||
|
case "":
|
||||||
|
fallthrough
|
||||||
|
case "off":
|
||||||
|
// No handler, discard data
|
||||||
|
default:
|
||||||
|
// Write to file specified
|
||||||
|
if !filepath.IsAbs(output) {
|
||||||
|
output = filepath.Join(basePath, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(output, "json") {
|
||||||
|
c.SetJsonFile(output, options)
|
||||||
|
} else {
|
||||||
|
// Override defaults for a terminal file
|
||||||
|
options.SetExtendedOptions("noColor", true)
|
||||||
|
options.SetExtendedOptions("smallDate", false)
|
||||||
|
c.SetTerminalFile(output, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// This structure and method will handle the old output format and log it to the new format
|
||||||
|
type loggerRewrite struct {
|
||||||
|
Logger MultiLogger
|
||||||
|
Level log15.Lvl
|
||||||
|
hideDeprecated bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var log_deprecated = []byte("* LOG DEPRECATED * ")
|
||||||
|
|
||||||
|
func (lr loggerRewrite) Write(p []byte) (n int, err error) {
|
||||||
|
if !lr.hideDeprecated {
|
||||||
|
p = append(log_deprecated, p...)
|
||||||
|
}
|
||||||
|
n = len(p)
|
||||||
|
if len(p) > 0 && p[n-1] == '\n' {
|
||||||
|
p = p[:n-1]
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
|
||||||
|
switch lr.Level {
|
||||||
|
case log15.LvlInfo:
|
||||||
|
lr.Logger.Info(string(p))
|
||||||
|
case log15.LvlDebug:
|
||||||
|
lr.Logger.Debug(string(p))
|
||||||
|
case log15.LvlWarn:
|
||||||
|
lr.Logger.Warn(string(p))
|
||||||
|
case log15.LvlError:
|
||||||
|
lr.Logger.Error(string(p))
|
||||||
|
case log15.LvlCrit:
|
||||||
|
lr.Logger.Crit(string(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// For logging purposes the call stack can be used to record the stack trace of a bad error
|
||||||
|
// simply pass it as a context field in your log statement like
|
||||||
|
// `controller.Log.Critc("This should not occur","stack",revel.NewCallStack())`
|
||||||
|
func NewCallStack() interface{} {
|
||||||
|
return stack.Trace()
|
||||||
|
}
|
||||||
61
model/command_config.go
Normal file
61
model/command_config.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type (
|
||||||
|
// The Revel command type
|
||||||
|
COMMAND int
|
||||||
|
|
||||||
|
// The Command config for the line input
|
||||||
|
CommandConfig struct {
|
||||||
|
Index COMMAND // The index
|
||||||
|
Verbose bool `short:"v" long:"debug" description:"If set the logger is set to verbose"` // True if debug is active
|
||||||
|
HistoricMode bool `long:"historic-run-mode" description:"If set the runmode is passed a string not json"` // True if debug is active
|
||||||
|
ImportPath string // The import path (converted from various commands)
|
||||||
|
GoPath string // The GoPath
|
||||||
|
GoCmd string // The full path to the go executable
|
||||||
|
SrcRoot string // The source root
|
||||||
|
AppPath string // The application path
|
||||||
|
AppName string // The applicaiton name
|
||||||
|
BasePath string // The base path
|
||||||
|
SkeletonPath string // The skeleton path
|
||||||
|
BuildFlags []string `short:"X" long:"build-flags" description:"These flags will be used when building the application. May be specified multiple times, only applicable for Build, Run, Package, Test commands"`
|
||||||
|
// The new command
|
||||||
|
New struct {
|
||||||
|
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
|
||||||
|
Skeleton string `short:"s" long:"skeleton" description:"Path to skeleton folder (Must exist on GO PATH)" required:"false"`
|
||||||
|
Vendored bool `short:"V" long:"vendor" description:"True if project should contain a vendor folder to be initialized. Creates the vendor folder and the 'Gopkg.toml' file in the root"`
|
||||||
|
Run bool `short:"r" long:"run" description:"True if you want to run the application right away"`
|
||||||
|
} `command:"new"`
|
||||||
|
// The build command
|
||||||
|
Build struct {
|
||||||
|
TargetPath string `short:"t" long:"target-path" description:"Path to target folder. Folder will be completely deleted if it exists" required:"true"`
|
||||||
|
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
|
||||||
|
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
|
||||||
|
} `command:"build"`
|
||||||
|
// The run command
|
||||||
|
Run struct {
|
||||||
|
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
|
||||||
|
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
|
||||||
|
Port string `short:"p" long:"port" description:"The port to listen"`
|
||||||
|
NoProxy bool `short:"n" long:"no-proxy" description:"True if proxy server should not be started. This will only update the main and routes files on change"`
|
||||||
|
} `command:"run"`
|
||||||
|
// The package command
|
||||||
|
Package struct {
|
||||||
|
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
|
||||||
|
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
|
||||||
|
} `command:"package"`
|
||||||
|
// The clean command
|
||||||
|
Clean struct {
|
||||||
|
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
|
||||||
|
} `command:"clean"`
|
||||||
|
// The test command
|
||||||
|
Test struct {
|
||||||
|
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
|
||||||
|
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
|
||||||
|
Function string `short:"f" long:"suite-function" description:"The suite.function"`
|
||||||
|
} `command:"test"`
|
||||||
|
// The version command
|
||||||
|
Version struct {
|
||||||
|
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"false"`
|
||||||
|
} `command:"version"`
|
||||||
|
}
|
||||||
|
)
|
||||||
11
model/embedded_type_name.go
Normal file
11
model/embedded_type_name.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
// The embedded type name takes the import path and structure name
|
||||||
|
type EmbeddedTypeName struct {
|
||||||
|
ImportPath, StructName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the type to a properly formatted import line
|
||||||
|
func (s *EmbeddedTypeName) String() string {
|
||||||
|
return s.ImportPath + "." + s.StructName
|
||||||
|
}
|
||||||
25
model/method.go
Normal file
25
model/method.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
|
||||||
|
// methodCall describes a call to c.Render(..)
|
||||||
|
// It documents the argument names used, in order to propagate them to RenderArgs.
|
||||||
|
type MethodCall struct {
|
||||||
|
Path string // e.g. "myapp/app/controllers.(*Application).Action"
|
||||||
|
Line int
|
||||||
|
Names []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MethodSpec holds the information of one Method
|
||||||
|
type MethodSpec struct {
|
||||||
|
Name string // Name of the method, e.g. "Index"
|
||||||
|
Args []*MethodArg // Argument descriptors
|
||||||
|
RenderCalls []*MethodCall // Descriptions of Render() invocations from this Method.
|
||||||
|
}
|
||||||
|
|
||||||
|
// MethodArg holds the information of one argument
|
||||||
|
type MethodArg struct {
|
||||||
|
Name string // Name of the argument.
|
||||||
|
TypeExpr TypeExpr // The name of the type, e.g. "int", "*pkg.UserType"
|
||||||
|
ImportPath string // If the arg is of an imported type, this is the import path.
|
||||||
|
}
|
||||||
|
|
||||||
294
model/revel_container.go
Normal file
294
model/revel_container.go
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
// This package will be shared between Revel and Revel CLI eventually
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"github.com/revel/config"
|
||||||
|
"go/build"
|
||||||
|
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
const (
|
||||||
|
// Event type when templates are going to be refreshed (receivers are registered template engines added to the template.engine conf option)
|
||||||
|
TEMPLATE_REFRESH_REQUESTED = iota
|
||||||
|
// Event type when templates are refreshed (receivers are registered template engines added to the template.engine conf option)
|
||||||
|
TEMPLATE_REFRESH_COMPLETED
|
||||||
|
// Event type before all module loads, events thrown to handlers added to AddInitEventHandler
|
||||||
|
|
||||||
|
// Event type before all module loads, events thrown to handlers added to AddInitEventHandler
|
||||||
|
REVEL_BEFORE_MODULES_LOADED
|
||||||
|
// Event type called when a new module is found
|
||||||
|
REVEL_BEFORE_MODULE_LOADED
|
||||||
|
// Event type called when after a new module is found
|
||||||
|
REVEL_AFTER_MODULE_LOADED
|
||||||
|
// Event type after all module loads, events thrown to handlers added to AddInitEventHandler
|
||||||
|
REVEL_AFTER_MODULES_LOADED
|
||||||
|
|
||||||
|
// Event type before server engine is initialized, receivers are active server engine and handlers added to AddInitEventHandler
|
||||||
|
ENGINE_BEFORE_INITIALIZED
|
||||||
|
// Event type before server engine is started, receivers are active server engine and handlers added to AddInitEventHandler
|
||||||
|
ENGINE_STARTED
|
||||||
|
// Event type after server engine is stopped, receivers are active server engine and handlers added to AddInitEventHandler
|
||||||
|
ENGINE_SHUTDOWN
|
||||||
|
|
||||||
|
// Called before routes are refreshed
|
||||||
|
ROUTE_REFRESH_REQUESTED
|
||||||
|
// Called after routes have been refreshed
|
||||||
|
ROUTE_REFRESH_COMPLETED
|
||||||
|
)
|
||||||
|
type (
|
||||||
|
// The container object for describing all Revels variables
|
||||||
|
RevelContainer struct {
|
||||||
|
ImportPath string // The import path
|
||||||
|
SourcePath string // The full source path
|
||||||
|
RunMode string // The current run mode
|
||||||
|
RevelPath string // The path to the Revel source code
|
||||||
|
BasePath string // The base path to the application
|
||||||
|
AppPath string // The application path (BasePath + "/app"
|
||||||
|
ViewsPath string // The application views path
|
||||||
|
CodePaths []string // All the code paths
|
||||||
|
TemplatePaths []string // All the template paths
|
||||||
|
ConfPaths []string // All the configuration paths
|
||||||
|
Config *config.Context // The global config object
|
||||||
|
Packaged bool // True if packaged
|
||||||
|
DevMode bool // True if running in dev mode
|
||||||
|
HTTPPort int // The http port
|
||||||
|
HTTPAddr string // The http address
|
||||||
|
HTTPSsl bool // True if running https
|
||||||
|
HTTPSslCert string // The SSL certificate
|
||||||
|
HTTPSslKey string // The SSL key
|
||||||
|
AppName string // The application name
|
||||||
|
AppRoot string // The application root from the config `app.root`
|
||||||
|
CookiePrefix string // The cookie prefix
|
||||||
|
CookieDomain string // The cookie domain
|
||||||
|
CookieSecure bool // True if cookie is secure
|
||||||
|
SecretStr string // The secret string
|
||||||
|
MimeConfig *config.Context // The mime configuration
|
||||||
|
ModulePathMap map[string]string // The module path map
|
||||||
|
}
|
||||||
|
|
||||||
|
RevelCallback interface {
|
||||||
|
FireEvent(key int, value interface{}) (response int)
|
||||||
|
}
|
||||||
|
doNothingRevelCallback struct {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
// Simple callback to pass to the RevelCallback that does nothing
|
||||||
|
var DoNothingRevelCallback = RevelCallback(&doNothingRevelCallback{})
|
||||||
|
|
||||||
|
func (_ *doNothingRevelCallback) FireEvent(key int, value interface{}) (response int) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevelImportPath Revel framework import path
|
||||||
|
var RevelImportPath = "github.com/revel/revel"
|
||||||
|
|
||||||
|
// This function returns a container object describing the revel application
|
||||||
|
// eventually this type of function will replace the global variables.
|
||||||
|
func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp *RevelContainer) {
|
||||||
|
rp = &RevelContainer{ModulePathMap: map[string]string{}}
|
||||||
|
// Ignore trailing slashes.
|
||||||
|
rp.ImportPath = strings.TrimRight(importPath, "/")
|
||||||
|
rp.SourcePath = srcPath
|
||||||
|
rp.RunMode = mode
|
||||||
|
|
||||||
|
// If the SourcePath is not specified, find it using build.Import.
|
||||||
|
var revelSourcePath string // may be different from the app source path
|
||||||
|
if rp.SourcePath == "" {
|
||||||
|
revelSourcePath, rp.SourcePath = findSrcPaths(importPath)
|
||||||
|
} else {
|
||||||
|
// If the SourcePath was specified, assume both Revel and the app are within it.
|
||||||
|
rp.SourcePath = filepath.Clean(rp.SourcePath)
|
||||||
|
revelSourcePath = rp.SourcePath
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup paths for application
|
||||||
|
rp.RevelPath = filepath.Join(revelSourcePath, filepath.FromSlash(RevelImportPath))
|
||||||
|
rp.BasePath = filepath.Join(rp.SourcePath, filepath.FromSlash(importPath))
|
||||||
|
rp.AppPath = filepath.Join(rp.BasePath, "app")
|
||||||
|
rp.ViewsPath = filepath.Join(rp.AppPath, "views")
|
||||||
|
|
||||||
|
rp.CodePaths = []string{rp.AppPath}
|
||||||
|
rp.TemplatePaths = []string{}
|
||||||
|
|
||||||
|
if rp.ConfPaths == nil {
|
||||||
|
rp.ConfPaths = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config load order
|
||||||
|
// 1. framework (revel/conf/*)
|
||||||
|
// 2. application (conf/*)
|
||||||
|
// 3. user supplied configs (...) - User configs can override/add any from above
|
||||||
|
rp.ConfPaths = append(
|
||||||
|
[]string{
|
||||||
|
filepath.Join(rp.RevelPath, "conf"),
|
||||||
|
filepath.Join(rp.BasePath, "conf"),
|
||||||
|
},
|
||||||
|
rp.ConfPaths...)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
rp.Config, err = config.LoadContext("app.conf", rp.ConfPaths)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Unable to load configuartion file ","error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the selected runmode appears in app.conf.
|
||||||
|
// If empty string is passed as the mode, treat it as "DEFAULT"
|
||||||
|
if mode == "" {
|
||||||
|
mode = config.DefaultSection
|
||||||
|
}
|
||||||
|
if !rp.Config.HasSection(mode) {
|
||||||
|
utils.Logger.Fatal("app.conf: No mode found:", mode)
|
||||||
|
}
|
||||||
|
rp.Config.SetSection(mode)
|
||||||
|
|
||||||
|
// Configure properties from app.conf
|
||||||
|
rp.DevMode = rp.Config.BoolDefault("mode.dev", false)
|
||||||
|
rp.HTTPPort = rp.Config.IntDefault("http.port", 9000)
|
||||||
|
rp.HTTPAddr = rp.Config.StringDefault("http.addr", "")
|
||||||
|
rp.HTTPSsl = rp.Config.BoolDefault("http.ssl", false)
|
||||||
|
rp.HTTPSslCert = rp.Config.StringDefault("http.sslcert", "")
|
||||||
|
rp.HTTPSslKey = rp.Config.StringDefault("http.sslkey", "")
|
||||||
|
if rp.HTTPSsl {
|
||||||
|
if rp.HTTPSslCert == "" {
|
||||||
|
utils.Logger.Fatal("No http.sslcert provided.")
|
||||||
|
}
|
||||||
|
if rp.HTTPSslKey == "" {
|
||||||
|
utils.Logger.Fatal("No http.sslkey provided.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
rp.AppName = rp.Config.StringDefault("app.name", "(not set)")
|
||||||
|
rp.AppRoot = rp.Config.StringDefault("app.root", "")
|
||||||
|
rp.CookiePrefix = rp.Config.StringDefault("cookie.prefix", "REVEL")
|
||||||
|
rp.CookieDomain = rp.Config.StringDefault("cookie.domain", "")
|
||||||
|
rp.CookieSecure = rp.Config.BoolDefault("cookie.secure", rp.HTTPSsl)
|
||||||
|
rp.SecretStr = rp.Config.StringDefault("app.secret", "")
|
||||||
|
|
||||||
|
|
||||||
|
callback.FireEvent(REVEL_BEFORE_MODULES_LOADED, nil)
|
||||||
|
rp.loadModules(callback)
|
||||||
|
callback.FireEvent(REVEL_AFTER_MODULES_LOADED, nil)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadMimeConfig load mime-types.conf on init.
|
||||||
|
func (rp *RevelContainer) LoadMimeConfig() {
|
||||||
|
var err error
|
||||||
|
rp.MimeConfig, err = config.LoadContext("mime-types.conf", rp.ConfPaths)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Failed to load mime type config:", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loads modules based on the configuration setup.
|
||||||
|
// This will fire the REVEL_BEFORE_MODULE_LOADED, REVEL_AFTER_MODULE_LOADED
|
||||||
|
// for each module loaded. The callback will receive the RevelContainer, name, moduleImportPath and modulePath
|
||||||
|
// It will automatically add in the code paths for the module to the
|
||||||
|
// container object
|
||||||
|
func (rp *RevelContainer) loadModules(callback RevelCallback) {
|
||||||
|
keys := []string{}
|
||||||
|
for _, key := range rp.Config.Options("module.") {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reorder module order by key name, a poor mans sort but at least it is consistent
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, key := range keys {
|
||||||
|
moduleImportPath := rp.Config.StringDefault(key, "")
|
||||||
|
if moduleImportPath == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
modulePath, err := rp.ResolveImportPath(moduleImportPath)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Error("Failed to load module. Import of path failed", "modulePath", moduleImportPath, "error", err)
|
||||||
|
}
|
||||||
|
// Drop anything between module.???.<name of module>
|
||||||
|
name := key[len("module."):]
|
||||||
|
if index := strings.Index(name, "."); index > -1 {
|
||||||
|
name = name[index+1:]
|
||||||
|
}
|
||||||
|
callback.FireEvent(REVEL_BEFORE_MODULE_LOADED, []interface{}{rp, name, moduleImportPath, modulePath})
|
||||||
|
rp.addModulePaths(name, moduleImportPath, modulePath)
|
||||||
|
callback.FireEvent(REVEL_AFTER_MODULE_LOADED, []interface{}{rp, name, moduleImportPath, modulePath})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a module paths to the container object
|
||||||
|
func (rp *RevelContainer) addModulePaths(name, importPath, modulePath string) {
|
||||||
|
if codePath := filepath.Join(modulePath, "app"); utils.DirExists(codePath) {
|
||||||
|
rp.CodePaths = append(rp.CodePaths, codePath)
|
||||||
|
rp.ModulePathMap[name] = modulePath
|
||||||
|
if viewsPath := filepath.Join(modulePath, "app", "views"); utils.DirExists(viewsPath) {
|
||||||
|
rp.TemplatePaths = append(rp.TemplatePaths, viewsPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hack: There is presently no way for the testrunner module to add the
|
||||||
|
// "test" subdirectory to the CodePaths. So this does it instead.
|
||||||
|
if importPath == rp.Config.StringDefault("module.testrunner", "github.com/revel/modules/testrunner") {
|
||||||
|
joinedPath := filepath.Join(rp.BasePath, "tests")
|
||||||
|
rp.CodePaths = append(rp.CodePaths, joinedPath)
|
||||||
|
}
|
||||||
|
if testsPath := filepath.Join(modulePath, "tests"); utils.DirExists(testsPath) {
|
||||||
|
rp.CodePaths = append(rp.CodePaths, testsPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveImportPath returns the filesystem path for the given import path.
|
||||||
|
// Returns an error if the import path could not be found.
|
||||||
|
func (rp *RevelContainer) ResolveImportPath(importPath string) (string, error) {
|
||||||
|
if rp.Packaged {
|
||||||
|
return filepath.Join(rp.SourcePath, importPath), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
modPkg, err := build.Import(importPath, rp.RevelPath, build.FindOnly)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return modPkg.Dir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory
|
||||||
|
func findSrcPaths(importPath string) (revelSourcePath, appSourcePath string) {
|
||||||
|
var (
|
||||||
|
gopaths = filepath.SplitList(build.Default.GOPATH)
|
||||||
|
goroot = build.Default.GOROOT
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(gopaths) == 0 {
|
||||||
|
utils.Logger.Fatalf("GOPATH environment variable is not set. " +
|
||||||
|
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.ContainsString(gopaths, goroot) {
|
||||||
|
utils.Logger.Fatalf("GOPATH (%s) must not include your GOROOT (%s). "+
|
||||||
|
"Please refer to http://golang.org/doc/code.html to configure your Go environment.",
|
||||||
|
gopaths, goroot)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
appPkg, err := build.Import(importPath, "", build.FindOnly)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Failed to import "+importPath+" with error:", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
revelPkg, err := build.Import(RevelImportPath, appPkg.Dir, build.FindOnly)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Failed to find Revel with error:", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
revelSourcePath, appSourcePath = revelPkg.Dir[:len(revelPkg.Dir)-len(RevelImportPath)], appPkg.SrcRoot
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
121
model/source_info.go
Normal file
121
model/source_info.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
// SourceInfo is the top-level struct containing all extracted information
|
||||||
|
// about the app source code, used to generate main.go.
|
||||||
|
import (
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"path/filepath"
|
||||||
|
"unicode"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SourceInfo struct {
|
||||||
|
// StructSpecs lists type info for all structs found under the code paths.
|
||||||
|
// They may be queried to determine which ones (transitively) embed certain types.
|
||||||
|
StructSpecs []*TypeInfo
|
||||||
|
// ValidationKeys provides a two-level lookup. The keys are:
|
||||||
|
// 1. The fully-qualified function name,
|
||||||
|
// e.g. "github.com/revel/examples/chat/app/controllers.(*Application).Action"
|
||||||
|
// 2. Within that func's file, the line number of the (overall) expression statement.
|
||||||
|
// e.g. the line returned from runtime.Caller()
|
||||||
|
// The result of the lookup the name of variable being validated.
|
||||||
|
ValidationKeys map[string]map[int]string
|
||||||
|
// A list of import paths.
|
||||||
|
// Revel notices files with an init() function and imports that package.
|
||||||
|
InitImportPaths []string
|
||||||
|
|
||||||
|
// controllerSpecs lists type info for all structs found under
|
||||||
|
// app/controllers/... that embed (directly or indirectly) revel.Controller
|
||||||
|
controllerSpecs []*TypeInfo
|
||||||
|
// testSuites list the types that constitute the set of application tests.
|
||||||
|
testSuites []*TypeInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypesThatEmbed returns all types that (directly or indirectly) embed the
|
||||||
|
// target type, which must be a fully qualified type name,
|
||||||
|
// e.g. "github.com/revel/revel.Controller"
|
||||||
|
func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered []*TypeInfo) {
|
||||||
|
// Do a search in the "embedded type graph", starting with the target type.
|
||||||
|
var (
|
||||||
|
nodeQueue = []string{targetType}
|
||||||
|
processed []string
|
||||||
|
)
|
||||||
|
for len(nodeQueue) > 0 {
|
||||||
|
controllerSimpleName := nodeQueue[0]
|
||||||
|
nodeQueue = nodeQueue[1:]
|
||||||
|
processed = append(processed, controllerSimpleName)
|
||||||
|
|
||||||
|
// Look through all known structs.
|
||||||
|
for _, spec := range s.StructSpecs {
|
||||||
|
// If this one has been processed or is already in nodeQueue, then skip it.
|
||||||
|
if utils.ContainsString(processed, spec.String()) ||
|
||||||
|
utils.ContainsString(nodeQueue, spec.String()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look through the embedded types to see if the current type is among them.
|
||||||
|
for _, embeddedType := range spec.EmbeddedTypes {
|
||||||
|
|
||||||
|
// If so, add this type's simple name to the nodeQueue, and its spec to
|
||||||
|
// the filtered list.
|
||||||
|
if controllerSimpleName == embeddedType.String() {
|
||||||
|
nodeQueue = append(nodeQueue, spec.String())
|
||||||
|
filtered = append(filtered, spec)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Strip out any specifications that contain a lower case
|
||||||
|
for exit := false; !exit; exit = true {
|
||||||
|
for i, filteredItem := range filtered {
|
||||||
|
if unicode.IsLower([]rune(filteredItem.StructName)[0]) {
|
||||||
|
utils.Logger.Info("Debug: Skipping adding spec for unexported type",
|
||||||
|
"type", filteredItem.StructName,
|
||||||
|
"package", filteredItem.ImportPath)
|
||||||
|
filtered = append(filtered[:i], filtered[i+1:]...)
|
||||||
|
exit = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for any missed types that where from expected packages
|
||||||
|
for _, spec := range s.StructSpecs {
|
||||||
|
if spec.PackageName == packageFilter {
|
||||||
|
found := false
|
||||||
|
for _, filteredItem := range filtered {
|
||||||
|
if filteredItem.StructName == spec.StructName {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report non controller structures in controller folder.
|
||||||
|
if !found && !strings.HasPrefix(spec.StructName, "Test") {
|
||||||
|
utils.Logger.Warn("Type found in package: " + packageFilter +
|
||||||
|
", but did not embed from: " + filepath.Base(targetType),
|
||||||
|
"name", spec.StructName, "path", spec.ImportPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ControllerSpecs returns the all the contollers that embeds
|
||||||
|
// `revel.Controller`
|
||||||
|
func (s *SourceInfo) ControllerSpecs() []*TypeInfo {
|
||||||
|
if s.controllerSpecs == nil {
|
||||||
|
s.controllerSpecs = s.TypesThatEmbed(RevelImportPath+".Controller", "controllers")
|
||||||
|
}
|
||||||
|
return s.controllerSpecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSuites returns the all the Application tests that embeds
|
||||||
|
// `testing.TestSuite`
|
||||||
|
func (s *SourceInfo) TestSuites() []*TypeInfo {
|
||||||
|
if s.testSuites == nil {
|
||||||
|
s.testSuites = s.TypesThatEmbed(RevelImportPath+"/testing.TestSuite", "testsuite")
|
||||||
|
}
|
||||||
|
return s.testSuites
|
||||||
|
}
|
||||||
100
model/type_expr.go
Normal file
100
model/type_expr.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// TypeExpr provides a type name that may be rewritten to use a package name.
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TypeExpr struct {
|
||||||
|
Expr string // The unqualified type expression, e.g. "[]*MyType"
|
||||||
|
PkgName string // The default package idenifier
|
||||||
|
pkgIndex int // The index where the package identifier should be inserted.
|
||||||
|
Valid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeName returns the fully-qualified type name for this expression.
|
||||||
|
// The caller may optionally specify a package name to override the default.
|
||||||
|
func (e TypeExpr) TypeName(pkgOverride string) string {
|
||||||
|
pkgName := FirstNonEmpty(pkgOverride, e.PkgName)
|
||||||
|
if pkgName == "" {
|
||||||
|
return e.Expr
|
||||||
|
}
|
||||||
|
return e.Expr[:e.pkgIndex] + pkgName + "." + e.Expr[e.pkgIndex:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTypeExpr returns the syntactic expression for referencing this type in Go.
|
||||||
|
func NewTypeExpr(pkgName string, expr ast.Expr) TypeExpr {
|
||||||
|
error := ""
|
||||||
|
switch t := expr.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
if IsBuiltinType(t.Name) {
|
||||||
|
pkgName = ""
|
||||||
|
}
|
||||||
|
return TypeExpr{t.Name, pkgName, 0, true}
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
e := NewTypeExpr(pkgName, t.X)
|
||||||
|
return TypeExpr{t.Sel.Name, e.Expr, 0, e.Valid}
|
||||||
|
case *ast.StarExpr:
|
||||||
|
e := NewTypeExpr(pkgName, t.X)
|
||||||
|
return TypeExpr{"*" + e.Expr, e.PkgName, e.pkgIndex + 1, e.Valid}
|
||||||
|
case *ast.ArrayType:
|
||||||
|
e := NewTypeExpr(pkgName, t.Elt)
|
||||||
|
return TypeExpr{"[]" + e.Expr, e.PkgName, e.pkgIndex + 2, e.Valid}
|
||||||
|
case *ast.MapType:
|
||||||
|
if identKey, ok := t.Key.(*ast.Ident); ok && IsBuiltinType(identKey.Name) {
|
||||||
|
e := NewTypeExpr(pkgName, t.Value)
|
||||||
|
return TypeExpr{"map[" + identKey.Name + "]" + e.Expr, e.PkgName, e.pkgIndex + len("map["+identKey.Name+"]"), e.Valid}
|
||||||
|
}
|
||||||
|
error = fmt.Sprintf("Failed to generate name for Map field :%v. Make sure the field name is valid.", t.Key)
|
||||||
|
case *ast.Ellipsis:
|
||||||
|
e := NewTypeExpr(pkgName, t.Elt)
|
||||||
|
return TypeExpr{"[]" + e.Expr, e.PkgName, e.pkgIndex + 2, e.Valid}
|
||||||
|
default:
|
||||||
|
error = fmt.Sprintf("Failed to generate name for field: %v Package: %v. Make sure the field name is valid.", expr, pkgName)
|
||||||
|
|
||||||
|
}
|
||||||
|
return TypeExpr{Valid: false, Expr:error}
|
||||||
|
}
|
||||||
|
|
||||||
|
var builtInTypes = map[string]struct{}{
|
||||||
|
"bool": {},
|
||||||
|
"byte": {},
|
||||||
|
"complex128": {},
|
||||||
|
"complex64": {},
|
||||||
|
"error": {},
|
||||||
|
"float32": {},
|
||||||
|
"float64": {},
|
||||||
|
"int": {},
|
||||||
|
"int16": {},
|
||||||
|
"int32": {},
|
||||||
|
"int64": {},
|
||||||
|
"int8": {},
|
||||||
|
"rune": {},
|
||||||
|
"string": {},
|
||||||
|
"uint": {},
|
||||||
|
"uint16": {},
|
||||||
|
"uint32": {},
|
||||||
|
"uint64": {},
|
||||||
|
"uint8": {},
|
||||||
|
"uintptr": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBuiltinType checks the given type is built-in types of Go
|
||||||
|
func IsBuiltinType(name string) bool {
|
||||||
|
_, ok := builtInTypes[name]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the first non empty string from a list of arguements
|
||||||
|
func FirstNonEmpty(strs ...string) string {
|
||||||
|
for _, str := range strs {
|
||||||
|
if len(str) > 0 {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
16
model/type_info.go
Normal file
16
model/type_info.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
// TypeInfo summarizes information about a struct type in the app source code.
|
||||||
|
type TypeInfo struct {
|
||||||
|
StructName string // e.g. "Application"
|
||||||
|
ImportPath string // e.g. "github.com/revel/examples/chat/app/controllers"
|
||||||
|
PackageName string // e.g. "controllers"
|
||||||
|
MethodSpecs []*MethodSpec // Method specifications, the action functions
|
||||||
|
EmbeddedTypes []*EmbeddedTypeName // Used internally to identify controllers that indirectly embed *revel.Controller.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the type information as a properly formatted import string
|
||||||
|
func (s *TypeInfo) String() string {
|
||||||
|
return s.ImportPath + "." + s.StructName
|
||||||
|
}
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// Revel Framework source code and usage is governed by a MIT style
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package harness
|
package parser
|
||||||
|
|
||||||
// This file handles the app code introspection.
|
// This file handles the app code introspection.
|
||||||
// It catalogs the controllers, their methods, and their arguments.
|
// It catalogs the controllers, their methods, and their arguments.
|
||||||
@@ -17,97 +17,34 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"unicode"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
"github.com/revel/revel"
|
|
||||||
"log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SourceInfo is the top-level struct containing all extracted information
|
|
||||||
// about the app source code, used to generate main.go.
|
|
||||||
type SourceInfo struct {
|
|
||||||
// StructSpecs lists type info for all structs found under the code paths.
|
|
||||||
// They may be queried to determine which ones (transitively) embed certain types.
|
|
||||||
StructSpecs []*TypeInfo
|
|
||||||
// ValidationKeys provides a two-level lookup. The keys are:
|
|
||||||
// 1. The fully-qualified function name,
|
|
||||||
// e.g. "github.com/revel/examples/chat/app/controllers.(*Application).Action"
|
|
||||||
// 2. Within that func's file, the line number of the (overall) expression statement.
|
|
||||||
// e.g. the line returned from runtime.Caller()
|
|
||||||
// The result of the lookup the name of variable being validated.
|
|
||||||
ValidationKeys map[string]map[int]string
|
|
||||||
// A list of import paths.
|
|
||||||
// Revel notices files with an init() function and imports that package.
|
|
||||||
InitImportPaths []string
|
|
||||||
|
|
||||||
// controllerSpecs lists type info for all structs found under
|
|
||||||
// app/controllers/... that embed (directly or indirectly) revel.Controller
|
|
||||||
controllerSpecs []*TypeInfo
|
|
||||||
// testSuites list the types that constitute the set of application tests.
|
|
||||||
testSuites []*TypeInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeInfo summarizes information about a struct type in the app source code.
|
|
||||||
type TypeInfo struct {
|
|
||||||
StructName string // e.g. "Application"
|
|
||||||
ImportPath string // e.g. "github.com/revel/examples/chat/app/controllers"
|
|
||||||
PackageName string // e.g. "controllers"
|
|
||||||
MethodSpecs []*MethodSpec
|
|
||||||
|
|
||||||
// Used internally to identify controllers that indirectly embed *revel.Controller.
|
|
||||||
embeddedTypes []*embeddedTypeName
|
|
||||||
}
|
|
||||||
|
|
||||||
// methodCall describes a call to c.Render(..)
|
|
||||||
// It documents the argument names used, in order to propagate them to RenderArgs.
|
|
||||||
type methodCall struct {
|
|
||||||
Path string // e.g. "myapp/app/controllers.(*Application).Action"
|
|
||||||
Line int
|
|
||||||
Names []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// MethodSpec holds the information of one Method
|
|
||||||
type MethodSpec struct {
|
|
||||||
Name string // Name of the method, e.g. "Index"
|
|
||||||
Args []*MethodArg // Argument descriptors
|
|
||||||
RenderCalls []*methodCall // Descriptions of Render() invocations from this Method.
|
|
||||||
}
|
|
||||||
|
|
||||||
// MethodArg holds the information of one argument
|
|
||||||
type MethodArg struct {
|
|
||||||
Name string // Name of the argument.
|
|
||||||
TypeExpr TypeExpr // The name of the type, e.g. "int", "*pkg.UserType"
|
|
||||||
ImportPath string // If the arg is of an imported type, this is the import path.
|
|
||||||
}
|
|
||||||
|
|
||||||
type embeddedTypeName struct {
|
|
||||||
ImportPath, StructName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maps a controller simple name (e.g. "Login") to the methods for which it is a
|
// Maps a controller simple name (e.g. "Login") to the methods for which it is a
|
||||||
// receiver.
|
// receiver.
|
||||||
type methodMap map[string][]*MethodSpec
|
type methodMap map[string][]*model.MethodSpec
|
||||||
|
|
||||||
// ProcessSource parses the app controllers directory and
|
// ProcessSource parses the app controllers directory and
|
||||||
// returns a list of the controller types found.
|
// returns a list of the controller types found.
|
||||||
// Otherwise CompileError if the parsing fails.
|
// Otherwise CompileError if the parsing fails.
|
||||||
func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
|
func ProcessSource(paths *model.RevelContainer) (*model.SourceInfo, *utils.Error) {
|
||||||
var (
|
var (
|
||||||
srcInfo *SourceInfo
|
srcInfo *model.SourceInfo
|
||||||
compileError *revel.Error
|
compileError *utils.Error
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, root := range roots {
|
for _, root := range paths.CodePaths {
|
||||||
rootImportPath := importPathFromPath(root)
|
rootImportPath := importPathFromPath(root)
|
||||||
if rootImportPath == "" {
|
if rootImportPath == "" {
|
||||||
revel.RevelLog.Warn("Skipping empty code path", "path", root)
|
utils.Logger.Info("Skipping empty code path", "path", root)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start walking the directory tree.
|
// Start walking the directory tree.
|
||||||
_ = revel.Walk(root, func(path string, info os.FileInfo, err error) error {
|
_ = utils.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Error("Error scanning app source:", "error", err)
|
utils.Logger.Error("Error scanning app source:", "error", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,17 +67,17 @@ func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if errList, ok := err.(scanner.ErrorList); ok {
|
if errList, ok := err.(scanner.ErrorList); ok {
|
||||||
var pos = errList[0].Pos
|
var pos = errList[0].Pos
|
||||||
compileError = &revel.Error{
|
compileError = &utils.Error{
|
||||||
SourceType: ".go source",
|
SourceType: ".go source",
|
||||||
Title: "Go Compilation Error",
|
Title: "Go Compilation Error",
|
||||||
Path: pos.Filename,
|
Path: pos.Filename,
|
||||||
Description: errList[0].Msg,
|
Description: errList[0].Msg,
|
||||||
Line: pos.Line,
|
Line: pos.Line,
|
||||||
Column: pos.Column,
|
Column: pos.Column,
|
||||||
SourceLines: revel.MustReadLines(pos.Filename),
|
SourceLines: utils.MustReadLines(pos.Filename),
|
||||||
}
|
}
|
||||||
|
|
||||||
errorLink := revel.Config.StringDefault("error.link", "")
|
errorLink := paths.Config.StringDefault("error.link", "")
|
||||||
|
|
||||||
if errorLink != "" {
|
if errorLink != "" {
|
||||||
compileError.SetLink(errorLink)
|
compileError.SetLink(errorLink)
|
||||||
@@ -149,9 +86,9 @@ func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
|
|||||||
return compileError
|
return compileError
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is exception, err alredy checked above. Here just a print
|
// This is exception, err already checked above. Here just a print
|
||||||
ast.Print(nil, err)
|
ast.Print(nil, err)
|
||||||
revel.RevelLog.Fatal("Failed to parse dir", "error", err)
|
utils.Logger.Fatal("Failed to parse dir", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip "main" packages.
|
// Skip "main" packages.
|
||||||
@@ -176,7 +113,7 @@ func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
|
|||||||
for i := range pkgs {
|
for i := range pkgs {
|
||||||
println("Found package ", i)
|
println("Found package ", i)
|
||||||
}
|
}
|
||||||
revel.RevelLog.Error("Most unexpected! Multiple packages in a single directory:", "packages", pkgs)
|
utils.Logger.Fatal("Most unexpected! Multiple packages in a single directory:", "packages", pkgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
var pkg *ast.Package
|
var pkg *ast.Package
|
||||||
@@ -192,7 +129,7 @@ func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
|
|||||||
return srcInfo, compileError
|
return srcInfo, compileError
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendSourceInfo(srcInfo1, srcInfo2 *SourceInfo) *SourceInfo {
|
func appendSourceInfo(srcInfo1, srcInfo2 *model.SourceInfo) *model.SourceInfo {
|
||||||
if srcInfo1 == nil {
|
if srcInfo1 == nil {
|
||||||
return srcInfo2
|
return srcInfo2
|
||||||
}
|
}
|
||||||
@@ -201,7 +138,7 @@ func appendSourceInfo(srcInfo1, srcInfo2 *SourceInfo) *SourceInfo {
|
|||||||
srcInfo1.InitImportPaths = append(srcInfo1.InitImportPaths, srcInfo2.InitImportPaths...)
|
srcInfo1.InitImportPaths = append(srcInfo1.InitImportPaths, srcInfo2.InitImportPaths...)
|
||||||
for k, v := range srcInfo2.ValidationKeys {
|
for k, v := range srcInfo2.ValidationKeys {
|
||||||
if _, ok := srcInfo1.ValidationKeys[k]; ok {
|
if _, ok := srcInfo1.ValidationKeys[k]; ok {
|
||||||
revel.RevelLog.Warn("Key conflict when scanning validation calls:", "key", k)
|
utils.Logger.Warn("Warn: Key conflict when scanning validation calls:", "key", k)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
srcInfo1.ValidationKeys[k] = v
|
srcInfo1.ValidationKeys[k] = v
|
||||||
@@ -209,9 +146,9 @@ func appendSourceInfo(srcInfo1, srcInfo2 *SourceInfo) *SourceInfo {
|
|||||||
return srcInfo1
|
return srcInfo1
|
||||||
}
|
}
|
||||||
|
|
||||||
func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast.Package) *SourceInfo {
|
func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast.Package) *model.SourceInfo {
|
||||||
var (
|
var (
|
||||||
structSpecs []*TypeInfo
|
structSpecs []*model.TypeInfo
|
||||||
initImportPaths []string
|
initImportPaths []string
|
||||||
|
|
||||||
methodSpecs = make(methodMap)
|
methodSpecs = make(methodMap)
|
||||||
@@ -223,8 +160,8 @@ func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast
|
|||||||
)
|
)
|
||||||
|
|
||||||
// For each source file in the package...
|
// For each source file in the package...
|
||||||
log.Println("Exaiming files in path", pkgPath)
|
utils.Logger.Info("Exaiming files in path", "package", pkgPath)
|
||||||
for _, file := range pkg.Files {
|
for fname, file := range pkg.Files {
|
||||||
// Imports maps the package key to the full import path.
|
// Imports maps the package key to the full import path.
|
||||||
// e.g. import "sample/app/models" => "models": "sample/app/models"
|
// e.g. import "sample/app/models" => "models": "sample/app/models"
|
||||||
imports := map[string]string{}
|
imports := map[string]string{}
|
||||||
@@ -235,16 +172,16 @@ func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast
|
|||||||
|
|
||||||
if scanControllers {
|
if scanControllers {
|
||||||
// Match and add both structs and methods
|
// Match and add both structs and methods
|
||||||
structSpecs = appendStruct(structSpecs, pkgImportPath, pkg, decl, imports, fset)
|
structSpecs = appendStruct(fname, structSpecs, pkgImportPath, pkg, decl, imports, fset)
|
||||||
appendAction(fset, methodSpecs, decl, pkgImportPath, pkg.Name, imports)
|
appendAction(fset, methodSpecs, decl, pkgImportPath, pkg.Name, imports)
|
||||||
} else if scanTests {
|
} else if scanTests {
|
||||||
structSpecs = appendStruct(structSpecs, pkgImportPath, pkg, decl, imports, fset)
|
structSpecs = appendStruct(fname, structSpecs, pkgImportPath, pkg, decl, imports, fset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is a func... (ignore nil for external (non-Go) function)
|
// If this is a func... (ignore nil for external (non-Go) function)
|
||||||
if funcDecl, ok := decl.(*ast.FuncDecl); ok && funcDecl.Body != nil {
|
if funcDecl, ok := decl.(*ast.FuncDecl); ok && funcDecl.Body != nil {
|
||||||
// Scan it for validation calls
|
// Scan it for validation calls
|
||||||
lineKeys := getValidationKeys(fset, funcDecl, imports)
|
lineKeys := getValidationKeys(fname, fset, funcDecl, imports)
|
||||||
if len(lineKeys) > 0 {
|
if len(lineKeys) > 0 {
|
||||||
validationKeys[pkgImportPath+"."+getFuncName(funcDecl)] = lineKeys
|
validationKeys[pkgImportPath+"."+getFuncName(funcDecl)] = lineKeys
|
||||||
}
|
}
|
||||||
@@ -262,7 +199,7 @@ func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast
|
|||||||
spec.MethodSpecs = methodSpecs[spec.StructName]
|
spec.MethodSpecs = methodSpecs[spec.StructName]
|
||||||
}
|
}
|
||||||
|
|
||||||
return &SourceInfo{
|
return &model.SourceInfo{
|
||||||
StructSpecs: structSpecs,
|
StructSpecs: structSpecs,
|
||||||
ValidationKeys: validationKeys,
|
ValidationKeys: validationKeys,
|
||||||
InitImportPaths: initImportPaths,
|
InitImportPaths: initImportPaths,
|
||||||
@@ -319,7 +256,7 @@ func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
|
|||||||
// We expect this to happen for apps using reverse routing (since we
|
// We expect this to happen for apps using reverse routing (since we
|
||||||
// have not yet generated the routes). Don't log that.
|
// have not yet generated the routes). Don't log that.
|
||||||
if !strings.HasSuffix(fullPath, "/app/routes") {
|
if !strings.HasSuffix(fullPath, "/app/routes") {
|
||||||
revel.RevelLog.Debug("Could not find import:", "path", fullPath)
|
utils.Logger.Info("Debug: Could not find import:", "path", fullPath)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -332,7 +269,7 @@ func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
|
|||||||
|
|
||||||
// If this Decl is a struct type definition, it is summarized and added to specs.
|
// If this Decl is a struct type definition, it is summarized and added to specs.
|
||||||
// Else, specs is returned unchanged.
|
// Else, specs is returned unchanged.
|
||||||
func appendStruct(specs []*TypeInfo, pkgImportPath string, pkg *ast.Package, decl ast.Decl, imports map[string]string, fset *token.FileSet) []*TypeInfo {
|
func appendStruct(fileName string, specs []*model.TypeInfo, pkgImportPath string, pkg *ast.Package, decl ast.Decl, imports map[string]string, fset *token.FileSet) []*model.TypeInfo {
|
||||||
// Filter out non-Struct type declarations.
|
// Filter out non-Struct type declarations.
|
||||||
spec, found := getStructTypeDecl(decl, fset)
|
spec, found := getStructTypeDecl(decl, fset)
|
||||||
if !found {
|
if !found {
|
||||||
@@ -344,7 +281,7 @@ func appendStruct(specs []*TypeInfo, pkgImportPath string, pkg *ast.Package, dec
|
|||||||
// At this point we know it's a type declaration for a struct.
|
// At this point we know it's a type declaration for a struct.
|
||||||
// Fill in the rest of the info by diving into the fields.
|
// Fill in the rest of the info by diving into the fields.
|
||||||
// Add it provisionally to the Controller list -- it's later filtered using field info.
|
// Add it provisionally to the Controller list -- it's later filtered using field info.
|
||||||
controllerSpec := &TypeInfo{
|
controllerSpec := &model.TypeInfo{
|
||||||
StructName: spec.Name.Name,
|
StructName: spec.Name.Name,
|
||||||
ImportPath: pkgImportPath,
|
ImportPath: pkgImportPath,
|
||||||
PackageName: pkg.Name,
|
PackageName: pkg.Name,
|
||||||
@@ -398,12 +335,12 @@ func appendStruct(specs []*TypeInfo, pkgImportPath string, pkg *ast.Package, dec
|
|||||||
} else {
|
} else {
|
||||||
var ok bool
|
var ok bool
|
||||||
if importPath, ok = imports[pkgName]; !ok {
|
if importPath, ok = imports[pkgName]; !ok {
|
||||||
revel.RevelLog.Error("Failed to find import path for ", "package", pkgName, "type", typeName)
|
utils.Logger.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
controllerSpec.embeddedTypes = append(controllerSpec.embeddedTypes, &embeddedTypeName{
|
controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{
|
||||||
ImportPath: importPath,
|
ImportPath: importPath,
|
||||||
StructName: typeName,
|
StructName: typeName,
|
||||||
})
|
})
|
||||||
@@ -443,11 +380,11 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
|
|||||||
if selExpr.Sel.Name != "Result" {
|
if selExpr.Sel.Name != "Result" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != revel.RevelImportPath {
|
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != model.RevelImportPath {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
method := &MethodSpec{
|
method := &model.MethodSpec{
|
||||||
Name: funcDecl.Name.Name,
|
Name: funcDecl.Name.Name,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,9 +392,9 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
|
|||||||
for _, field := range funcDecl.Type.Params.List {
|
for _, field := range funcDecl.Type.Params.List {
|
||||||
for _, name := range field.Names {
|
for _, name := range field.Names {
|
||||||
var importPath string
|
var importPath string
|
||||||
typeExpr := NewTypeExpr(pkgName, field.Type)
|
typeExpr := model.NewTypeExpr(pkgName, field.Type)
|
||||||
if !typeExpr.Valid {
|
if !typeExpr.Valid {
|
||||||
revel.RevelLog.Warnf("Didn't understand argument '%s' of action %s. Ignoring.", name, getFuncName(funcDecl))
|
utils.Logger.Warn("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, getFuncName(funcDecl))
|
||||||
return // We didn't understand one of the args. Ignore this action.
|
return // We didn't understand one of the args. Ignore this action.
|
||||||
}
|
}
|
||||||
// Local object
|
// Local object
|
||||||
@@ -466,10 +403,10 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
|
|||||||
} else if typeExpr.PkgName != "" {
|
} else if typeExpr.PkgName != "" {
|
||||||
var ok bool
|
var ok bool
|
||||||
if importPath, ok = imports[typeExpr.PkgName]; !ok {
|
if importPath, ok = imports[typeExpr.PkgName]; !ok {
|
||||||
revel.RevelLog.Errorf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
|
utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
method.Args = append(method.Args, &MethodArg{
|
method.Args = append(method.Args, &model.MethodArg{
|
||||||
Name: name.Name,
|
Name: name.Name,
|
||||||
TypeExpr: typeExpr,
|
TypeExpr: typeExpr,
|
||||||
ImportPath: importPath,
|
ImportPath: importPath,
|
||||||
@@ -479,7 +416,7 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
|
|||||||
|
|
||||||
// Add a description of the calls to Render from the method.
|
// Add a description of the calls to Render from the method.
|
||||||
// Inspect every node (e.g. always return true).
|
// Inspect every node (e.g. always return true).
|
||||||
method.RenderCalls = []*methodCall{}
|
method.RenderCalls = []*model.MethodCall{}
|
||||||
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||||
// Is it a function call?
|
// Is it a function call?
|
||||||
callExpr, ok := node.(*ast.CallExpr)
|
callExpr, ok := node.(*ast.CallExpr)
|
||||||
@@ -501,7 +438,7 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
|
|||||||
|
|
||||||
// Add this call's args to the renderArgs.
|
// Add this call's args to the renderArgs.
|
||||||
pos := fset.Position(callExpr.Lparen)
|
pos := fset.Position(callExpr.Lparen)
|
||||||
methodCall := &methodCall{
|
methodCall := &model.MethodCall{
|
||||||
Line: pos.Line,
|
Line: pos.Line,
|
||||||
Names: []string{},
|
Names: []string{},
|
||||||
}
|
}
|
||||||
@@ -541,7 +478,7 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
|
|||||||
//
|
//
|
||||||
// The end result is that we can set the default validation key for each call to
|
// The end result is that we can set the default validation key for each call to
|
||||||
// be the same as the local variable.
|
// be the same as the local variable.
|
||||||
func getValidationKeys(fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[string]string) map[int]string {
|
func getValidationKeys(fname string, fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[string]string) map[int]string {
|
||||||
var (
|
var (
|
||||||
lineKeys = make(map[int]string)
|
lineKeys = make(map[int]string)
|
||||||
|
|
||||||
@@ -597,8 +534,11 @@ func getValidationKeys(fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if typeExpr := NewTypeExpr("", key); typeExpr.Valid {
|
if typeExpr := model.NewTypeExpr("", key); typeExpr.Valid {
|
||||||
lineKeys[fset.Position(callExpr.Pos()).Line] = typeExpr.TypeName("")
|
lineKeys[fset.Position(callExpr.End()).Line] = typeExpr.TypeName("")
|
||||||
|
} else {
|
||||||
|
utils.Logger.Error("Error: Failed to generate key for field validation. Make sure the field name is valid.", "file", fname,
|
||||||
|
"line", fset.Position(callExpr.End()).Line, "function", funcDecl.Name.String())
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@@ -624,21 +564,13 @@ func getValidationParameter(funcDecl *ast.FuncDecl, imports map[string]string) *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == revel.RevelImportPath {
|
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == model.RevelImportPath {
|
||||||
return field.Names[0].Obj
|
return field.Names[0].Obj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TypeInfo) String() string {
|
|
||||||
return s.ImportPath + "." + s.StructName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *embeddedTypeName) String() string {
|
|
||||||
return s.ImportPath + "." + s.StructName
|
|
||||||
}
|
|
||||||
|
|
||||||
// getStructTypeDecl checks if the given decl is a type declaration for a
|
// getStructTypeDecl checks if the given decl is a type declaration for a
|
||||||
// struct. If so, the TypeSpec is returned.
|
// struct. If so, the TypeSpec is returned.
|
||||||
func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) {
|
func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) {
|
||||||
@@ -652,7 +584,7 @@ func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(genDecl.Specs) == 0 {
|
if len(genDecl.Specs) == 0 {
|
||||||
revel.RevelLog.Warnf("Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
|
utils.Logger.Warn("Warn: Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,173 +594,6 @@ func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TypesThatEmbed returns all types that (directly or indirectly) embed the
|
|
||||||
// target type, which must be a fully qualified type name,
|
|
||||||
// e.g. "github.com/revel/revel.Controller"
|
|
||||||
func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered []*TypeInfo) {
|
|
||||||
// Do a search in the "embedded type graph", starting with the target type.
|
|
||||||
var (
|
|
||||||
nodeQueue = []string{targetType}
|
|
||||||
processed []string
|
|
||||||
)
|
|
||||||
for len(nodeQueue) > 0 {
|
|
||||||
controllerSimpleName := nodeQueue[0]
|
|
||||||
nodeQueue = nodeQueue[1:]
|
|
||||||
processed = append(processed, controllerSimpleName)
|
|
||||||
|
|
||||||
// Look through all known structs.
|
|
||||||
for _, spec := range s.StructSpecs {
|
|
||||||
// If this one has been processed or is already in nodeQueue, then skip it.
|
|
||||||
if revel.ContainsString(processed, spec.String()) ||
|
|
||||||
revel.ContainsString(nodeQueue, spec.String()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look through the embedded types to see if the current type is among them.
|
|
||||||
for _, embeddedType := range spec.embeddedTypes {
|
|
||||||
|
|
||||||
// If so, add this type's simple name to the nodeQueue, and its spec to
|
|
||||||
// the filtered list.
|
|
||||||
if controllerSimpleName == embeddedType.String() {
|
|
||||||
nodeQueue = append(nodeQueue, spec.String())
|
|
||||||
filtered = append(filtered, spec)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Strip out any specifications that contain a lower case
|
|
||||||
for exit := false; !exit; exit = true {
|
|
||||||
for i, filteredItem := range filtered {
|
|
||||||
if unicode.IsLower([]rune(filteredItem.StructName)[0]) {
|
|
||||||
revel.RevelLog.Debug("Skipping adding spec for unexported type",
|
|
||||||
"type", filteredItem.StructName,
|
|
||||||
"package", filteredItem.ImportPath)
|
|
||||||
filtered = append(filtered[:i], filtered[i+1:]...)
|
|
||||||
exit = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for any missed types that where from expected packages
|
|
||||||
for _, spec := range s.StructSpecs {
|
|
||||||
if spec.PackageName == packageFilter {
|
|
||||||
found := false
|
|
||||||
for _, filteredItem := range filtered {
|
|
||||||
if filteredItem.StructName == spec.StructName {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
revel.RevelLog.Warn("Type found in package: "+packageFilter+
|
|
||||||
", but did not embed from: "+filepath.Base(targetType),
|
|
||||||
"name", spec.StructName, "path", spec.ImportPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ControllerSpecs returns the all the contollers that embeds
|
|
||||||
// `revel.Controller`
|
|
||||||
func (s *SourceInfo) ControllerSpecs() []*TypeInfo {
|
|
||||||
if s.controllerSpecs == nil {
|
|
||||||
s.controllerSpecs = s.TypesThatEmbed(revel.RevelImportPath+".Controller", "controllers")
|
|
||||||
}
|
|
||||||
return s.controllerSpecs
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestSuites returns the all the Application tests that embeds
|
|
||||||
// `testing.TestSuite`
|
|
||||||
func (s *SourceInfo) TestSuites() []*TypeInfo {
|
|
||||||
if s.testSuites == nil {
|
|
||||||
s.testSuites = s.TypesThatEmbed(revel.RevelImportPath+"/testing.TestSuite", "testsuite")
|
|
||||||
}
|
|
||||||
return s.testSuites
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeExpr provides a type name that may be rewritten to use a package name.
|
|
||||||
type TypeExpr struct {
|
|
||||||
Expr string // The unqualified type expression, e.g. "[]*MyType"
|
|
||||||
PkgName string // The default package idenifier
|
|
||||||
pkgIndex int // The index where the package identifier should be inserted.
|
|
||||||
Valid bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeName returns the fully-qualified type name for this expression.
|
|
||||||
// The caller may optionally specify a package name to override the default.
|
|
||||||
func (e TypeExpr) TypeName(pkgOverride string) string {
|
|
||||||
pkgName := revel.FirstNonEmpty(pkgOverride, e.PkgName)
|
|
||||||
if pkgName == "" {
|
|
||||||
return e.Expr
|
|
||||||
}
|
|
||||||
return e.Expr[:e.pkgIndex] + pkgName + "." + e.Expr[e.pkgIndex:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTypeExpr returns the syntactic expression for referencing this type in Go.
|
|
||||||
func NewTypeExpr(pkgName string, expr ast.Expr) TypeExpr {
|
|
||||||
switch t := expr.(type) {
|
|
||||||
case *ast.Ident:
|
|
||||||
if IsBuiltinType(t.Name) {
|
|
||||||
pkgName = ""
|
|
||||||
}
|
|
||||||
return TypeExpr{t.Name, pkgName, 0, true}
|
|
||||||
case *ast.SelectorExpr:
|
|
||||||
e := NewTypeExpr(pkgName, t.X)
|
|
||||||
return TypeExpr{t.Sel.Name, e.Expr, 0, e.Valid}
|
|
||||||
case *ast.StarExpr:
|
|
||||||
e := NewTypeExpr(pkgName, t.X)
|
|
||||||
return TypeExpr{"*" + e.Expr, e.PkgName, e.pkgIndex + 1, e.Valid}
|
|
||||||
case *ast.ArrayType:
|
|
||||||
e := NewTypeExpr(pkgName, t.Elt)
|
|
||||||
return TypeExpr{"[]" + e.Expr, e.PkgName, e.pkgIndex + 2, e.Valid}
|
|
||||||
case *ast.MapType:
|
|
||||||
if identKey, ok := t.Key.(*ast.Ident); ok && IsBuiltinType(identKey.Name) {
|
|
||||||
e := NewTypeExpr(pkgName, t.Value)
|
|
||||||
return TypeExpr{"map[" + identKey.Name + "]" + e.Expr, e.PkgName, e.pkgIndex + len("map["+identKey.Name+"]"), e.Valid}
|
|
||||||
}
|
|
||||||
|
|
||||||
revel.RevelLog.Error("Failed to generate name for field. Make sure the field name is valid.")
|
|
||||||
case *ast.Ellipsis:
|
|
||||||
e := NewTypeExpr(pkgName, t.Elt)
|
|
||||||
return TypeExpr{"[]" + e.Expr, e.PkgName, e.pkgIndex + 2, e.Valid}
|
|
||||||
default:
|
|
||||||
revel.RevelLog.Error("Failed to generate name for field. Make sure the field name is valid.", "package", pkgName, "expresion",expr)
|
|
||||||
}
|
|
||||||
return TypeExpr{Valid: false}
|
|
||||||
}
|
|
||||||
|
|
||||||
var builtInTypes = map[string]struct{}{
|
|
||||||
"bool": {},
|
|
||||||
"byte": {},
|
|
||||||
"complex128": {},
|
|
||||||
"complex64": {},
|
|
||||||
"error": {},
|
|
||||||
"float32": {},
|
|
||||||
"float64": {},
|
|
||||||
"int": {},
|
|
||||||
"int16": {},
|
|
||||||
"int32": {},
|
|
||||||
"int64": {},
|
|
||||||
"int8": {},
|
|
||||||
"rune": {},
|
|
||||||
"string": {},
|
|
||||||
"uint": {},
|
|
||||||
"uint16": {},
|
|
||||||
"uint32": {},
|
|
||||||
"uint64": {},
|
|
||||||
"uint8": {},
|
|
||||||
"uintptr": {},
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsBuiltinType checks the given type is built-in types of Go
|
|
||||||
func IsBuiltinType(name string) bool {
|
|
||||||
_, ok := builtInTypes[name]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func importPathFromPath(root string) string {
|
func importPathFromPath(root string) string {
|
||||||
if vendorIdx := strings.Index(root, "/vendor/"); vendorIdx != -1 {
|
if vendorIdx := strings.Index(root, "/vendor/"); vendorIdx != -1 {
|
||||||
return filepath.ToSlash(root[vendorIdx+8:])
|
return filepath.ToSlash(root[vendorIdx+8:])
|
||||||
@@ -842,10 +607,10 @@ func importPathFromPath(root string) string {
|
|||||||
|
|
||||||
srcPath := filepath.Join(build.Default.GOROOT, "src", "pkg")
|
srcPath := filepath.Join(build.Default.GOROOT, "src", "pkg")
|
||||||
if strings.HasPrefix(root, srcPath) {
|
if strings.HasPrefix(root, srcPath) {
|
||||||
revel.RevelLog.Warn("Code path should be in GOPATH, but is in GOROOT:", "path", root)
|
utils.Logger.Warn("Code path should be in GOPATH, but is in GOROOT:", "path", root)
|
||||||
return filepath.ToSlash(root[len(srcPath)+1:])
|
return filepath.ToSlash(root[len(srcPath)+1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
revel.RevelLog.Error("Unexpected! Code path is not in GOPATH:", "path", root)
|
utils.Logger.Error("Unexpected! Code path is not in GOPATH:", "path", root)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// Revel Framework source code and usage is governed by a MIT style
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package harness
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/revel"
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
const validationKeysSource = `
|
const validationKeysSource = `
|
||||||
@@ -80,7 +81,7 @@ func TestGetValidationKeys(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, decl := range file.Decls {
|
for i, decl := range file.Decls {
|
||||||
lineKeys := getValidationKeys(fset, decl.(*ast.FuncDecl), map[string]string{"revel": revel.RevelImportPath})
|
lineKeys := getValidationKeys("test", fset, decl.(*ast.FuncDecl), map[string]string{"revel": revel.RevelImportPath})
|
||||||
for k, v := range expectedValidationKeys[i] {
|
for k, v := range expectedValidationKeys[i] {
|
||||||
if lineKeys[k] != v {
|
if lineKeys[k] != v {
|
||||||
t.Errorf("Not found - %d: %v - Actual Map: %v", k, v, lineKeys)
|
t.Errorf("Not found - %d: %v - Actual Map: %v", k, v, lineKeys)
|
||||||
@@ -93,7 +94,7 @@ func TestGetValidationKeys(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var TypeExprs = map[string]TypeExpr{
|
var TypeExprs = map[string]model.TypeExpr{
|
||||||
"int": {"int", "", 0, true},
|
"int": {"int", "", 0, true},
|
||||||
"*int": {"*int", "", 1, true},
|
"*int": {"*int", "", 1, true},
|
||||||
"[]int": {"[]int", "", 2, true},
|
"[]int": {"[]int", "", 2, true},
|
||||||
@@ -136,7 +137,7 @@ func TestTypeExpr(t *testing.T) {
|
|||||||
expr = &ast.Ellipsis{Ellipsis: expr.Pos(), Elt: expr}
|
expr = &ast.Ellipsis{Ellipsis: expr.Pos(), Elt: expr}
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := NewTypeExpr("pkg", expr)
|
actual := model.NewTypeExpr("pkg", expr)
|
||||||
if !reflect.DeepEqual(expected, actual) {
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
t.Error("Fail, expected", expected, ", was", actual)
|
t.Error("Fail, expected", expected, ", was", actual)
|
||||||
}
|
}
|
||||||
@@ -151,7 +152,7 @@ func TestProcessBookingSource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
controllerPackage := "github.com/revel/examples/booking/app/controllers"
|
controllerPackage := "github.com/revel/examples/booking/app/controllers"
|
||||||
expectedControllerSpecs := []*TypeInfo{
|
expectedControllerSpecs := []*model.TypeInfo{
|
||||||
{"GorpController", controllerPackage, "controllers", nil, nil},
|
{"GorpController", controllerPackage, "controllers", nil, nil},
|
||||||
{"Application", controllerPackage, "controllers", nil, nil},
|
{"Application", controllerPackage, "controllers", nil, nil},
|
||||||
{"Hotels", controllerPackage, "controllers", nil, nil},
|
{"Hotels", controllerPackage, "controllers", nil, nil},
|
||||||
13
proxy/proxy.go
Normal file
13
proxy/proxy.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||||
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package proxy for a Revel Framework.
|
||||||
|
//
|
||||||
|
// It has a following responsibilities:
|
||||||
|
// 1. Build and run the users program in a proxy
|
||||||
|
// 2. Monitor the user source and restart the program when necessary.
|
||||||
|
//
|
||||||
|
// Source files are generated in the app/tmp directory.
|
||||||
|
|
||||||
|
package proxy
|
||||||
130
revel/build.go
130
revel/build.go
@@ -5,70 +5,86 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/revel/cmd/harness"
|
"github.com/revel/cmd/harness"
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"go/build"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdBuild = &Command{
|
var cmdBuild = &Command{
|
||||||
UsageLine: "build [import path] [target path] [run mode]",
|
UsageLine: "build -i [import path] -t [target path] -r [run mode]",
|
||||||
Short: "build a Revel application (e.g. for deployment)",
|
Short: "build a Revel application (e.g. for deployment)",
|
||||||
Long: `
|
Long: `
|
||||||
Build the Revel web application named by the given import path.
|
Build the Revel web application named by the given import path.
|
||||||
This allows it to be deployed and run on a machine that lacks a Go installation.
|
This allows it to be deployed and run on a machine that lacks a Go installation.
|
||||||
|
|
||||||
The run mode is used to select which set of app.conf configuration should
|
|
||||||
apply and may be used to determine logic in the application itself.
|
|
||||||
|
|
||||||
Run mode defaults to "dev".
|
|
||||||
|
|
||||||
WARNING: The target path will be completely deleted, if it already exists!
|
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
revel build github.com/revel/examples/chat /tmp/chat
|
revel build -a github.com/revel/examples/chat -t /tmp/chat
|
||||||
|
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmdBuild.Run = buildApp
|
cmdBuild.RunWith = buildApp
|
||||||
|
cmdBuild.UpdateConfig = updateBuildConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildApp(args []string) {
|
// The update config updates the configuration command so that it can run
|
||||||
|
func updateBuildConfig(c *model.CommandConfig, args []string) (bool) {
|
||||||
|
c.Index = BUILD
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long)
|
fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long)
|
||||||
return
|
return false
|
||||||
|
}
|
||||||
|
c.Build.ImportPath = args[0]
|
||||||
|
c.Build.TargetPath = args[1]
|
||||||
|
if len(args)>2 {
|
||||||
|
c.Build.Mode = args[1]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main entry point to build application from command line
|
||||||
|
func buildApp(c *model.CommandConfig) {
|
||||||
|
c.ImportPath = c.Build.ImportPath
|
||||||
|
appImportPath, destPath, mode := c.Build.ImportPath , c.Build.TargetPath, DefaultRunMode
|
||||||
|
if len(c.Build.Mode) > 0 {
|
||||||
|
mode = c.Build.Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
appImportPath, destPath, mode := args[0], args[1], DefaultRunMode
|
// Convert target to absolute path
|
||||||
if len(args) >= 3 {
|
destPath, _ = filepath.Abs(destPath)
|
||||||
mode = args[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
if !revel.Initialized {
|
revel_paths := model.NewRevelPaths(mode, appImportPath, "", model.DoNothingRevelCallback)
|
||||||
revel.Init(mode, appImportPath, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, verify that it is either already empty or looks like a previous
|
// First, verify that it is either already empty or looks like a previous
|
||||||
// build (to avoid clobbering anything)
|
// build (to avoid clobbering anything)
|
||||||
if exists(destPath) && !empty(destPath) && !exists(filepath.Join(destPath, "run.sh")) {
|
if utils.Exists(destPath) && !utils.Empty(destPath) && !utils.Exists(filepath.Join(destPath, "run.sh")) {
|
||||||
errorf("Abort: %s exists and does not look like a build directory.", destPath)
|
utils.Logger.Errorf("Abort: %s exists and does not look like a build directory.", destPath)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
|
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
|
||||||
revel.RevelLog.Fatal("Remove all error","error", err)
|
utils.Logger.Error("Remove all error","error", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(destPath, 0777); err != nil {
|
if err := os.MkdirAll(destPath, 0777); err != nil {
|
||||||
revel.RevelLog.Fatal("makedir error","error",err)
|
utils.Logger.Error("makedir error","error",err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
app, reverr := harness.Build()
|
app, reverr := harness.Build(c,revel_paths)
|
||||||
panicOnError(reverr, "Failed to build")
|
if reverr!=nil {
|
||||||
|
utils.Logger.Error("Failed to build application","error",reverr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Included are:
|
// Included are:
|
||||||
// - run scripts
|
// - run scripts
|
||||||
@@ -79,15 +95,15 @@ func buildApp(args []string) {
|
|||||||
// Revel and the app are in a directory structure mirroring import path
|
// Revel and the app are in a directory structure mirroring import path
|
||||||
srcPath := filepath.Join(destPath, "src")
|
srcPath := filepath.Join(destPath, "src")
|
||||||
destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath))
|
destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath))
|
||||||
tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(revel.RevelImportPath))
|
tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(model.RevelImportPath))
|
||||||
mustCopyFile(destBinaryPath, app.BinaryPath)
|
utils.MustCopyFile(destBinaryPath, app.BinaryPath)
|
||||||
mustChmod(destBinaryPath, 0755)
|
utils.MustChmod(destBinaryPath, 0755)
|
||||||
_ = mustCopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revel.RevelPath, "conf"), nil)
|
_ = utils.MustCopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revel_paths.RevelPath, "conf"), nil)
|
||||||
_ = mustCopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revel.RevelPath, "templates"), nil)
|
_ = utils.MustCopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revel_paths.RevelPath, "templates"), nil)
|
||||||
_ = mustCopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revel.BasePath, nil)
|
_ = utils.MustCopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revel_paths.BasePath, nil)
|
||||||
|
|
||||||
// Find all the modules used and copy them over.
|
// Find all the modules used and copy them over.
|
||||||
config := revel.Config.Raw()
|
config := revel_paths.Config.Raw()
|
||||||
modulePaths := make(map[string]string) // import path => filesystem path
|
modulePaths := make(map[string]string) // import path => filesystem path
|
||||||
for _, section := range config.Sections() {
|
for _, section := range config.Sections() {
|
||||||
options, _ := config.SectionOptions(section)
|
options, _ := config.SectionOptions(section)
|
||||||
@@ -99,32 +115,46 @@ func buildApp(args []string) {
|
|||||||
if moduleImportPath == "" {
|
if moduleImportPath == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
modulePath, err := revel.ResolveImportPath(moduleImportPath)
|
|
||||||
|
modPkg, err := build.Import(c.ImportPath, revel_paths.RevelPath, build.FindOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Fatalf("Failed to load module %s: %s", key[len("module."):], err)
|
utils.Logger.Fatalf("Failed to load module %s (%s): %s", key[len("module."):], c.ImportPath, err)
|
||||||
}
|
}
|
||||||
modulePaths[moduleImportPath] = modulePath
|
modulePaths[moduleImportPath] = modPkg.Dir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for importPath, fsPath := range modulePaths {
|
for importPath, fsPath := range modulePaths {
|
||||||
_ = mustCopyDir(filepath.Join(srcPath, importPath), fsPath, nil)
|
_ = utils.MustCopyDir(filepath.Join(srcPath, importPath), fsPath, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmplData, runShPath := map[string]interface{}{
|
tmplData := map[string]interface{}{
|
||||||
"BinName": filepath.Base(app.BinaryPath),
|
"BinName": filepath.Base(app.BinaryPath),
|
||||||
"ImportPath": appImportPath,
|
"ImportPath": appImportPath,
|
||||||
"Mode": mode,
|
"Mode": mode,
|
||||||
}, filepath.Join(destPath, "run.sh")
|
}
|
||||||
|
|
||||||
mustRenderTemplate(
|
utils.MustGenerateTemplate(
|
||||||
runShPath,
|
filepath.Join(destPath, "run.sh"),
|
||||||
filepath.Join(revel.RevelPath, "..", "cmd", "revel", "package_run.sh.template"),
|
PACKAGE_RUN_SH,
|
||||||
tmplData)
|
tmplData,
|
||||||
|
)
|
||||||
mustChmod(runShPath, 0755)
|
utils.MustChmod(filepath.Join(destPath, "run.sh"), 0755)
|
||||||
|
utils.MustGenerateTemplate(
|
||||||
mustRenderTemplate(
|
|
||||||
filepath.Join(destPath, "run.bat"),
|
filepath.Join(destPath, "run.bat"),
|
||||||
filepath.Join(revel.RevelPath, "..", "cmd", "revel", "package_run.bat.template"),
|
PACKAGE_RUN_BAT,
|
||||||
tmplData)
|
tmplData,
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Println("Your application has been built in:", destPath)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PACKAGE_RUN_SH = `#!/bin/sh
|
||||||
|
|
||||||
|
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
||||||
|
"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}
|
||||||
|
`
|
||||||
|
const PACKAGE_RUN_BAT = `@echo off
|
||||||
|
|
||||||
|
{{.BinName}} -importPath {{.ImportPath}} -srcPath "%CD%\src" -runMode {{.Mode}}
|
||||||
|
`
|
||||||
|
|||||||
@@ -6,39 +6,52 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
"go/build"
|
"go/build"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdClean = &Command{
|
var cmdClean = &Command{
|
||||||
UsageLine: "clean [import path]",
|
UsageLine: "clean -i [import path]",
|
||||||
Short: "clean a Revel application's temp files",
|
Short: "clean a Revel application's temp files",
|
||||||
Long: `
|
Long: `
|
||||||
Clean the Revel web application named by the given import path.
|
Clean the Revel web application named by the given import path.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
revel clean github.com/revel/examples/chat
|
revel clean -a github.com/revel/examples/chat
|
||||||
|
|
||||||
It removes the app/tmp and app/routes directory.
|
It removes the app/tmp and app/routes directory.
|
||||||
|
|
||||||
|
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmdClean.Run = cleanApp
|
cmdClean.UpdateConfig = updateCleanConfig
|
||||||
|
cmdClean.RunWith = cleanApp
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanApp(args []string) {
|
// Update the clean command configuration, using old method
|
||||||
|
func updateCleanConfig(c *model.CommandConfig, args []string) bool {
|
||||||
|
c.Index = CLEAN
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
fmt.Fprintf(os.Stderr, cmdClean.Long)
|
fmt.Fprintf(os.Stderr, cmdClean.Long)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
c.Clean.ImportPath = args[0]
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
appPkg, err := build.Import(args[0], "", build.FindOnly)
|
// Clean the source directory of generated files
|
||||||
|
func cleanApp(c *model.CommandConfig) {
|
||||||
|
c.ImportPath = c.Clean.ImportPath
|
||||||
|
|
||||||
|
appPkg, err := build.Import(c.ImportPath, "", build.FindOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Abort: Failed to find import path:", err)
|
utils.Logger.Fatal("Abort: Failed to find import path:", "error", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
purgeDirs := []string{
|
purgeDirs := []string{
|
||||||
@@ -50,7 +63,7 @@ func cleanApp(args []string) {
|
|||||||
fmt.Println("Removing:", dir)
|
fmt.Println("Removing:", dir)
|
||||||
err = os.RemoveAll(dir)
|
err = os.RemoveAll(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Abort:", err)
|
utils.Logger.Error("Failed to clean dir", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
289
revel/new.go
289
revel/new.go
@@ -14,11 +14,12 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdNew = &Command{
|
var cmdNew = &Command{
|
||||||
UsageLine: "new [path] [skeleton]",
|
UsageLine: "new -i [path] -s [skeleton]",
|
||||||
Short: "create a skeleton Revel application",
|
Short: "create a skeleton Revel application",
|
||||||
Long: `
|
Long: `
|
||||||
New creates a few files to get a new Revel application running quickly.
|
New creates a few files to get a new Revel application running quickly.
|
||||||
@@ -30,61 +31,113 @@ Skeleton is an optional argument, provided as an import path
|
|||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
revel new import/path/helloworld
|
revel new -a import/path/helloworld
|
||||||
|
|
||||||
|
revel new -a import/path/helloworld -s import/path/skeleton
|
||||||
|
|
||||||
revel new import/path/helloworld import/path/skeleton
|
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmdNew.Run = newApp
|
cmdNew.RunWith = newApp
|
||||||
|
cmdNew.UpdateConfig = updateNewConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// Called when unable to parse the command line automatically and assumes an old launch
|
||||||
|
func updateNewConfig(c *model.CommandConfig, args []string) bool {
|
||||||
// go related paths
|
c.Index = NEW
|
||||||
gopath string
|
|
||||||
gocmd string
|
|
||||||
srcRoot string
|
|
||||||
|
|
||||||
// revel related paths
|
|
||||||
revelPkg *build.Package
|
|
||||||
revelCmdPkg *build.Package
|
|
||||||
appPath string
|
|
||||||
appName string
|
|
||||||
basePath string
|
|
||||||
importPath string
|
|
||||||
skeletonPath string
|
|
||||||
)
|
|
||||||
|
|
||||||
func newApp(args []string) {
|
|
||||||
// check for proper args by count
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
errorf("No import path given.\nRun 'revel help new' for usage.\n")
|
fmt.Fprintf(os.Stderr, cmdNew.Long)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
if len(args) > 2 {
|
c.New.ImportPath = args[0]
|
||||||
errorf("Too many arguments provided.\nRun 'revel help new' for usage.\n")
|
if len(args)>1 {
|
||||||
|
c.New.Skeleton = args[1]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call to create a new application
|
||||||
|
func newApp(c *model.CommandConfig) {
|
||||||
|
// check for proper args by count
|
||||||
|
c.ImportPath = c.New.ImportPath
|
||||||
|
c.SkeletonPath = c.New.Skeleton
|
||||||
|
|
||||||
|
// Check for an existing folder so we dont clober it
|
||||||
|
c.AppPath = filepath.Join(c.SrcRoot, filepath.FromSlash(c.ImportPath))
|
||||||
|
_, err := build.Import(c.ImportPath, "", build.FindOnly)
|
||||||
|
if err==nil || !utils.Empty(c.AppPath) {
|
||||||
|
utils.Logger.Fatal("Abort: Import path already exists.","path", c.ImportPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.New.Vendored {
|
||||||
|
depPath, err := exec.LookPath("dep")
|
||||||
|
if err != nil {
|
||||||
|
// Do not halt build unless a new package needs to be imported
|
||||||
|
utils.Logger.Fatal("New: `dep` executable not found in PATH, but vendor folder requested." +
|
||||||
|
"You must install the dep tool before creating a vendored project. " +
|
||||||
|
"You can install the `dep` tool by doing a `go get -u github.com/golang/dep/cmd/dep`")
|
||||||
|
}
|
||||||
|
vendorPath := filepath.Join(c.ImportPath,"vendor")
|
||||||
|
if !utils.DirExists(vendorPath) {
|
||||||
|
err := os.MkdirAll(vendorPath,os.ModePerm)
|
||||||
|
utils.PanicOnError(err, "Failed to create " + vendorPath)
|
||||||
|
}
|
||||||
|
// In order for dep to run there needs to be a source file in the folder
|
||||||
|
tempPath := filepath.Join(c.ImportPath,"tmp")
|
||||||
|
if !utils.DirExists(tempPath) {
|
||||||
|
err := os.MkdirAll(tempPath,os.ModePerm)
|
||||||
|
utils.PanicOnError(err, "Failed to create " + vendorPath)
|
||||||
|
err = utils.MustGenerateTemplate(filepath.Join(tempPath,"main.go"), NEW_MAIN_FILE, nil)
|
||||||
|
utils.PanicOnError(err, "Failed to create main file " + vendorPath)
|
||||||
|
|
||||||
|
}
|
||||||
|
packageFile := filepath.Join(c.ImportPath,"Gopkg.toml")
|
||||||
|
if !utils.Exists(packageFile) {
|
||||||
|
utils.MustGenerateTemplate(packageFile,VENDOR_GOPKG,nil)
|
||||||
|
} else {
|
||||||
|
utils.Logger.Info("Package file exists in skeleto, skipping adding")
|
||||||
|
}
|
||||||
|
|
||||||
|
getCmd := exec.Command(depPath, "ensure", "-v")
|
||||||
|
getCmd.Dir = c.ImportPath
|
||||||
|
utils.Logger.Info("Exec:", "args", getCmd.Args)
|
||||||
|
getCmd.Dir = c.ImportPath
|
||||||
|
getOutput, err := getCmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal(string(getOutput))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO build.Default.GOPATH = build.Default.GOPATH + string(os.PathListSeparator) + c.ImportPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// checking and setting go paths
|
|
||||||
initGoPaths()
|
|
||||||
|
|
||||||
// checking and setting application
|
// checking and setting application
|
||||||
setApplicationPath(args)
|
setApplicationPath(c)
|
||||||
|
|
||||||
// checking and setting skeleton
|
// checking and setting skeleton
|
||||||
setSkeletonPath(args)
|
setSkeletonPath(c)
|
||||||
|
|
||||||
// copy files to new app directory
|
// copy files to new app directory
|
||||||
copyNewAppFiles()
|
copyNewAppFiles(c)
|
||||||
|
|
||||||
|
|
||||||
// goodbye world
|
// goodbye world
|
||||||
fmt.Fprintln(os.Stdout, "Your application is ready:\n ", appPath)
|
fmt.Fprintln(os.Stdout, "Your application is ready:\n ", c.AppPath)
|
||||||
fmt.Fprintln(os.Stdout, "\nYou can run it with:\n revel run", importPath)
|
// Check to see if it should be run right off
|
||||||
|
if c.New.Run {
|
||||||
|
c.Run.ImportPath = c.ImportPath
|
||||||
|
runApp(c)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(os.Stdout, "\nYou can run it with:\n revel run -a ", c.ImportPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used to generate a new secret key
|
||||||
const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
|
||||||
|
// Generate a secret key
|
||||||
func generateSecret() string {
|
func generateSecret() string {
|
||||||
chars := make([]byte, 64)
|
chars := make([]byte, 64)
|
||||||
for i := 0; i < 64; i++ {
|
for i := 0; i < 64; i++ {
|
||||||
@@ -93,129 +146,145 @@ func generateSecret() string {
|
|||||||
return string(chars)
|
return string(chars)
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookup and set Go related variables
|
// Sets the applicaiton path
|
||||||
func initGoPaths() {
|
func setApplicationPath(c *model.CommandConfig) {
|
||||||
// lookup go path
|
|
||||||
gopath = build.Default.GOPATH
|
|
||||||
if gopath == "" {
|
|
||||||
errorf("Abort: GOPATH environment variable is not set. " +
|
|
||||||
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for go executable
|
|
||||||
var err error
|
|
||||||
gocmd, err = exec.LookPath("go")
|
|
||||||
if err != nil {
|
|
||||||
errorf("Go executable not found in PATH.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// revel/revel#1004 choose go path relative to current working directory
|
|
||||||
workingDir, _ := os.Getwd()
|
|
||||||
goPathList := filepath.SplitList(gopath)
|
|
||||||
for _, path := range goPathList {
|
|
||||||
if strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
|
|
||||||
srcRoot = path
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
path, _ = filepath.EvalSymlinks(path)
|
|
||||||
if len(path) > 0 && strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
|
|
||||||
srcRoot = path
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(srcRoot) == 0 {
|
|
||||||
revel.RevelLog.Fatal("Abort: could not create a Revel application outside of GOPATH.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// set go src path
|
|
||||||
srcRoot = filepath.Join(srcRoot, "src")
|
|
||||||
}
|
|
||||||
|
|
||||||
func setApplicationPath(args []string) {
|
|
||||||
var err error
|
|
||||||
importPath = args[0]
|
|
||||||
|
|
||||||
// revel/revel#1014 validate relative path, we cannot use built-in functions
|
// revel/revel#1014 validate relative path, we cannot use built-in functions
|
||||||
// since Go import path is valid relative path too.
|
// since Go import path is valid relative path too.
|
||||||
// so check basic part of the path, which is "."
|
// so check basic part of the path, which is "."
|
||||||
if filepath.IsAbs(importPath) || strings.HasPrefix(importPath, ".") {
|
if filepath.IsAbs(c.ImportPath) || strings.HasPrefix(c.ImportPath, ".") {
|
||||||
errorf("Abort: '%s' looks like a directory. Please provide a Go import path instead.",
|
utils.Logger.Fatalf("Abort: '%s' looks like a directory. Please provide a Go import path instead.",
|
||||||
importPath)
|
c.ImportPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
appPath = filepath.Join(srcRoot, filepath.FromSlash(importPath))
|
|
||||||
|
|
||||||
_, err = build.Import(importPath, "", build.FindOnly)
|
// If we are running a vendored version of Revel we do not need to check for it.
|
||||||
if err == nil && !empty(appPath) {
|
if !c.New.Vendored {
|
||||||
errorf("Abort: Import path %s already exists.\n", importPath)
|
var err error
|
||||||
}
|
_, err = build.Import(model.RevelImportPath, "", build.FindOnly)
|
||||||
|
|
||||||
revelPkg, err = build.Import(revel.RevelImportPath, "", build.FindOnly)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("Abort: Could not find Revel source code: %s\n", err)
|
// Go get the revel project
|
||||||
|
|
||||||
|
utils.Logger.Fatal("Abort: Could not find Revel source code:", "error", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appName = filepath.Base(appPath)
|
c.AppName = filepath.Base(c.AppPath)
|
||||||
basePath = filepath.ToSlash(filepath.Dir(importPath))
|
c.BasePath = filepath.ToSlash(filepath.Dir(c.ImportPath))
|
||||||
|
|
||||||
if basePath == "." {
|
if c.BasePath == "." {
|
||||||
// we need to remove the a single '.' when
|
// we need to remove the a single '.' when
|
||||||
// the app is in the $GOROOT/src directory
|
// the app is in the $GOROOT/src directory
|
||||||
basePath = ""
|
c.BasePath = ""
|
||||||
} else {
|
} else {
|
||||||
// we need to append a '/' when the app is
|
// we need to append a '/' when the app is
|
||||||
// is a subdirectory such as $GOROOT/src/path/to/revelapp
|
// is a subdirectory such as $GOROOT/src/path/to/revelapp
|
||||||
basePath += "/"
|
c.BasePath += "/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSkeletonPath(args []string) {
|
// Set the skeleton path
|
||||||
|
func setSkeletonPath(c *model.CommandConfig) {
|
||||||
var err error
|
var err error
|
||||||
if len(args) == 2 { // user specified
|
if len(c.SkeletonPath) > 0 { // user specified
|
||||||
skeletonName := args[1]
|
|
||||||
_, err = build.Import(skeletonName, "", build.FindOnly)
|
_, err = build.Import(c.SkeletonPath, "", build.FindOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Execute "go get <pkg>"
|
// Execute "go get <pkg>"
|
||||||
getCmd := exec.Command(gocmd, "get", "-d", skeletonName)
|
getCmd := exec.Command(c.GoCmd, "get", "-d", c.SkeletonPath)
|
||||||
fmt.Println("Exec:", getCmd.Args)
|
fmt.Println("Exec:", getCmd.Args)
|
||||||
getOutput, err := getCmd.CombinedOutput()
|
getOutput, err := getCmd.CombinedOutput()
|
||||||
|
|
||||||
// check getOutput for no buildible string
|
// check getOutput for no buildible string
|
||||||
bpos := bytes.Index(getOutput, []byte("no buildable Go source files in"))
|
bpos := bytes.Index(getOutput, []byte("no buildable Go source files in"))
|
||||||
if err != nil && bpos == -1 {
|
if err != nil && bpos == -1 {
|
||||||
errorf("Abort: Could not find or 'go get' Skeleton source code: %s\n%s\n", getOutput, skeletonName)
|
utils.Logger.Fatalf("Abort: Could not find or 'go get' Skeleton source code: %s\n%s\n", getOutput, c.SkeletonPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// use the
|
// use the
|
||||||
skeletonPath = filepath.Join(srcRoot, skeletonName)
|
c.SkeletonPath = filepath.Join(c.SrcRoot, c.SkeletonPath)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// use the revel default
|
// use the revel default
|
||||||
revelCmdPkg, err = build.Import(RevelCmdImportPath, "", build.FindOnly)
|
revelCmdPkg, err := build.Import(RevelCmdImportPath, "", build.FindOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("Abort: Could not find Revel Cmd source code: %s\n", err)
|
utils.Logger.Fatalf("Abort: Could not find Revel Cmd source code: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
skeletonPath = filepath.Join(revelCmdPkg.Dir, "revel", "skeleton")
|
c.SkeletonPath = filepath.Join(revelCmdPkg.Dir, "revel", "skeleton")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyNewAppFiles() {
|
func copyNewAppFiles(c *model.CommandConfig) {
|
||||||
var err error
|
var err error
|
||||||
err = os.MkdirAll(appPath, 0777)
|
err = os.MkdirAll(c.AppPath, 0777)
|
||||||
panicOnError(err, "Failed to create directory "+appPath)
|
utils.PanicOnError(err, "Failed to create directory "+c.AppPath)
|
||||||
|
|
||||||
_ = mustCopyDir(appPath, skeletonPath, map[string]interface{}{
|
_ = utils.MustCopyDir(c.AppPath, c.SkeletonPath, map[string]interface{}{
|
||||||
// app.conf
|
// app.conf
|
||||||
"AppName": appName,
|
"AppName": c.AppName,
|
||||||
"BasePath": basePath,
|
"BasePath": c.BasePath,
|
||||||
"Secret": generateSecret(),
|
"Secret": generateSecret(),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore.
|
// Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore.
|
||||||
gitignore := ".gitignore"
|
gitignore := ".gitignore"
|
||||||
mustCopyFile(filepath.Join(appPath, gitignore), filepath.Join(skeletonPath, gitignore))
|
utils.MustCopyFile(filepath.Join(c.AppPath, gitignore), filepath.Join(c.SkeletonPath, gitignore))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
VENDOR_GOPKG = `#
|
||||||
|
# Revel Gopkg.toml
|
||||||
|
#
|
||||||
|
# If you want to use a specific version of Revel change the branches below
|
||||||
|
#
|
||||||
|
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||||
|
# for detailed Gopkg.toml documentation.
|
||||||
|
#
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
# version = "1.0.0"
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project2"
|
||||||
|
# branch = "dev"
|
||||||
|
# source = "github.com/myfork/project2"
|
||||||
|
#
|
||||||
|
# [[override]]
|
||||||
|
# name = "github.com/x/y"
|
||||||
|
# version = "2.4.0"
|
||||||
|
required = ["github.com/revel/cmd/revel"]
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/revel/modules"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/revel/revel"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/revel/cmd"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/revel/log15"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/revel/cron"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/xeonx/timeago"
|
||||||
|
|
||||||
|
`
|
||||||
|
NEW_MAIN_FILE = `package main
|
||||||
|
|
||||||
|
`
|
||||||
|
)
|
||||||
@@ -10,11 +10,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdPackage = &Command{
|
var cmdPackage = &Command{
|
||||||
UsageLine: "package [import path] [run mode]",
|
UsageLine: "package -i [import path] -r [run mode]",
|
||||||
Short: "package a Revel application (e.g. for deployment)",
|
Short: "package a Revel application (e.g. for deployment)",
|
||||||
Long: `
|
Long: `
|
||||||
Package the Revel web application named by the given import path.
|
Package the Revel web application named by the given import path.
|
||||||
@@ -27,43 +28,60 @@ Run mode defaults to "dev".
|
|||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
revel package github.com/revel/examples/chat
|
revel package -i github.com/revel/examples/chat
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmdPackage.Run = packageApp
|
cmdPackage.RunWith = packageApp
|
||||||
|
cmdPackage.UpdateConfig = updatePackageConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func packageApp(args []string) {
|
// Called when unable to parse the command line automatically and assumes an old launch
|
||||||
|
func updatePackageConfig(c *model.CommandConfig, args []string) bool {
|
||||||
|
c.Index = PACAKAGE
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
fmt.Fprint(os.Stderr, cmdPackage.Long)
|
fmt.Fprintf(os.Stderr, cmdPackage.Long)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
c.New.ImportPath = args[0]
|
||||||
|
if len(args)>1 {
|
||||||
|
c.New.Skeleton = args[1]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func packageApp(c *model.CommandConfig) {
|
||||||
|
|
||||||
// Determine the run mode.
|
// Determine the run mode.
|
||||||
mode := DefaultRunMode
|
mode := DefaultRunMode
|
||||||
if len(args) >= 2 {
|
if len(c.Package.Mode) >= 0 {
|
||||||
mode = args[1]
|
mode = c.Package.Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
appImportPath := args[0]
|
appImportPath := c.Package.ImportPath
|
||||||
revel.Init(mode, appImportPath, "")
|
revel_paths := model.NewRevelPaths(mode, appImportPath, "", model.DoNothingRevelCallback)
|
||||||
|
|
||||||
// Remove the archive if it already exists.
|
// Remove the archive if it already exists.
|
||||||
destFile := filepath.Base(revel.BasePath) + ".tar.gz"
|
destFile := filepath.Base(revel_paths.BasePath) + ".tar.gz"
|
||||||
if err := os.Remove(destFile); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(destFile); err != nil && !os.IsNotExist(err) {
|
||||||
revel.RevelLog.Fatal("Unable to remove target file","error",err,"file",destFile)
|
utils.Logger.Error("Unable to remove target file","error",err,"file",destFile)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect stuff in a temp directory.
|
// Collect stuff in a temp directory.
|
||||||
tmpDir, err := ioutil.TempDir("", filepath.Base(revel.BasePath))
|
tmpDir, err := ioutil.TempDir("", filepath.Base(revel_paths.BasePath))
|
||||||
panicOnError(err, "Failed to get temp dir")
|
utils.PanicOnError(err, "Failed to get temp dir")
|
||||||
|
|
||||||
buildApp([]string{args[0], tmpDir, mode})
|
// Build expects the command the build to contain the proper data
|
||||||
|
c.Build.ImportPath = appImportPath
|
||||||
|
c.Build.Mode = mode
|
||||||
|
c.Build.TargetPath = tmpDir
|
||||||
|
buildApp(c)
|
||||||
|
|
||||||
// Create the zip file.
|
// Create the zip file.
|
||||||
archiveName := mustTarGzDir(destFile, tmpDir)
|
archiveName := utils.MustTarGzDir(destFile, tmpDir)
|
||||||
|
|
||||||
fmt.Println("Your archive is ready:", archiveName)
|
fmt.Println("Your archive is ready:", archiveName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
@echo off
|
|
||||||
{{.BinName}} -importPath {{.ImportPath}} -srcPath "%CD%\src" -runMode {{.Mode}}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
|
||||||
"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}
|
|
||||||
144
revel/rev.go
144
revel/rev.go
@@ -1,144 +0,0 @@
|
|||||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
|
||||||
// Revel Framework source code and usage is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// The command line tool for running Revel apps.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/agtorre/gocolorize"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// RevelCmdImportPath Revel framework cmd tool import path
|
|
||||||
RevelCmdImportPath = "github.com/revel/cmd"
|
|
||||||
|
|
||||||
// DefaultRunMode for revel's application
|
|
||||||
DefaultRunMode = "dev"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Command structure cribbed from the genius organization of the "go" command.
|
|
||||||
type Command struct {
|
|
||||||
Run func(args []string)
|
|
||||||
UsageLine, Short, Long string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns command name from usage line
|
|
||||||
func (cmd *Command) Name() string {
|
|
||||||
name := cmd.UsageLine
|
|
||||||
i := strings.Index(name, " ")
|
|
||||||
if i >= 0 {
|
|
||||||
name = name[:i]
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
var commands = []*Command{
|
|
||||||
cmdNew,
|
|
||||||
cmdRun,
|
|
||||||
cmdBuild,
|
|
||||||
cmdPackage,
|
|
||||||
cmdClean,
|
|
||||||
cmdTest,
|
|
||||||
cmdVersion,
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
gocolorize.SetPlain(true)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(os.Stdout, gocolorize.NewColor("blue").Paint(header))
|
|
||||||
flag.Usage = func() { usage(1) }
|
|
||||||
flag.Parse()
|
|
||||||
args := flag.Args()
|
|
||||||
|
|
||||||
if len(args) < 1 || args[0] == "help" {
|
|
||||||
if len(args) == 1 {
|
|
||||||
usage(0)
|
|
||||||
}
|
|
||||||
if len(args) > 1 {
|
|
||||||
for _, cmd := range commands {
|
|
||||||
if cmd.Name() == args[1] {
|
|
||||||
tmpl(os.Stdout, helpTemplate, cmd)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
usage(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands use panic to abort execution when something goes wrong.
|
|
||||||
// Panics are logged at the point of error. Ignore those.
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
if _, ok := err.(LoggedError); !ok {
|
|
||||||
// This panic was not expected / logged.
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for _, cmd := range commands {
|
|
||||||
if cmd.Name() == args[0] {
|
|
||||||
cmd.Run(args[1:])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errorf("unknown command %q\nRun 'revel help' for usage.\n", args[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func errorf(format string, args ...interface{}) {
|
|
||||||
// Ensure the user's command prompt starts on the next line.
|
|
||||||
if !strings.HasSuffix(format, "\n") {
|
|
||||||
format += "\n"
|
|
||||||
}
|
|
||||||
fmt.Fprintf(os.Stderr, format, args...)
|
|
||||||
panic(LoggedError{}) // Panic instead of os.Exit so that deferred will run.
|
|
||||||
}
|
|
||||||
|
|
||||||
const header = `~
|
|
||||||
~ revel! http://revel.github.io
|
|
||||||
~
|
|
||||||
`
|
|
||||||
|
|
||||||
const usageTemplate = `usage: revel command [arguments]
|
|
||||||
|
|
||||||
The commands are:
|
|
||||||
{{range .}}
|
|
||||||
{{.Name | printf "%-11s"}} {{.Short}}{{end}}
|
|
||||||
|
|
||||||
Use "revel help [command]" for more information.
|
|
||||||
`
|
|
||||||
|
|
||||||
var helpTemplate = `usage: revel {{.UsageLine}}
|
|
||||||
{{.Long}}
|
|
||||||
`
|
|
||||||
|
|
||||||
func usage(exitCode int) {
|
|
||||||
tmpl(os.Stderr, usageTemplate, commands)
|
|
||||||
os.Exit(exitCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func tmpl(w io.Writer, text string, data interface{}) {
|
|
||||||
t := template.New("top")
|
|
||||||
template.Must(t.Parse(text))
|
|
||||||
if err := t.Execute(w, data); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
300
revel/revel.go
Normal file
300
revel/revel.go
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||||
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// The command line tool for running Revel apps.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jessevdk/go-flags"
|
||||||
|
|
||||||
|
"github.com/agtorre/gocolorize"
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"github.com/revel/cmd/logger"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"go/build"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RevelCmdImportPath Revel framework cmd tool import path
|
||||||
|
RevelCmdImportPath = "github.com/revel/cmd"
|
||||||
|
|
||||||
|
// DefaultRunMode for revel's application
|
||||||
|
DefaultRunMode = "dev"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command structure cribbed from the genius organization of the "go" command.
|
||||||
|
type Command struct {
|
||||||
|
UpdateConfig func(c *model.CommandConfig, args []string) bool
|
||||||
|
RunWith func(c *model.CommandConfig)
|
||||||
|
UsageLine, Short, Long string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns command name from usage line
|
||||||
|
func (cmd *Command) Name() string {
|
||||||
|
name := cmd.UsageLine
|
||||||
|
i := strings.Index(name, " ")
|
||||||
|
if i >= 0 {
|
||||||
|
name = name[:i]
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// The constants
|
||||||
|
const (
|
||||||
|
NEW model.COMMAND = iota +1
|
||||||
|
RUN
|
||||||
|
BUILD
|
||||||
|
PACAKAGE
|
||||||
|
CLEAN
|
||||||
|
TEST
|
||||||
|
VERSION
|
||||||
|
)
|
||||||
|
// The commands
|
||||||
|
var commands = []*Command{
|
||||||
|
nil, // Safety net, prevent missing index from running
|
||||||
|
cmdNew,
|
||||||
|
cmdRun,
|
||||||
|
cmdBuild,
|
||||||
|
cmdPackage,
|
||||||
|
cmdClean,
|
||||||
|
cmdTest,
|
||||||
|
cmdVersion,
|
||||||
|
}
|
||||||
|
func main() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
gocolorize.SetPlain(true)
|
||||||
|
}
|
||||||
|
c := &model.CommandConfig{}
|
||||||
|
wd,_ := os.Getwd()
|
||||||
|
|
||||||
|
utils.InitLogger(wd,logger.LvlError)
|
||||||
|
|
||||||
|
parser := flags.NewParser(c, flags.HelpFlag | flags.PassDoubleDash)
|
||||||
|
if ini:=flag.String("ini","none","");*ini!="none" {
|
||||||
|
if err:=flags.NewIniParser(parser).ParseFile(*ini);err!=nil {
|
||||||
|
utils.Logger.Error("Unable to load ini", "error",err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err := parser.Parse(); err != nil {
|
||||||
|
utils.Logger.Info("Command line options failed", "error", err.Error())
|
||||||
|
|
||||||
|
// Decode nature of error
|
||||||
|
if perr,ok:=err.(*flags.Error); ok {
|
||||||
|
if perr.Type == flags.ErrRequired {
|
||||||
|
// Try the old way
|
||||||
|
if !main_parse_old(c) {
|
||||||
|
println("Command line error:", err.Error())
|
||||||
|
parser.WriteHelp(os.Stdout)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println("Command line error:", err.Error())
|
||||||
|
parser.WriteHelp(os.Stdout)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println("Command line error:", err.Error())
|
||||||
|
parser.WriteHelp(os.Stdout)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch parser.Active.Name {
|
||||||
|
case "new":
|
||||||
|
c.Index = NEW
|
||||||
|
case "run":
|
||||||
|
c.Index = RUN
|
||||||
|
case "build":
|
||||||
|
c.Index = BUILD
|
||||||
|
case "package":
|
||||||
|
c.Index = PACAKAGE
|
||||||
|
case "clean":
|
||||||
|
c.Index = CLEAN
|
||||||
|
case "test":
|
||||||
|
c.Index = TEST
|
||||||
|
case "version":
|
||||||
|
c.Index = VERSION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch based on the verbose flag
|
||||||
|
if c.Verbose {
|
||||||
|
utils.InitLogger(wd, logger.LvlDebug)
|
||||||
|
} else {
|
||||||
|
utils.InitLogger(wd, logger.LvlWarn)
|
||||||
|
}
|
||||||
|
println("Revel executing:", commands[c.Index].Short)
|
||||||
|
// checking and setting go paths
|
||||||
|
initGoPaths(c)
|
||||||
|
|
||||||
|
commands[c.Index].RunWith(c)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to populate the CommandConfig using the old techniques
|
||||||
|
func main_parse_old(c *model.CommandConfig) bool {
|
||||||
|
// Take the old command format and try to parse them
|
||||||
|
flag.Usage = func() { usage(1) }
|
||||||
|
flag.Parse()
|
||||||
|
args := flag.Args()
|
||||||
|
|
||||||
|
if len(args) < 1 || args[0] == "help" {
|
||||||
|
if len(args) == 1 {
|
||||||
|
usage(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) > 1 {
|
||||||
|
for _, cmd := range commands {
|
||||||
|
if cmd!=nil && cmd.Name() == args[1] {
|
||||||
|
tmpl(os.Stdout, helpTemplate, cmd)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usage(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cmd := range commands {
|
||||||
|
if cmd!=nil && cmd.Name() == args[0] {
|
||||||
|
println("Running", cmd.Name())
|
||||||
|
return cmd.UpdateConfig(c, args[1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func main_old() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
gocolorize.SetPlain(true)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stdout, gocolorize.NewColor("blue").Paint(header))
|
||||||
|
flag.Usage = func() { usage(1) }
|
||||||
|
flag.Parse()
|
||||||
|
args := flag.Args()
|
||||||
|
|
||||||
|
if len(args) < 1 || args[0] == "help" {
|
||||||
|
if len(args) == 1 {
|
||||||
|
usage(0)
|
||||||
|
}
|
||||||
|
if len(args) > 1 {
|
||||||
|
for _, cmd := range commands {
|
||||||
|
if cmd.Name() == args[1] {
|
||||||
|
tmpl(os.Stdout, helpTemplate, cmd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usage(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commands use panic to abort execution when something goes wrong.
|
||||||
|
// Panics are logged at the point of error. Ignore those.
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
if _, ok := err.(utils.LoggedError); !ok {
|
||||||
|
// This panic was not expected / logged.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
//for _, cmd := range commands {
|
||||||
|
// if cmd.Name() == args[0] {
|
||||||
|
// cmd.UpdateConfig(args[1:])
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
utils.Logger.Fatalf("unknown command %q\nRun 'revel help' for usage.\n", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = `~
|
||||||
|
~ revel! http://revel.github.io
|
||||||
|
~
|
||||||
|
`
|
||||||
|
|
||||||
|
const usageTemplate = `usage: revel command [arguments]
|
||||||
|
|
||||||
|
The commands are:
|
||||||
|
{{range .}}
|
||||||
|
{{.Name | printf "%-11s"}} {{.Short}}{{end}}
|
||||||
|
|
||||||
|
Use "revel help [command]" for more information.
|
||||||
|
`
|
||||||
|
|
||||||
|
var helpTemplate = `usage: revel {{.UsageLine}}
|
||||||
|
{{.Long}}
|
||||||
|
`
|
||||||
|
|
||||||
|
func usage(exitCode int) {
|
||||||
|
tmpl(os.Stderr, usageTemplate, commands)
|
||||||
|
os.Exit(exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tmpl(w io.Writer, text string, data interface{}) {
|
||||||
|
t := template.New("top")
|
||||||
|
template.Must(t.Parse(text))
|
||||||
|
if err := t.Execute(w, data); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup and set Go related variables
|
||||||
|
func initGoPaths(c *model.CommandConfig) {
|
||||||
|
// lookup go path
|
||||||
|
c.GoPath = build.Default.GOPATH
|
||||||
|
if c.GoPath == "" {
|
||||||
|
utils.Logger.Fatal("Abort: GOPATH environment variable is not set. " +
|
||||||
|
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for go executable
|
||||||
|
var err error
|
||||||
|
c.GoCmd, err = exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Go executable not found in PATH.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// revel/revel#1004 choose go path relative to current working directory
|
||||||
|
workingDir, _ := os.Getwd()
|
||||||
|
goPathList := filepath.SplitList(c.GoPath)
|
||||||
|
for _, path := range goPathList {
|
||||||
|
if strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
|
||||||
|
c.SrcRoot = path
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
path, _ = filepath.EvalSymlinks(path)
|
||||||
|
if len(path) > 0 && strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
|
||||||
|
c.SrcRoot = path
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.SrcRoot) == 0 {
|
||||||
|
utils.Logger.Fatal("Abort: could not create a Revel application outside of GOPATH.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// set go src path
|
||||||
|
c.SrcRoot = filepath.Join(c.SrcRoot, "src")
|
||||||
|
}
|
||||||
114
revel/run.go
114
revel/run.go
@@ -5,11 +5,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/build"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
"github.com/revel/cmd/harness"
|
"github.com/revel/cmd/harness"
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"go/build"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdRun = &Command{
|
var cmdRun = &Command{
|
||||||
@@ -40,49 +42,41 @@ type RunArgs struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmdRun.Run = runApp
|
cmdRun.RunWith = runApp
|
||||||
|
cmdRun.UpdateConfig = updateRunConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRunArgs(args []string) *RunArgs {
|
func updateRunConfig(c *model.CommandConfig, args []string) bool {
|
||||||
inputArgs := RunArgs{
|
|
||||||
ImportPath: importPathFromCurrentDir(),
|
|
||||||
Mode: DefaultRunMode,
|
|
||||||
Port: revel.HTTPPort,
|
|
||||||
}
|
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 3:
|
case 3:
|
||||||
// Possible combinations
|
// Possible combinations
|
||||||
// revel run [import-path] [run-mode] [port]
|
// revel run [import-path] [run-mode] [port]
|
||||||
port, err := strconv.Atoi(args[2])
|
c.Run.ImportPath = args[0]
|
||||||
if err != nil {
|
c.Run.Mode = args[1]
|
||||||
errorf("Failed to parse port as integer: %s", args[2])
|
c.Run.Port = args[2]
|
||||||
}
|
|
||||||
inputArgs.ImportPath = args[0]
|
|
||||||
inputArgs.Mode = args[1]
|
|
||||||
inputArgs.Port = port
|
|
||||||
case 2:
|
case 2:
|
||||||
// Possible combinations
|
// Possible combinations
|
||||||
// 1. revel run [import-path] [run-mode]
|
// 1. revel run [import-path] [run-mode]
|
||||||
// 2. revel run [import-path] [port]
|
// 2. revel run [import-path] [port]
|
||||||
// 3. revel run [run-mode] [port]
|
// 3. revel run [run-mode] [port]
|
||||||
|
|
||||||
|
// Check to see if the import path evaluates out to something that may be on a gopath
|
||||||
if _, err := build.Import(args[0], "", build.FindOnly); err == nil {
|
if _, err := build.Import(args[0], "", build.FindOnly); err == nil {
|
||||||
// 1st arg is the import path
|
// 1st arg is the import path
|
||||||
inputArgs.ImportPath = args[0]
|
c.Run.ImportPath = args[0]
|
||||||
if port, err := strconv.Atoi(args[1]); err == nil {
|
|
||||||
|
if _, err := strconv.Atoi(args[1]); err == nil {
|
||||||
// 2nd arg is the port number
|
// 2nd arg is the port number
|
||||||
inputArgs.Port = port
|
c.Run.Port = args[1]
|
||||||
} else {
|
} else {
|
||||||
// 2nd arg is the run mode
|
// 2nd arg is the run mode
|
||||||
inputArgs.Mode = args[1]
|
c.Run.Mode = args[1]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 1st arg is the run mode
|
// 1st arg is the run mode
|
||||||
port, err := strconv.Atoi(args[1])
|
c.Run.Mode = args[0]
|
||||||
if err != nil {
|
c.Run.Port = args[1]
|
||||||
errorf("Failed to parse port as integer: %s", args[1])
|
|
||||||
}
|
|
||||||
inputArgs.Mode = args[0]
|
|
||||||
inputArgs.Port = port
|
|
||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
// Possible combinations
|
// Possible combinations
|
||||||
@@ -91,52 +85,62 @@ func parseRunArgs(args []string) *RunArgs {
|
|||||||
// 3. revel run [run-mode]
|
// 3. revel run [run-mode]
|
||||||
_, err := build.Import(args[0], "", build.FindOnly)
|
_, err := build.Import(args[0], "", build.FindOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.RevelLog.Warn("Unable to run using an import path, assuming import path is working directory %s %s", "Argument", args[0], "error", err.Error())
|
utils.Logger.Warn("Unable to run using an import path, assuming import path is working directory %s %s", "Argument", args[0], "error", err.Error())
|
||||||
}
|
}
|
||||||
println("Trying to build with", args[0], err)
|
utils.Logger.Info("Trying to build with", args[0], err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// 1st arg is the import path
|
// 1st arg is the import path
|
||||||
inputArgs.ImportPath = args[0]
|
c.Run.ImportPath = args[0]
|
||||||
} else if port, err := strconv.Atoi(args[0]); err == nil {
|
} else if _, err := strconv.Atoi(args[0]); err == nil {
|
||||||
// 1st arg is the port number
|
// 1st arg is the port number
|
||||||
inputArgs.Port = port
|
c.Run.Port = args[0]
|
||||||
} else {
|
} else {
|
||||||
// 1st arg is the run mode
|
// 1st arg is the run mode
|
||||||
inputArgs.Mode = args[0]
|
c.Run.Mode = args[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
c.Index = RUN
|
||||||
return &inputArgs
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func runApp(args []string) {
|
func runApp(c *model.CommandConfig) {
|
||||||
runArgs := parseRunArgs(args)
|
if c.Run.Mode == "" {
|
||||||
|
c.Run.Mode = "dev"
|
||||||
// Find and parse app.conf
|
|
||||||
revel.Init(runArgs.Mode, runArgs.ImportPath, "")
|
|
||||||
revel.LoadMimeConfig()
|
|
||||||
|
|
||||||
// fallback to default port
|
|
||||||
if runArgs.Port == 0 {
|
|
||||||
runArgs.Port = revel.HTTPPort
|
|
||||||
}
|
}
|
||||||
|
|
||||||
revel.RevelLog.Infof("Running %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, runArgs.Mode)
|
revel_path := model.NewRevelPaths(c.Run.Mode, c.Run.ImportPath, "", model.DoNothingRevelCallback)
|
||||||
revel.RevelLog.Debug("Base path:", "path", revel.BasePath)
|
if c.Run.Port != "" {
|
||||||
|
port, err := strconv.Atoi(c.Run.Port)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatalf("Failed to parse port as integer: %s", c.Run.Port)
|
||||||
|
}
|
||||||
|
revel_path.HTTPPort = port
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Logger.Infof("Running %s (%s) in %s mode\n", revel_path.AppName, revel_path.ImportPath, revel_path.RunMode)
|
||||||
|
utils.Logger.Debug("Base path:", "path", revel_path.BasePath)
|
||||||
|
|
||||||
// If the app is run in "watched" mode, use the harness to run it.
|
// If the app is run in "watched" mode, use the harness to run it.
|
||||||
if revel.Config.BoolDefault("watch", true) && revel.Config.BoolDefault("watch.code", true) {
|
if revel_path.Config.BoolDefault("watch", true) && revel_path.Config.BoolDefault("watch.code", true) {
|
||||||
revel.RevelLog.Debug("Running in watched mode.")
|
utils.Logger.Info("Running in watched mode.")
|
||||||
revel.HTTPPort = runArgs.Port
|
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v}`, revel_path.RunMode, c.Verbose)
|
||||||
harness.NewHarness().Run() // Never returns.
|
if c.HistoricMode {
|
||||||
|
runMode = revel_path.RunMode
|
||||||
|
}
|
||||||
|
// **** Never returns.
|
||||||
|
harness.NewHarness(c, revel_path, runMode, c.Run.NoProxy).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Else, just build and run the app.
|
// Else, just build and run the app.
|
||||||
revel.RevelLog.Debug("Running in live build mode.")
|
utils.Logger.Debug("Running in live build mode.")
|
||||||
app, err := harness.Build()
|
app, err := harness.Build(c, revel_path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("Failed to build app: %s", err)
|
utils.Logger.Errorf("Failed to build app: %s", err)
|
||||||
}
|
}
|
||||||
app.Port = runArgs.Port
|
app.Port = revel_path.HTTPPort
|
||||||
app.Cmd().Run()
|
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v}`, app.Paths.RunMode, c.Verbose)
|
||||||
|
if c.HistoricMode {
|
||||||
|
runMode = revel_path.RunMode
|
||||||
|
}
|
||||||
|
app.Cmd(runMode).Run()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ func init() {
|
|||||||
HeaderFilter, // Add some security based headers
|
HeaderFilter, // Add some security based headers
|
||||||
revel.InterceptorFilter, // Run interceptors around the action.
|
revel.InterceptorFilter, // Run interceptors around the action.
|
||||||
revel.CompressFilter, // Compress the result.
|
revel.CompressFilter, // Compress the result.
|
||||||
|
revel.BeforeAfterFilter, // Call the before and after filter functions
|
||||||
revel.ActionInvoker, // Invoke the action.
|
revel.ActionInvoker, // Invoke the action.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ watch = true
|
|||||||
# Rebuild when a new request is received and changes have been detected.
|
# Rebuild when a new request is received and changes have been detected.
|
||||||
# "eager"
|
# "eager"
|
||||||
# Rebuild as soon as changes are detected.
|
# Rebuild as soon as changes are detected.
|
||||||
watch.mode = normal
|
watch.mode = eager
|
||||||
|
|
||||||
# Watch the entire `$GOPATH` for changes.
|
# Watch the entire `$GOPATH` for changes.
|
||||||
# Values:
|
# Values:
|
||||||
|
|||||||
162
revel/test.go
162
revel/test.go
@@ -16,8 +16,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/revel/cmd/harness"
|
"github.com/revel/cmd/harness"
|
||||||
"github.com/revel/modules/testrunner/app/controllers"
|
"github.com/revel/cmd/model"
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/tests"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdTest = &Command{
|
var cmdTest = &Command{
|
||||||
@@ -47,80 +48,104 @@ or one of UserTest's methods:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmdTest.Run = testApp
|
cmdTest.RunWith = testApp
|
||||||
|
cmdTest.UpdateConfig = updateTestConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func testApp(args []string) {
|
// Called to update the config command with from the older stype
|
||||||
var err error
|
func updateTestConfig(c *model.CommandConfig, args []string) bool {
|
||||||
if len(args) == 0 {
|
c.Index = TEST
|
||||||
errorf("No import path given.\nRun 'revel help test' for usage.\n")
|
// The full test runs
|
||||||
|
// revel test <import path> (run mode) (suite(.function))
|
||||||
|
if len(args) < 1 {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
c.Test.ImportPath = args[0]
|
||||||
|
if len(args) > 1 {
|
||||||
|
c.Test.Mode = args[1]
|
||||||
|
}
|
||||||
|
if len(args) > 2 {
|
||||||
|
c.Test.Function = args[2]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called to test the application
|
||||||
|
func testApp(c *model.CommandConfig) {
|
||||||
|
var err error
|
||||||
|
|
||||||
mode := DefaultRunMode
|
mode := DefaultRunMode
|
||||||
if len(args) >= 2 {
|
if c.Test.Mode != "" {
|
||||||
mode = args[1]
|
mode = c.Test.Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find and parse app.conf
|
// Find and parse app.conf
|
||||||
revel.Init(mode, args[0], "")
|
revel_path := model.NewRevelPaths(mode, c.Test.ImportPath, "", model.DoNothingRevelCallback)
|
||||||
|
|
||||||
// Ensure that the testrunner is loaded in this mode.
|
// Ensure that the testrunner is loaded in this mode.
|
||||||
checkTestRunner()
|
// todo checkTestRunner()
|
||||||
|
|
||||||
// Create a directory to hold the test result files.
|
// Create a directory to hold the test result files.
|
||||||
resultPath := filepath.Join(revel.BasePath, "test-results")
|
resultPath := filepath.Join(revel_path.BasePath, "test-results")
|
||||||
if err = os.RemoveAll(resultPath); err != nil {
|
if err = os.RemoveAll(resultPath); err != nil {
|
||||||
errorf("Failed to remove test result directory %s: %s", resultPath, err)
|
utils.Logger.Errorf("Failed to remove test result directory %s: %s", resultPath, err)
|
||||||
}
|
}
|
||||||
if err = os.Mkdir(resultPath, 0777); err != nil {
|
if err = os.Mkdir(resultPath, 0777); err != nil {
|
||||||
errorf("Failed to create test result directory %s: %s", resultPath, err)
|
utils.Logger.Errorf("Failed to create test result directory %s: %s", resultPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct all the output into a file in the test-results directory.
|
// Direct all the output into a file in the test-results directory.
|
||||||
file, err := os.OpenFile(filepath.Join(resultPath, "app.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
file, err := os.OpenFile(filepath.Join(resultPath, "app.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("Failed to create test result log file: %s", err)
|
utils.Logger.Errorf("Failed to create test result log file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app, reverr := harness.Build()
|
app, reverr := harness.Build(c, revel_path)
|
||||||
if reverr != nil {
|
if reverr != nil {
|
||||||
errorf("Error building: %s", reverr)
|
utils.Logger.Errorf("Error building: %s", reverr)
|
||||||
}
|
}
|
||||||
cmd := app.Cmd()
|
runMode := fmt.Sprintf(`{"mode":"%s","testModeFlag":true, "specialUseFlag":%v}`, app.Paths.RunMode, c.Verbose)
|
||||||
|
if c.HistoricMode {
|
||||||
|
runMode = app.Paths.RunMode
|
||||||
|
}
|
||||||
|
cmd := app.Cmd(runMode)
|
||||||
|
|
||||||
cmd.Stderr = io.MultiWriter(cmd.Stderr, file)
|
cmd.Stderr = io.MultiWriter(cmd.Stderr, file)
|
||||||
cmd.Stdout = io.MultiWriter(cmd.Stderr, file)
|
cmd.Stdout = io.MultiWriter(cmd.Stderr, file)
|
||||||
|
|
||||||
// Start the app...
|
// Start the app...
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(c); err != nil {
|
||||||
errorf("%s", err)
|
utils.Logger.Errorf("%s", err)
|
||||||
}
|
}
|
||||||
defer cmd.Kill()
|
defer cmd.Kill()
|
||||||
revel.INFO.Printf("Testing %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, mode)
|
|
||||||
|
|
||||||
var httpAddr = revel.HTTPAddr
|
var httpAddr = revel_path.HTTPAddr
|
||||||
if httpAddr == "" {
|
if httpAddr == "" {
|
||||||
httpAddr = "127.0.0.1"
|
httpAddr = "localhost"
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpProto = "http"
|
var httpProto = "http"
|
||||||
if revel.HTTPSsl {
|
if revel_path.HTTPSsl {
|
||||||
httpProto = "https"
|
httpProto = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a list of tests
|
// Get a list of tests
|
||||||
var baseURL = fmt.Sprintf("%s://%s:%d", httpProto, httpAddr, revel.HTTPPort)
|
var baseURL = fmt.Sprintf("%s://%s:%d", httpProto, httpAddr, revel_path.HTTPPort)
|
||||||
|
|
||||||
|
utils.Logger.Infof("Testing %s (%s) in %s mode URL %s \n", revel_path.AppName, revel_path.ImportPath, mode, baseURL)
|
||||||
testSuites, _ := getTestsList(baseURL)
|
testSuites, _ := getTestsList(baseURL)
|
||||||
|
|
||||||
// If a specific TestSuite[.Method] is specified, only run that suite/test
|
// If a specific TestSuite[.Method] is specified, only run that suite/test
|
||||||
if len(args) == 3 {
|
if c.Test.Function != "" {
|
||||||
testSuites = filterTestSuites(testSuites, args[2])
|
testSuites = filterTestSuites(testSuites, c.Test.Function)
|
||||||
}
|
}
|
||||||
|
|
||||||
testSuiteCount := len(*testSuites)
|
testSuiteCount := len(*testSuites)
|
||||||
fmt.Printf("\n%d test suite%s to run.\n", testSuiteCount, pluralize(testSuiteCount, "", "s"))
|
fmt.Printf("\n%d test suite%s to run.\n", testSuiteCount, pluralize(testSuiteCount, "", "s"))
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
// Run each suite.
|
// Run each suite.
|
||||||
failedResults, overallSuccess := runTestSuites(baseURL, resultPath, testSuites)
|
failedResults, overallSuccess := runTestSuites(revel_path, baseURL, resultPath, testSuites)
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
if overallSuccess {
|
if overallSuccess {
|
||||||
@@ -137,16 +162,18 @@ func testApp(args []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeResultFile(resultPath, "result.failed", "failed")
|
writeResultFile(resultPath, "result.failed", "failed")
|
||||||
errorf("Some tests failed. See file://%s for results.", resultPath)
|
utils.Logger.Errorf("Some tests failed. See file://%s for results.", resultPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Outputs the results to a file
|
||||||
func writeResultFile(resultPath, name, content string) {
|
func writeResultFile(resultPath, name, content string) {
|
||||||
if err := ioutil.WriteFile(filepath.Join(resultPath, name), []byte(content), 0666); err != nil {
|
if err := ioutil.WriteFile(filepath.Join(resultPath, name), []byte(content), 0666); err != nil {
|
||||||
errorf("Failed to write result file %s: %s", filepath.Join(resultPath, name), err)
|
utils.Logger.Errorf("Failed to write result file %s: %s", filepath.Join(resultPath, name), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determines if response should be plural
|
||||||
func pluralize(num int, singular, plural string) string {
|
func pluralize(num int, singular, plural string) string {
|
||||||
if num == 1 {
|
if num == 1 {
|
||||||
return singular
|
return singular
|
||||||
@@ -156,7 +183,7 @@ func pluralize(num int, singular, plural string) string {
|
|||||||
|
|
||||||
// Filters test suites and individual tests to match
|
// Filters test suites and individual tests to match
|
||||||
// the parsed command line parameter
|
// the parsed command line parameter
|
||||||
func filterTestSuites(suites *[]controllers.TestSuiteDesc, suiteArgument string) *[]controllers.TestSuiteDesc {
|
func filterTestSuites(suites *[]tests.TestSuiteDesc, suiteArgument string) *[]tests.TestSuiteDesc {
|
||||||
var suiteName, testName string
|
var suiteName, testName string
|
||||||
argArray := strings.Split(suiteArgument, ".")
|
argArray := strings.Split(suiteArgument, ".")
|
||||||
suiteName = argArray[0]
|
suiteName = argArray[0]
|
||||||
@@ -171,54 +198,34 @@ func filterTestSuites(suites *[]controllers.TestSuiteDesc, suiteArgument string)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if testName == "" {
|
if testName == "" {
|
||||||
return &[]controllers.TestSuiteDesc{suite}
|
return &[]tests.TestSuiteDesc{suite}
|
||||||
}
|
}
|
||||||
// Only run a particular test in a suite
|
// Only run a particular test in a suite
|
||||||
for _, test := range suite.Tests {
|
for _, test := range suite.Tests {
|
||||||
if test.Name != testName {
|
if test.Name != testName {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return &[]controllers.TestSuiteDesc{
|
return &[]tests.TestSuiteDesc{
|
||||||
{
|
{
|
||||||
Name: suite.Name,
|
Name: suite.Name,
|
||||||
Tests: []controllers.TestDesc{test},
|
Tests: []tests.TestDesc{test},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
errorf("Couldn't find test %s in suite %s", testName, suiteName)
|
utils.Logger.Errorf("Couldn't find test %s in suite %s", testName, suiteName)
|
||||||
}
|
}
|
||||||
errorf("Couldn't find test suite %s", suiteName)
|
utils.Logger.Errorf("Couldn't find test suite %s", suiteName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkTestRunner() {
|
|
||||||
testRunnerFound := false
|
|
||||||
for _, module := range revel.Modules {
|
|
||||||
if module.ImportPath == revel.Config.StringDefault("module.testrunner", "github.com/revel/modules/testrunner") {
|
|
||||||
testRunnerFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !testRunnerFound {
|
|
||||||
errorf(`Error: The testrunner module is not running.
|
|
||||||
|
|
||||||
You can add it to a run mode configuration with the following line:
|
|
||||||
|
|
||||||
module.testrunner = github.com/revel/modules/testrunner
|
|
||||||
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a list of tests from server.
|
// Get a list of tests from server.
|
||||||
// Since this is the first request to the server, retry/sleep a couple times
|
// Since this is the first request to the server, retry/sleep a couple times
|
||||||
// in case it hasn't finished starting up yet.
|
// in case it hasn't finished starting up yet.
|
||||||
func getTestsList(baseURL string) (*[]controllers.TestSuiteDesc, error) {
|
func getTestsList(baseURL string) (*[]tests.TestSuiteDesc, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
resp *http.Response
|
resp *http.Response
|
||||||
testSuites []controllers.TestSuiteDesc
|
testSuites []tests.TestSuiteDesc
|
||||||
)
|
)
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
if resp, err = http.Get(baseURL + "/@tests.list"); err == nil {
|
if resp, err = http.Get(baseURL + "/@tests.list"); err == nil {
|
||||||
@@ -231,9 +238,9 @@ func getTestsList(baseURL string) (*[]controllers.TestSuiteDesc, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("Failed to request test list: %s", err)
|
utils.Logger.Fatalf("Failed to request test list: %s %s", baseURL, err)
|
||||||
} else {
|
} else {
|
||||||
errorf("Failed to request test list: non-200 response")
|
utils.Logger.Fatalf("Failed to request test list: non-200 response %s", baseURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -245,21 +252,15 @@ func getTestsList(baseURL string) (*[]controllers.TestSuiteDesc, error) {
|
|||||||
return &testSuites, err
|
return &testSuites, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTestSuites(baseURL, resultPath string, testSuites *[]controllers.TestSuiteDesc) (*[]controllers.TestSuiteResult, bool) {
|
// Run the testsuites using the container
|
||||||
// Load the result template, which we execute for each suite.
|
func runTestSuites(paths *model.RevelContainer, baseURL, resultPath string, testSuites *[]tests.TestSuiteDesc) (*[]tests.TestSuiteResult, bool) {
|
||||||
module, _ := revel.ModuleByName("testrunner")
|
|
||||||
TemplateLoader := revel.NewTemplateLoader([]string{filepath.Join(module.Path, "app", "views")})
|
// We can determine the testsuite location by finding the test module and extracting the data from it
|
||||||
if err := TemplateLoader.Refresh(); err != nil {
|
resultFilePath := filepath.Join(paths.ModulePathMap["testrunner"], "app", "views", "TestRunner/SuiteResult.html")
|
||||||
errorf("Failed to compile templates: %s", err)
|
|
||||||
}
|
|
||||||
resultTemplate, err := TemplateLoader.Template("TestRunner/SuiteResult.html")
|
|
||||||
if err != nil {
|
|
||||||
errorf("Failed to load suite result template: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
overallSuccess = true
|
overallSuccess = true
|
||||||
failedResults []controllers.TestSuiteResult
|
failedResults []tests.TestSuiteResult
|
||||||
)
|
)
|
||||||
for _, suite := range *testSuites {
|
for _, suite := range *testSuites {
|
||||||
// Print the name of the suite we're running.
|
// Print the name of the suite we're running.
|
||||||
@@ -271,21 +272,24 @@ func runTestSuites(baseURL, resultPath string, testSuites *[]controllers.TestSui
|
|||||||
|
|
||||||
// Run every test.
|
// Run every test.
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
suiteResult := controllers.TestSuiteResult{Name: suite.Name, Passed: true}
|
suiteResult := tests.TestSuiteResult{Name: suite.Name, Passed: true}
|
||||||
for _, test := range suite.Tests {
|
for _, test := range suite.Tests {
|
||||||
testURL := baseURL + "/@tests/" + suite.Name + "/" + test.Name
|
testURL := baseURL + "/@tests/" + suite.Name + "/" + test.Name
|
||||||
resp, err := http.Get(testURL)
|
resp, err := http.Get(testURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("Failed to fetch test result at url %s: %s", testURL, err)
|
utils.Logger.Errorf("Failed to fetch test result at url %s: %s", testURL, err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var testResult controllers.TestResult
|
var testResult tests.TestResult
|
||||||
err = json.NewDecoder(resp.Body).Decode(&testResult)
|
err = json.NewDecoder(resp.Body).Decode(&testResult)
|
||||||
if err == nil && !testResult.Passed {
|
if err == nil && !testResult.Passed {
|
||||||
suiteResult.Passed = false
|
suiteResult.Passed = false
|
||||||
|
fmt.Printf(" %s.%s : FAILED\n", suite.Name, test.Name)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" %s.%s : PASSED\n", suite.Name, test.Name)
|
||||||
}
|
}
|
||||||
suiteResult.Results = append(suiteResult.Results, testResult)
|
suiteResult.Results = append(suiteResult.Results, testResult)
|
||||||
}
|
}
|
||||||
@@ -301,13 +305,7 @@ func runTestSuites(baseURL, resultPath string, testSuites *[]controllers.TestSui
|
|||||||
// Create the result HTML file.
|
// Create the result HTML file.
|
||||||
suiteResultFilename := filepath.Join(resultPath,
|
suiteResultFilename := filepath.Join(resultPath,
|
||||||
fmt.Sprintf("%s.%s.html", suite.Name, strings.ToLower(suiteResultStr)))
|
fmt.Sprintf("%s.%s.html", suite.Name, strings.ToLower(suiteResultStr)))
|
||||||
suiteResultFile, err := os.Create(suiteResultFilename)
|
utils.MustRenderTemplate(suiteResultFilename, resultFilePath, suiteResult)
|
||||||
if err != nil {
|
|
||||||
errorf("Failed to create result file %s: %s", suiteResultFilename, err)
|
|
||||||
}
|
|
||||||
if err = resultTemplate.Render(suiteResultFile, suiteResult); err != nil {
|
|
||||||
errorf("Failed to render result template: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &failedResults, overallSuccess
|
return &failedResults, overallSuccess
|
||||||
|
|||||||
176
revel/util.go
176
revel/util.go
@@ -1,176 +0,0 @@
|
|||||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
|
||||||
// Revel Framework source code and usage is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"compress/gzip"
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/revel/revel"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoggedError is wrapper to differentiate logged panics from unexpected ones.
|
|
||||||
type LoggedError struct{ error }
|
|
||||||
|
|
||||||
func panicOnError(err error, msg string) {
|
|
||||||
if revErr, ok := err.(*revel.Error); (ok && revErr != nil) || (!ok && err != nil) {
|
|
||||||
fmt.Fprintf(os.Stderr, "Abort: %s: %s\n", msg, err)
|
|
||||||
panic(LoggedError{err})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustCopyFile(destFilename, srcFilename string) {
|
|
||||||
destFile, err := os.Create(destFilename)
|
|
||||||
panicOnError(err, "Failed to create file "+destFilename)
|
|
||||||
|
|
||||||
srcFile, err := os.Open(srcFilename)
|
|
||||||
panicOnError(err, "Failed to open file "+srcFilename)
|
|
||||||
|
|
||||||
_, err = io.Copy(destFile, srcFile)
|
|
||||||
panicOnError(err,
|
|
||||||
fmt.Sprintf("Failed to copy data from %s to %s", srcFile.Name(), destFile.Name()))
|
|
||||||
|
|
||||||
err = destFile.Close()
|
|
||||||
panicOnError(err, "Failed to close file "+destFile.Name())
|
|
||||||
|
|
||||||
err = srcFile.Close()
|
|
||||||
panicOnError(err, "Failed to close file "+srcFile.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustRenderTemplate(destPath, srcPath string, data map[string]interface{}) {
|
|
||||||
tmpl, err := template.ParseFiles(srcPath)
|
|
||||||
panicOnError(err, "Failed to parse template "+srcPath)
|
|
||||||
|
|
||||||
f, err := os.Create(destPath)
|
|
||||||
panicOnError(err, "Failed to create "+destPath)
|
|
||||||
|
|
||||||
err = tmpl.Execute(f, data)
|
|
||||||
panicOnError(err, "Failed to render template "+srcPath)
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
panicOnError(err, "Failed to close "+f.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustChmod(filename string, mode os.FileMode) {
|
|
||||||
err := os.Chmod(filename, mode)
|
|
||||||
panicOnError(err, fmt.Sprintf("Failed to chmod %d %q", mode, filename))
|
|
||||||
}
|
|
||||||
|
|
||||||
// copyDir copies a directory tree over to a new directory. Any files ending in
|
|
||||||
// ".template" are treated as a Go template and rendered using the given data.
|
|
||||||
// Additionally, the trailing ".template" is stripped from the file name.
|
|
||||||
// Also, dot files and dot directories are skipped.
|
|
||||||
func mustCopyDir(destDir, srcDir string, data map[string]interface{}) error {
|
|
||||||
return revel.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
|
|
||||||
// Get the relative path from the source base, and the corresponding path in
|
|
||||||
// the dest directory.
|
|
||||||
relSrcPath := strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator))
|
|
||||||
destPath := filepath.Join(destDir, relSrcPath)
|
|
||||||
|
|
||||||
// Skip dot files and dot directories.
|
|
||||||
if strings.HasPrefix(relSrcPath, ".") {
|
|
||||||
if info.IsDir() {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a subdirectory if necessary.
|
|
||||||
if info.IsDir() {
|
|
||||||
err := os.MkdirAll(filepath.Join(destDir, relSrcPath), 0777)
|
|
||||||
if !os.IsExist(err) {
|
|
||||||
panicOnError(err, "Failed to create directory")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this file ends in ".template", render it as a template.
|
|
||||||
if strings.HasSuffix(relSrcPath, ".template") {
|
|
||||||
mustRenderTemplate(destPath[:len(destPath)-len(".template")], srcPath, data)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Else, just copy it over.
|
|
||||||
mustCopyFile(destPath, srcPath)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustTarGzDir(destFilename, srcDir string) string {
|
|
||||||
zipFile, err := os.Create(destFilename)
|
|
||||||
panicOnError(err, "Failed to create archive")
|
|
||||||
defer func() {
|
|
||||||
_ = zipFile.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
gzipWriter := gzip.NewWriter(zipFile)
|
|
||||||
defer func() {
|
|
||||||
_ = gzipWriter.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
tarWriter := tar.NewWriter(gzipWriter)
|
|
||||||
defer func() {
|
|
||||||
_ = tarWriter.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
_ = revel.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
srcFile, err := os.Open(srcPath)
|
|
||||||
panicOnError(err, "Failed to read source file")
|
|
||||||
defer func() {
|
|
||||||
_ = srcFile.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = tarWriter.WriteHeader(&tar.Header{
|
|
||||||
Name: strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator)),
|
|
||||||
Size: info.Size(),
|
|
||||||
Mode: int64(info.Mode()),
|
|
||||||
ModTime: info.ModTime(),
|
|
||||||
})
|
|
||||||
panicOnError(err, "Failed to write tar entry header")
|
|
||||||
|
|
||||||
_, err = io.Copy(tarWriter, srcFile)
|
|
||||||
panicOnError(err, "Failed to copy")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return zipFile.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func exists(filename string) bool {
|
|
||||||
_, err := os.Stat(filename)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// empty returns true if the given directory is empty.
|
|
||||||
// the directory must exist.
|
|
||||||
func empty(dirname string) bool {
|
|
||||||
dir, err := os.Open(dirname)
|
|
||||||
if err != nil {
|
|
||||||
errorf("error opening directory: %s", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = dir.Close()
|
|
||||||
}()
|
|
||||||
results, _ := dir.Readdir(1)
|
|
||||||
return len(results) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func importPathFromCurrentDir() string {
|
|
||||||
pwd, _ := os.Getwd()
|
|
||||||
importPath, _ := filepath.Rel(filepath.Join(build.Default.GOPATH, "src"), pwd)
|
|
||||||
return filepath.ToSlash(importPath)
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"go/build"
|
||||||
|
"go/token"
|
||||||
|
"go/parser"
|
||||||
|
"go/ast"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdVersion = &Command{
|
var cmdVersion = &Command{
|
||||||
@@ -28,11 +35,46 @@ For example:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmdVersion.Run = versionApp
|
cmdVersion.RunWith = versionApp
|
||||||
}
|
}
|
||||||
|
|
||||||
func versionApp(args []string) {
|
// Displays the version of go and Revel
|
||||||
fmt.Printf("Version(s):")
|
func versionApp(c *model.CommandConfig) {
|
||||||
fmt.Printf("\n Revel v%v (%v)", revel.Version, revel.BuildDate)
|
revelPkg, err := build.Import(model.RevelImportPath, c.Version.ImportPath, build.FindOnly)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Errorf("Failed to find Revel with error:", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fset := token.NewFileSet() // positions are relative to fset
|
||||||
|
|
||||||
|
|
||||||
|
version, err := ioutil.ReadFile(filepath.Join(revelPkg.Dir,"version.go"))
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Errorf("Failed to find Revel version:", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse src but stop after processing the imports.
|
||||||
|
f, err := parser.ParseFile(fset, "", version, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Errorf("Failed to parse Revel version error:", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the imports from the file's AST.
|
||||||
|
for _, s := range f.Decls {
|
||||||
|
genDecl, ok := s.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if genDecl.Tok != token.CONST {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, a := range genDecl.Specs {
|
||||||
|
spec := a.(*ast.ValueSpec)
|
||||||
|
r := spec.Values[0].(*ast.BasicLit)
|
||||||
|
fmt.Printf("Revel %s = %s\n",spec.Names[0].Name,r.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("\n %s %s/%s\n\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
fmt.Printf("\n %s %s/%s\n\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
|
|||||||
158
tests/testrunner.go
Normal file
158
tests/testrunner.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||||
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// TestSuiteDesc is used for storing information about a single test suite.
|
||||||
|
// This structure is required by revel test cmd.
|
||||||
|
type TestSuiteDesc struct {
|
||||||
|
Name string
|
||||||
|
Tests []TestDesc
|
||||||
|
|
||||||
|
// Elem is reflect.Type which can be used for accessing methods
|
||||||
|
// of the test suite.
|
||||||
|
Elem reflect.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDesc is used for describing a single test of some test suite.
|
||||||
|
// This structure is required by revel test cmd.
|
||||||
|
type TestDesc struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSuiteResult stores the results the whole test suite.
|
||||||
|
// This structure is required by revel test cmd.
|
||||||
|
type TestSuiteResult struct {
|
||||||
|
Name string
|
||||||
|
Passed bool
|
||||||
|
Results []TestResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestResult represents the results of running a single test of some test suite.
|
||||||
|
// This structure is required by revel test cmd.
|
||||||
|
type TestResult struct {
|
||||||
|
Name string
|
||||||
|
Passed bool
|
||||||
|
ErrorHTML template.HTML
|
||||||
|
ErrorSummary string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
testSuites []TestSuiteDesc // A list of all available tests.
|
||||||
|
|
||||||
|
none = []reflect.Value{} // It is used as input for reflect call in a few places.
|
||||||
|
|
||||||
|
// registeredTests simplifies the search of test suites by their name.
|
||||||
|
// "TestSuite.TestName" is used as a key. Value represents index in testSuites.
|
||||||
|
registeredTests map[string]int
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Below are helper functions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// describeSuite expects testsuite interface as input parameter
|
||||||
|
// and returns its description in a form of TestSuiteDesc structure.
|
||||||
|
func describeSuite(testSuite interface{}) TestSuiteDesc {
|
||||||
|
t := reflect.TypeOf(testSuite)
|
||||||
|
|
||||||
|
// Get a list of methods of the embedded test type.
|
||||||
|
// It will be used to make sure the same tests are not included in multiple test suites.
|
||||||
|
super := t.Elem().Field(0).Type
|
||||||
|
superMethods := map[string]bool{}
|
||||||
|
for i := 0; i < super.NumMethod(); i++ {
|
||||||
|
// Save the current method's name.
|
||||||
|
superMethods[super.Method(i).Name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list of methods on the test suite that take no parameters, return
|
||||||
|
// no results, and were not part of the embedded type's method set.
|
||||||
|
var tests []TestDesc
|
||||||
|
for i := 0; i < t.NumMethod(); i++ {
|
||||||
|
m := t.Method(i)
|
||||||
|
mt := m.Type
|
||||||
|
|
||||||
|
// Make sure the test method meets the criterias:
|
||||||
|
// - method of testSuite without input parameters;
|
||||||
|
// - nothing is returned;
|
||||||
|
// - has "Test" prefix;
|
||||||
|
// - doesn't belong to the embedded structure.
|
||||||
|
methodWithoutParams := (mt.NumIn() == 1 && mt.In(0) == t)
|
||||||
|
nothingReturned := (mt.NumOut() == 0)
|
||||||
|
hasTestPrefix := (strings.HasPrefix(m.Name, "Test"))
|
||||||
|
if methodWithoutParams && nothingReturned && hasTestPrefix && !superMethods[m.Name] {
|
||||||
|
// Register the test suite's index so we can quickly find it by test's name later.
|
||||||
|
registeredTests[t.Elem().Name()+"."+m.Name] = len(testSuites)
|
||||||
|
|
||||||
|
// Add test to the list of tests.
|
||||||
|
tests = append(tests, TestDesc{m.Name})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TestSuiteDesc{
|
||||||
|
Name: t.Elem().Name(),
|
||||||
|
Tests: tests,
|
||||||
|
Elem: t.Elem(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorSummary gets an error and returns its summary in human readable format.
|
||||||
|
func errorSummary(err *utils.Error) (message string) {
|
||||||
|
expectedPrefix := "(expected)"
|
||||||
|
actualPrefix := "(actual)"
|
||||||
|
errDesc := err.Description
|
||||||
|
//strip the actual/expected stuff to provide more condensed display.
|
||||||
|
if strings.Index(errDesc, expectedPrefix) == 0 {
|
||||||
|
errDesc = errDesc[len(expectedPrefix):]
|
||||||
|
}
|
||||||
|
if strings.LastIndex(errDesc, actualPrefix) > 0 {
|
||||||
|
errDesc = errDesc[0 : len(errDesc)-len(actualPrefix)]
|
||||||
|
}
|
||||||
|
|
||||||
|
errFile := err.Path
|
||||||
|
slashIdx := strings.LastIndex(errFile, "/")
|
||||||
|
if slashIdx > 0 {
|
||||||
|
errFile = errFile[slashIdx+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
message = fmt.Sprintf("%s %s#%d", errDesc, errFile, err.Line)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// If line of error isn't known return the message as is.
|
||||||
|
if err.Line == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, include info about the line number and the relevant
|
||||||
|
// source code lines.
|
||||||
|
message += fmt.Sprintf(" (around line %d): ", err.Line)
|
||||||
|
for _, line := range err.ContextSource() {
|
||||||
|
if line.IsError {
|
||||||
|
message += line.Source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//sortbySuiteName sorts the testsuites by name.
|
||||||
|
type sortBySuiteName []interface{}
|
||||||
|
|
||||||
|
func (a sortBySuiteName) Len() int { return len(a) }
|
||||||
|
func (a sortBySuiteName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a sortBySuiteName) Less(i, j int) bool {
|
||||||
|
return reflect.TypeOf(a[i]).Elem().Name() < reflect.TypeOf(a[j]).Elem().Name()
|
||||||
|
}
|
||||||
81
utils/error.go
Normal file
81
utils/error.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The error is a wrapper for the
|
||||||
|
type Error struct {
|
||||||
|
SourceType string // The type of source that failed to build.
|
||||||
|
Title, Path, Description string // Description of the error, as presented to the user.
|
||||||
|
Line, Column int // Where the error was encountered.
|
||||||
|
SourceLines []string // The entire source file, split into lines.
|
||||||
|
Stack string // The raw stack trace string from debug.Stack().
|
||||||
|
MetaError string // Error that occurred producing the error page.
|
||||||
|
Link string // A configurable link to wrap the error source in
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a link based on the configuration setting "errors.link"
|
||||||
|
func (e *Error) SetLink(errorLink string) {
|
||||||
|
errorLink = strings.Replace(errorLink, "{{Path}}", e.Path, -1)
|
||||||
|
errorLink = strings.Replace(errorLink, "{{Line}}", strconv.Itoa(e.Line), -1)
|
||||||
|
|
||||||
|
e.Link = "<a href=" + errorLink + ">" + e.Path + ":" + strconv.Itoa(e.Line) + "</a>"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error method constructs a plaintext version of the error, taking
|
||||||
|
// account that fields are optionally set. Returns e.g. Compilation Error
|
||||||
|
// (in views/header.html:51): expected right delim in end; got "}"
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
if e == nil {
|
||||||
|
panic("opps")
|
||||||
|
}
|
||||||
|
loc := ""
|
||||||
|
if e.Path != "" {
|
||||||
|
line := ""
|
||||||
|
if e.Line != 0 {
|
||||||
|
line = fmt.Sprintf(":%d", e.Line)
|
||||||
|
}
|
||||||
|
loc = fmt.Sprintf("(in %s%s)", e.Path, line)
|
||||||
|
}
|
||||||
|
header := loc
|
||||||
|
if e.Title != "" {
|
||||||
|
if loc != "" {
|
||||||
|
header = fmt.Sprintf("%s %s: ", e.Title, loc)
|
||||||
|
} else {
|
||||||
|
header = fmt.Sprintf("%s: ", e.Title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s%s", header, e.Description)
|
||||||
|
}
|
||||||
|
// ContextSource method returns a snippet of the source around
|
||||||
|
// where the error occurred.
|
||||||
|
func (e *Error) ContextSource() []SourceLine {
|
||||||
|
if e.SourceLines == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
start := (e.Line - 1) - 5
|
||||||
|
if start < 0 {
|
||||||
|
start = 0
|
||||||
|
}
|
||||||
|
end := (e.Line - 1) + 5
|
||||||
|
if end > len(e.SourceLines) {
|
||||||
|
end = len(e.SourceLines)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := make([]SourceLine, end-start)
|
||||||
|
for i, src := range e.SourceLines[start:end] {
|
||||||
|
fileLine := start + i + 1
|
||||||
|
lines[i] = SourceLine{src, fileLine, fileLine == e.Line}
|
||||||
|
}
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
// SourceLine structure to hold the per-source-line details.
|
||||||
|
type SourceLine struct {
|
||||||
|
Source string
|
||||||
|
Line int
|
||||||
|
IsError bool
|
||||||
|
}
|
||||||
285
utils/file.go
Normal file
285
utils/file.go
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
|
||||||
|
// DirExists returns true if the given path exists and is a directory.
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"archive/tar"
|
||||||
|
"strings"
|
||||||
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"compress/gzip"
|
||||||
|
"go/build"
|
||||||
|
"io/ioutil"
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DirExists(filename string) bool {
|
||||||
|
fileInfo, err := os.Stat(filename)
|
||||||
|
return err == nil && fileInfo.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustReadLines reads the lines of the given file. Panics in the case of error.
|
||||||
|
func MustReadLines(filename string) []string {
|
||||||
|
r, err := ReadLines(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLines reads the lines of the given file. Panics in the case of error.
|
||||||
|
func ReadLines(filename string) ([]string, error) {
|
||||||
|
dataBytes, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return strings.Split(string(dataBytes), "\n"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustCopyFile(destFilename, srcFilename string) {
|
||||||
|
destFile, err := os.Create(destFilename)
|
||||||
|
PanicOnError(err, "Failed to create file "+destFilename)
|
||||||
|
|
||||||
|
srcFile, err := os.Open(srcFilename)
|
||||||
|
PanicOnError(err, "Failed to open file "+srcFilename)
|
||||||
|
|
||||||
|
_, err = io.Copy(destFile, srcFile)
|
||||||
|
PanicOnError(err,
|
||||||
|
fmt.Sprintf("Failed to copy data from %s to %s", srcFile.Name(), destFile.Name()))
|
||||||
|
|
||||||
|
err = destFile.Close()
|
||||||
|
PanicOnError(err, "Failed to close file "+destFile.Name())
|
||||||
|
|
||||||
|
err = srcFile.Close()
|
||||||
|
PanicOnError(err, "Failed to close file "+srcFile.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// GenerateTemplate renders the given template to produce source code, which it writes
|
||||||
|
// to the given file.
|
||||||
|
func MustGenerateTemplate(filename, templateSource string, args map[string]interface{}) (err error) {
|
||||||
|
tmpl := template.Must(template.New("").Parse(templateSource))
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err = tmpl.Execute(&b, args); err != nil {
|
||||||
|
Logger.Fatal("ExecuteTemplate: Execute failed", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sourceCode := b.String()
|
||||||
|
filePath := filepath.Dir(filename)
|
||||||
|
if !DirExists(filePath) {
|
||||||
|
err = os.Mkdir(filePath, 0777)
|
||||||
|
if err != nil && !os.IsExist(err) {
|
||||||
|
Logger.Fatal("Failed to make directory","dir", filePath, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Create the file
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
Logger.Fatal("Failed to create file","error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = file.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err = file.WriteString(sourceCode); err != nil {
|
||||||
|
Logger.Fatal("Failed to write to file: ", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the target path and source path and data. A template
|
||||||
|
func MustRenderTemplate(destPath, srcPath string, data interface{}) {
|
||||||
|
tmpl, err := template.ParseFiles(srcPath)
|
||||||
|
PanicOnError(err, "Failed to parse template "+srcPath)
|
||||||
|
|
||||||
|
f, err := os.Create(destPath)
|
||||||
|
PanicOnError(err, "Failed to create "+destPath)
|
||||||
|
|
||||||
|
err = tmpl.Execute(f, data)
|
||||||
|
PanicOnError(err, "Failed to render template "+srcPath)
|
||||||
|
|
||||||
|
err = f.Close()
|
||||||
|
PanicOnError(err, "Failed to close "+f.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the target path and source path and data. A template
|
||||||
|
func MustRenderTemplateToStream(output io.Writer, srcPath []string, data interface{}) {
|
||||||
|
tmpl, err := template.ParseFiles(srcPath...)
|
||||||
|
PanicOnError(err, "Failed to parse template "+srcPath[0])
|
||||||
|
|
||||||
|
err = tmpl.Execute(output, data)
|
||||||
|
PanicOnError(err, "Failed to render template "+srcPath[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustChmod(filename string, mode os.FileMode) {
|
||||||
|
err := os.Chmod(filename, mode)
|
||||||
|
PanicOnError(err, fmt.Sprintf("Failed to chmod %d %q", mode, filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called if panic
|
||||||
|
func PanicOnError(err error, msg string) {
|
||||||
|
if revErr, ok := err.(*Error); (ok && revErr != nil) || (!ok && err != nil) {
|
||||||
|
Logger.Fatalf("Abort: %s: %s %s\n", msg, revErr, err)
|
||||||
|
//panic(NewLoggedError(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyDir copies a directory tree over to a new directory. Any files ending in
|
||||||
|
// ".template" are treated as a Go template and rendered using the given data.
|
||||||
|
// Additionally, the trailing ".template" is stripped from the file name.
|
||||||
|
// Also, dot files and dot directories are skipped.
|
||||||
|
func MustCopyDir(destDir, srcDir string, data map[string]interface{}) error {
|
||||||
|
return fsWalk(srcDir, srcDir, func(srcPath string, info os.FileInfo, err error) error {
|
||||||
|
// Get the relative path from the source base, and the corresponding path in
|
||||||
|
// the dest directory.
|
||||||
|
relSrcPath := strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator))
|
||||||
|
destPath := filepath.Join(destDir, relSrcPath)
|
||||||
|
|
||||||
|
// Skip dot files and dot directories.
|
||||||
|
if strings.HasPrefix(relSrcPath, ".") {
|
||||||
|
if info.IsDir() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a subdirectory if necessary.
|
||||||
|
if info.IsDir() {
|
||||||
|
err := os.MkdirAll(filepath.Join(destDir, relSrcPath), 0777)
|
||||||
|
if !os.IsExist(err) {
|
||||||
|
PanicOnError(err, "Failed to create directory")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this file ends in ".template", render it as a template.
|
||||||
|
if strings.HasSuffix(relSrcPath, ".template") {
|
||||||
|
MustRenderTemplate(destPath[:len(destPath)-len(".template")], srcPath, data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, just copy it over.
|
||||||
|
MustCopyFile(destPath, srcPath)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Walk(root string, walkFn filepath.WalkFunc) error {
|
||||||
|
return fsWalk(root,root,walkFn)
|
||||||
|
}
|
||||||
|
func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
|
||||||
|
fsWalkFunc := func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var name string
|
||||||
|
name, err = filepath.Rel(fname, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
path = filepath.Join(linkName, name)
|
||||||
|
|
||||||
|
if err == nil && info.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
var symlinkPath string
|
||||||
|
symlinkPath, err = filepath.EvalSymlinks(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/golang/go/blob/master/src/path/filepath/path.go#L392
|
||||||
|
info, err = os.Lstat(symlinkPath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(path, info, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
return fsWalk(symlinkPath, path, walkFn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return walkFn(path, info, err)
|
||||||
|
}
|
||||||
|
err := filepath.Walk(fname, fsWalkFunc)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustTarGzDir(destFilename, srcDir string) string {
|
||||||
|
zipFile, err := os.Create(destFilename)
|
||||||
|
PanicOnError(err, "Failed to create archive")
|
||||||
|
defer func() {
|
||||||
|
_ = zipFile.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
gzipWriter := gzip.NewWriter(zipFile)
|
||||||
|
defer func() {
|
||||||
|
_ = gzipWriter.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
tarWriter := tar.NewWriter(gzipWriter)
|
||||||
|
defer func() {
|
||||||
|
_ = tarWriter.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
_ = fsWalk(srcDir, srcDir, func(srcPath string, info os.FileInfo, err error) error {
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
srcFile, err := os.Open(srcPath)
|
||||||
|
PanicOnError(err, "Failed to read source file")
|
||||||
|
defer func() {
|
||||||
|
_ = srcFile.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = tarWriter.WriteHeader(&tar.Header{
|
||||||
|
Name: strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator)),
|
||||||
|
Size: info.Size(),
|
||||||
|
Mode: int64(info.Mode()),
|
||||||
|
ModTime: info.ModTime(),
|
||||||
|
})
|
||||||
|
PanicOnError(err, "Failed to write tar entry header")
|
||||||
|
|
||||||
|
_, err = io.Copy(tarWriter, srcFile)
|
||||||
|
PanicOnError(err, "Failed to copy")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return zipFile.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Exists(filename string) bool {
|
||||||
|
_, err := os.Stat(filename)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty returns true if the given directory is empty.
|
||||||
|
// the directory must exist.
|
||||||
|
func Empty(dirname string) bool {
|
||||||
|
dir, err := os.Open(dirname)
|
||||||
|
if err != nil {
|
||||||
|
Logger.Infof("error opening directory: %s", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = dir.Close()
|
||||||
|
}()
|
||||||
|
results, _ := dir.Readdir(1)
|
||||||
|
return len(results) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportPathFromCurrentDir() string {
|
||||||
|
pwd, _ := os.Getwd()
|
||||||
|
importPath, _ := filepath.Rel(filepath.Join(build.Default.GOPATH, "src"), pwd)
|
||||||
|
return filepath.ToSlash(importPath)
|
||||||
|
}
|
||||||
43
utils/log.go
Normal file
43
utils/log.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/revel/cmd/logger"
|
||||||
|
"github.com/revel/config"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Logger = logger.New()
|
||||||
|
|
||||||
|
func InitLogger(basePath string, logLevel logger.LogLevel) {
|
||||||
|
newContext := config.NewContext()
|
||||||
|
if logLevel<logger.LvlInfo {
|
||||||
|
newContext.SetOption("log.info.output", "none")
|
||||||
|
newContext.SetOption("log.debug.output", "none")
|
||||||
|
} else {
|
||||||
|
newContext.SetOption("log.info.output", "stdout")
|
||||||
|
newContext.SetOption("log.debug.output", "stdout")
|
||||||
|
}
|
||||||
|
newContext.SetOption("log.warn.output","stderr")
|
||||||
|
newContext.SetOption("log.error.output","stderr")
|
||||||
|
newContext.SetOption("log.crit.output","stderr")
|
||||||
|
Logger.SetHandler(logger.InitializeFromConfig(basePath, newContext))
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is to throw a panic that may be caught by the packger so it can perform the needed
|
||||||
|
// imports
|
||||||
|
func Retry(format string, args ...interface{}) {
|
||||||
|
// Ensure the user's command prompt starts on the next line.
|
||||||
|
if !strings.HasSuffix(format, "\n") {
|
||||||
|
format += "\n"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, format, args...)
|
||||||
|
panic(format) // Panic instead of os.Exit so that deferred will run.
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoggedError struct{ error }
|
||||||
|
|
||||||
|
func NewLoggedError(err error) *LoggedError {
|
||||||
|
return &LoggedError{err}
|
||||||
|
}
|
||||||
11
utils/strings.go
Normal file
11
utils/strings.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
// Return true if the target string is in the list
|
||||||
|
func ContainsString(list []string, target string) bool {
|
||||||
|
for _, el := range list {
|
||||||
|
if el == target {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
306
watcher/watcher.go
Normal file
306
watcher/watcher.go
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||||
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package watcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"gopkg.in/fsnotify.v1"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Listener is an interface for receivers of filesystem events.
|
||||||
|
type Listener interface {
|
||||||
|
// Refresh is invoked by the watcher on relevant filesystem events.
|
||||||
|
// If the listener returns an error, it is served to the user on the current request.
|
||||||
|
Refresh() *utils.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiscerningListener allows the receiver to selectively watch files.
|
||||||
|
type DiscerningListener interface {
|
||||||
|
Listener
|
||||||
|
WatchDir(info os.FileInfo) bool
|
||||||
|
WatchFile(basename string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watcher allows listeners to register to be notified of changes under a given
|
||||||
|
// directory.
|
||||||
|
type Watcher struct {
|
||||||
|
// Parallel arrays of watcher/listener pairs.
|
||||||
|
watchers []*fsnotify.Watcher
|
||||||
|
listeners []Listener
|
||||||
|
forceRefresh bool
|
||||||
|
eagerRefresh bool
|
||||||
|
serial bool
|
||||||
|
lastError int
|
||||||
|
notifyMutex sync.Mutex
|
||||||
|
paths *model.RevelContainer
|
||||||
|
refreshTimer *time.Timer // The timer to countdown the next refresh
|
||||||
|
timerMutex *sync.Mutex // A mutex to prevent concurrent updates
|
||||||
|
refreshChannel chan *utils.Error
|
||||||
|
refreshChannelCount int
|
||||||
|
refreshTimerMS time.Duration // The number of milliseconds between refreshing builds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new watched based on the container
|
||||||
|
func NewWatcher(paths *model.RevelContainer, eagerRefresh bool) *Watcher {
|
||||||
|
return &Watcher{
|
||||||
|
forceRefresh: true,
|
||||||
|
lastError: -1,
|
||||||
|
paths: paths,
|
||||||
|
refreshTimerMS: time.Duration(paths.Config.IntDefault("watch.rebuild.delay", 10)),
|
||||||
|
eagerRefresh: eagerRefresh ||
|
||||||
|
paths.DevMode &&
|
||||||
|
paths.Config.BoolDefault("watch", true) &&
|
||||||
|
paths.Config.StringDefault("watch.mode", "normal") == "eager",
|
||||||
|
timerMutex: &sync.Mutex{},
|
||||||
|
refreshChannel: make(chan *utils.Error, 10),
|
||||||
|
refreshChannelCount: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen registers for events within the given root directories (recursively).
|
||||||
|
func (w *Watcher) Listen(listener Listener, roots ...string) {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Watcher: Failed to create watcher", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the unbuffered Event channel with a buffered one.
|
||||||
|
// Otherwise multiple change events only come out one at a time, across
|
||||||
|
// multiple page views. (There appears no way to "pump" the events out of
|
||||||
|
// the watcher)
|
||||||
|
// This causes a notification when you do a check in go, since you are modifying a buffer in use
|
||||||
|
watcher.Events = make(chan fsnotify.Event, 100)
|
||||||
|
watcher.Errors = make(chan error, 10)
|
||||||
|
|
||||||
|
// Walk through all files / directories under the root, adding each to watcher.
|
||||||
|
for _, p := range roots {
|
||||||
|
// is the directory / file a symlink?
|
||||||
|
f, err := os.Lstat(p)
|
||||||
|
if err == nil && f.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
var realPath string
|
||||||
|
realPath, err = filepath.EvalSymlinks(p)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
p = realPath
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(p)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Watcher: Failed to stat watched path", "path", p, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it is a file, watch that specific file.
|
||||||
|
if !fi.IsDir() {
|
||||||
|
err = watcher.Add(p)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Watcher: Failed to watch", "path", p, "error", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var watcherWalker func(path string, info os.FileInfo, err error) error
|
||||||
|
|
||||||
|
watcherWalker = func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Watcher: Error walking path:", "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
if dl, ok := listener.(DiscerningListener); ok {
|
||||||
|
if !dl.WatchDir(info) {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = watcher.Add(path)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Watcher: Failed to watch", "path", path, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, walk the directory tree.
|
||||||
|
err = utils.Walk(p, watcherWalker)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Watcher: Failed to walk directory", "path", p, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.eagerRefresh {
|
||||||
|
// Create goroutine to notify file changes in real time
|
||||||
|
go w.NotifyWhenUpdated(listener, watcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.watchers = append(w.watchers, watcher)
|
||||||
|
w.listeners = append(w.listeners, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyWhenUpdated notifies the watcher when a file event is received.
|
||||||
|
func (w *Watcher) NotifyWhenUpdated(listener Listener, watcher *fsnotify.Watcher) {
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case ev := <-watcher.Events:
|
||||||
|
if w.rebuildRequired(ev, listener) {
|
||||||
|
if w.serial {
|
||||||
|
// Serialize listener.Refresh() calls.
|
||||||
|
w.notifyMutex.Lock()
|
||||||
|
|
||||||
|
if err := listener.Refresh(); err != nil {
|
||||||
|
utils.Logger.Error("Watcher: Listener refresh reported error:", "error", err)
|
||||||
|
}
|
||||||
|
w.notifyMutex.Unlock()
|
||||||
|
} else {
|
||||||
|
// Run refresh in parallel
|
||||||
|
go func() {
|
||||||
|
w.notifyInProcess(listener)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-watcher.Errors:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify causes the watcher to forward any change events to listeners.
|
||||||
|
// It returns the first (if any) error returned.
|
||||||
|
func (w *Watcher) Notify() *utils.Error {
|
||||||
|
if w.serial {
|
||||||
|
// Serialize Notify() calls.
|
||||||
|
w.notifyMutex.Lock()
|
||||||
|
defer w.notifyMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, watcher := range w.watchers {
|
||||||
|
listener := w.listeners[i]
|
||||||
|
|
||||||
|
// Pull all pending events / errors from the watcher.
|
||||||
|
refresh := false
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case ev := <-watcher.Events:
|
||||||
|
if w.rebuildRequired(ev, listener) {
|
||||||
|
refresh = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case <-watcher.Errors:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
// No events left to pull
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Logger.Info("Watcher:Notify refresh state", "Current Index", i, " last error index", w.lastError)
|
||||||
|
if w.forceRefresh || refresh || w.lastError == i {
|
||||||
|
var err *utils.Error
|
||||||
|
if w.serial {
|
||||||
|
err = listener.Refresh()
|
||||||
|
} else {
|
||||||
|
err = w.notifyInProcess(listener)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
w.lastError = i
|
||||||
|
w.forceRefresh = true
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
w.lastError = -1
|
||||||
|
w.forceRefresh = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a queue for refresh notifications
|
||||||
|
// this will not return until one of the queue completes
|
||||||
|
func (w *Watcher) notifyInProcess(listener Listener) (err *utils.Error) {
|
||||||
|
shouldReturn := false
|
||||||
|
// This code block ensures that either a timer is created
|
||||||
|
// or that a process would be added the the h.refreshChannel
|
||||||
|
func() {
|
||||||
|
w.timerMutex.Lock()
|
||||||
|
defer w.timerMutex.Unlock()
|
||||||
|
// If we are in the process of a rebuild, forceRefresh will always be true
|
||||||
|
w.forceRefresh = true
|
||||||
|
if w.refreshTimer != nil {
|
||||||
|
utils.Logger.Info("Found existing timer running, resetting")
|
||||||
|
w.refreshTimer.Reset(time.Millisecond * w.refreshTimerMS)
|
||||||
|
shouldReturn = true
|
||||||
|
w.refreshChannelCount++
|
||||||
|
} else {
|
||||||
|
w.refreshTimer = time.NewTimer(time.Millisecond * w.refreshTimerMS)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// If another process is already waiting for the timer this one
|
||||||
|
// only needs to return the output from the channel
|
||||||
|
if shouldReturn {
|
||||||
|
return <-w.refreshChannel
|
||||||
|
}
|
||||||
|
utils.Logger.Info("Waiting for refresh timer to expire")
|
||||||
|
<-w.refreshTimer.C
|
||||||
|
w.timerMutex.Lock()
|
||||||
|
|
||||||
|
// Ensure the queue is properly dispatched even if a panic occurs
|
||||||
|
defer func() {
|
||||||
|
for x := 0; x < w.refreshChannelCount; x++ {
|
||||||
|
w.refreshChannel <- err
|
||||||
|
}
|
||||||
|
w.refreshChannelCount = 0
|
||||||
|
w.refreshTimer = nil
|
||||||
|
w.timerMutex.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = listener.Refresh()
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Info("Watcher: Recording error last build, setting rebuild on", "error", err)
|
||||||
|
} else {
|
||||||
|
w.lastError = -1
|
||||||
|
w.forceRefresh = false
|
||||||
|
}
|
||||||
|
utils.Logger.Info("Rebuilt, result", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) rebuildRequired(ev fsnotify.Event, listener Listener) bool {
|
||||||
|
// Ignore changes to dotfiles.
|
||||||
|
if strings.HasPrefix(filepath.Base(ev.Name), ".") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if dl, ok := listener.(DiscerningListener); ok {
|
||||||
|
if !dl.WatchFile(ev.Name) || ev.Op&fsnotify.Chmod == fsnotify.Chmod {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
var WatchFilter = func(c *Controller, fc []Filter) {
|
||||||
|
if MainWatcher != nil {
|
||||||
|
err := MainWatcher.Notify()
|
||||||
|
if err != nil {
|
||||||
|
c.Result = c.RenderError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fc[0](c, fc[1:])
|
||||||
|
}
|
||||||
|
*/
|
||||||
Reference in New Issue
Block a user