mirror of
https://github.com/kevin-DL/revel-cmd.git
synced 2026-01-11 18:54:31 +00:00
Enhancements to Revel command
Reformat of code Allow user to use a mix of command line arguments and flags Enhance the import tool to detect missing packages in the modules side Added test cases for all commands
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"GOLANG": {
|
||||
"ABC":[15, 25, 50, 70],
|
||||
"ABC":[25, 35, 50, 70],
|
||||
"BLOCK_NESTING":[5, 6, 7, 8],
|
||||
"CYCLO":[20, 30, 45, 60],
|
||||
"TOO_MANY_IVARS": [15, 18, 20, 25],
|
||||
|
||||
47
.travis.yml
47
.travis.yml
@@ -1,15 +1,15 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.8"
|
||||
- "1.8.7"
|
||||
- "1.9"
|
||||
- "1.10"
|
||||
- "1.11"
|
||||
- "tip"
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
- linux
|
||||
- windows
|
||||
|
||||
sudo: false
|
||||
@@ -19,19 +19,6 @@ branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
services:
|
||||
- memcache
|
||||
- redis-server
|
||||
|
||||
before_install:
|
||||
# TRAVIS_OS_NAME - linux and osx
|
||||
- echo $TRAVIS_OS_NAME
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
brew update && brew install memcached redis && brew services start redis && brew services start memcached
|
||||
fi
|
||||
- redis-server --daemonize yes
|
||||
- redis-cli info
|
||||
|
||||
install:
|
||||
# Setting environments variables
|
||||
@@ -39,17 +26,13 @@ install:
|
||||
- export REVEL_BRANCH="develop"
|
||||
- 'if [[ "$TRAVIS_BRANCH" == "master" ]]; then export REVEL_BRANCH="master"; fi'
|
||||
- 'echo "Travis branch: $TRAVIS_BRANCH, Revel dependency branch: $REVEL_BRANCH"'
|
||||
- git clone -b $REVEL_BRANCH git://github.com/revel/modules ../modules/
|
||||
- git clone -b $REVEL_BRANCH git://github.com/revel/revel ../revel/
|
||||
- git clone -b $REVEL_BRANCH git://github.com/revel/config ../config/
|
||||
- git clone -b $REVEL_BRANCH git://github.com/revel/cron ../cron/
|
||||
- git clone -b $REVEL_BRANCH git://github.com/revel/examples ../examples/
|
||||
- go get -v github.com/revel/revel/...
|
||||
- go get -v github.com/revel/cmd/revel
|
||||
- go get -t -v github.com/revel/cmd/revel
|
||||
- go get -u github.com/golang/dep/cmd/dep
|
||||
|
||||
- echo $GOPATH
|
||||
- echo $PATH
|
||||
- pwd
|
||||
script:
|
||||
- go test -v github.com/revel/cmd/...
|
||||
- go test -v github.com/revel/cmd/revel/...
|
||||
|
||||
# Ensure the new-app flow works (plus the other commands).
|
||||
- revel version
|
||||
@@ -65,21 +48,19 @@ script:
|
||||
- revel new -a my/testapp2
|
||||
- revel test -a my/testapp2
|
||||
- revel clean -a my/testapp2
|
||||
- revel build -a my/testapp2 -t build/testapp
|
||||
- revel build -a my/testapp2 -t build/testapp -m prod
|
||||
- revel build -a my/testapp2 -t build/testapp2
|
||||
- revel build -a my/testapp2 -t build/testapp2 -m prod
|
||||
- revel package -a my/testapp2
|
||||
- revel package -a my/testapp2 -m prod
|
||||
|
||||
- revel new -a my/testapp3 -V
|
||||
- revel test -a my/testapp3
|
||||
- revel clean -a my/testapp3
|
||||
- revel build -a my/testapp3 -t build/testapp
|
||||
- revel build -a my/testapp3 -t build/testapp -m prod
|
||||
- revel new -v -a my/testapp3 -V
|
||||
- revel test -v -a my/testapp3
|
||||
- revel clean -v -a my/testapp3
|
||||
- revel build -a my/testapp3 -t build/testapp3
|
||||
- revel build -a my/testapp3 -t build/testapp3 -m prod
|
||||
- revel package -a my/testapp3
|
||||
- revel package -a my/testapp3 -m prod
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
- go: 1.6
|
||||
os: osx
|
||||
|
||||
@@ -61,28 +61,30 @@ func NewAppCmd(binPath string, port int, runMode string, paths *model.RevelConta
|
||||
|
||||
// Start the app server, and wait until it is ready to serve requests.
|
||||
func (cmd AppCmd) Start(c *model.CommandConfig) error {
|
||||
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool),c}
|
||||
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool), c}
|
||||
cmd.Stdout = listeningWriter
|
||||
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
||||
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args, "dir", cmd.Dir, "env", cmd.Env)
|
||||
utils.CmdInit(cmd.Cmd, c.AppPath)
|
||||
if err := cmd.Cmd.Start(); err != nil {
|
||||
utils.Logger.Fatal("Error running:", "error", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case exitState := <-cmd.waitChan():
|
||||
println("Revel proxy is listening, point your browser to :", c.Run.Port)
|
||||
return errors.New("revel/harness: app died reason: " + exitState)
|
||||
|
||||
case <-time.After(60 * time.Second):
|
||||
println("Revel proxy is listening, point your browser to :", c.Run.Port)
|
||||
utils.Logger.Error("Killing revel server process did not respond after wait timeout.", "processid", cmd.Process.Pid)
|
||||
cmd.Kill()
|
||||
return errors.New("revel/harness: app timed out")
|
||||
|
||||
case <-listeningWriter.notifyReady:
|
||||
println("Revel proxy is listening, point your browser to :", c.Run.Port)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO remove this unreachable code and document it
|
||||
panic("Impossible")
|
||||
}
|
||||
|
||||
// Run the app server inline. Never returns.
|
||||
@@ -111,7 +113,7 @@ func (cmd AppCmd) waitChan() <-chan string {
|
||||
_ = cmd.Wait()
|
||||
state := cmd.ProcessState
|
||||
exitStatus := " unknown "
|
||||
if state!=nil {
|
||||
if state != nil {
|
||||
exitStatus = state.String()
|
||||
}
|
||||
|
||||
@@ -126,7 +128,7 @@ func (cmd AppCmd) waitChan() <-chan string {
|
||||
type startupListeningWriter struct {
|
||||
dest io.Writer
|
||||
notifyReady chan bool
|
||||
c *model.CommandConfig
|
||||
c *model.CommandConfig
|
||||
}
|
||||
|
||||
func (w *startupListeningWriter) Write(p []byte) (int, error) {
|
||||
|
||||
@@ -36,12 +36,12 @@ func (c ByString) Less(i, j int) bool { return c[i].String() < c[j].String() }
|
||||
// 2. Run the appropriate "go build" command.
|
||||
// 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.
|
||||
func Build(c *model.CommandConfig, paths *model.RevelContainer) (app *App, compileError *utils.Error) {
|
||||
func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err error) {
|
||||
// First, clear the generated files (to avoid them messing with ProcessSource).
|
||||
cleanSource(paths, "tmp", "routes")
|
||||
|
||||
sourceInfo, compileError := parser.ProcessSource(paths)
|
||||
if compileError != nil {
|
||||
sourceInfo, err := parser.ProcessSource(paths)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -68,9 +68,15 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (app *App, compi
|
||||
// without being the main thread
|
||||
cleanSource(paths, "tmp", "routes")
|
||||
|
||||
genSource(paths, "tmp", "main.go", RevelMainTemplate, templateArgs)
|
||||
genSource(paths, filepath.Join("tmp", "run"), "run.go", RevelRunTemplate, templateArgs)
|
||||
genSource(paths, "routes", "routes.go", RevelRoutesTemplate, templateArgs)
|
||||
if err = genSource(paths, "tmp", "main.go", RevelMainTemplate, templateArgs); err != nil {
|
||||
return
|
||||
}
|
||||
if err = genSource(paths, filepath.Join("tmp", "run"), "run.go", RevelRunTemplate, templateArgs); err != nil {
|
||||
return
|
||||
}
|
||||
if err = genSource(paths, "routes", "routes.go", RevelRoutesTemplate, templateArgs); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Read build config.
|
||||
buildTags := paths.Config.StringDefault("build.tags", "")
|
||||
@@ -102,23 +108,9 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (app *App, compi
|
||||
}
|
||||
}
|
||||
|
||||
var depPath string
|
||||
if useVendor {
|
||||
utils.Logger.Info("Vendor folder detected, scanning for deps in path")
|
||||
depPath, err = exec.LookPath("dep")
|
||||
if err != nil {
|
||||
// Do not halt build unless a new package needs to be imported
|
||||
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. " +
|
||||
"You can install the `dep` tool by doing a `go get -u github.com/golang/dep/cmd/dep`")
|
||||
}
|
||||
} else {
|
||||
utils.Logger.Info("No vendor folder detected, not using dependency manager to import files")
|
||||
}
|
||||
|
||||
pkg, err := build.Default.Import(paths.ImportPath, "", build.FindOnly)
|
||||
if err != nil {
|
||||
utils.Logger.Fatal("Failure importing", "path", paths.ImportPath)
|
||||
return
|
||||
}
|
||||
|
||||
// Binary path is a combination of $GOBIN/revel.d directory, app's import path and its name.
|
||||
@@ -191,6 +183,7 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (app *App, compi
|
||||
buildCmd.Env = append(os.Environ(),
|
||||
"GOPATH="+gopath,
|
||||
)
|
||||
utils.CmdInit(buildCmd, c.AppPath)
|
||||
utils.Logger.Info("Exec:", "args", buildCmd.Args)
|
||||
output, err := buildCmd.CombinedOutput()
|
||||
|
||||
@@ -220,32 +213,9 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (app *App, compi
|
||||
return nil, newCompileError(paths, output)
|
||||
}
|
||||
gotten[pkgName] = struct{}{}
|
||||
|
||||
// Execute "go get <pkg>"
|
||||
// Or dep `dep ensure -add <pkg>` if it is there
|
||||
var getCmd *exec.Cmd
|
||||
if useVendor {
|
||||
if depPath == "" {
|
||||
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, " +
|
||||
"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 _, pkg := range matches {
|
||||
utils.Logger.Warn("Missing package", "package", pkg[1])
|
||||
}
|
||||
}
|
||||
getCmd = exec.Command(depPath, "ensure", "-add", pkgName)
|
||||
getCmd.Dir = paths.AppPath
|
||||
|
||||
} else {
|
||||
getCmd = exec.Command(goPath, "get", pkgName)
|
||||
}
|
||||
utils.Logger.Info("Exec:", "args", getCmd.Args)
|
||||
getOutput, err := getCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
utils.Logger.Error("Build failed", "message", stOutput, "error", err)
|
||||
utils.Logger.Error("Failed to fetch the output", "getOutput", string(getOutput))
|
||||
return nil, newCompileError(paths, output)
|
||||
if err := c.PackageResolver(pkgName); err != nil {
|
||||
utils.Logger.Error("Unable to resolve package", "package", pkgName, "error", err)
|
||||
return nil, newCompileError(paths, []byte(err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,12 +306,9 @@ func cleanDir(paths *model.RevelContainer, dir string) {
|
||||
|
||||
// genSource renders the given template to produce source code, which it writes
|
||||
// to the given directory and file.
|
||||
func genSource(paths *model.RevelContainer, dir, filename, templateSource string, args map[string]interface{}) {
|
||||
func genSource(paths *model.RevelContainer, dir, filename, templateSource string, args map[string]interface{}) error {
|
||||
|
||||
err := utils.MustGenerateTemplate(filepath.Join(paths.AppPath, dir, filename), templateSource, args)
|
||||
if err != nil {
|
||||
utils.Logger.Fatal("Failed to generate template for source file", "error", err)
|
||||
}
|
||||
return utils.GenerateTemplate(filepath.Join(paths.AppPath, dir, filename), templateSource, args)
|
||||
}
|
||||
|
||||
// Looks through all the method args and returns a set of unique import paths
|
||||
@@ -433,7 +400,8 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error {
|
||||
// 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)
|
||||
newPath := filepath.Join(gp,"src", paths.ImportPath, relFilename)
|
||||
println(newPath)
|
||||
if utils.Exists(newPath) {
|
||||
return newPath
|
||||
}
|
||||
@@ -443,6 +411,7 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error {
|
||||
return newPath
|
||||
}
|
||||
|
||||
|
||||
// Read the source for the offending file.
|
||||
var (
|
||||
relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go"
|
||||
@@ -467,7 +436,7 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error {
|
||||
fileStr, err := utils.ReadLines(absFilename)
|
||||
if err != nil {
|
||||
compileError.MetaError = absFilename + ": " + err.Error()
|
||||
utils.Logger.Error("Unable to readlines "+compileError.MetaError, "error", err)
|
||||
utils.Logger.Info("Unable to readlines "+compileError.MetaError, "error", err)
|
||||
return compileError
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,9 @@ import (
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/utils"
|
||||
"github.com/revel/cmd/watcher"
|
||||
"sync"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -63,31 +63,31 @@ func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err erro
|
||||
// 1) Application/views/errors
|
||||
// 2) revel_home/views/errors
|
||||
// 3) views/errors
|
||||
if err==nil {
|
||||
if err == nil {
|
||||
utils.Logger.Panic("Caller passed in a nil error")
|
||||
}
|
||||
templateSet := template.New("__root__")
|
||||
seekViewOnPath:=func(view string) (path string) {
|
||||
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)
|
||||
}
|
||||
|
||||
data,err := ioutil.ReadFile(path)
|
||||
if err!=nil {
|
||||
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 {
|
||||
_, 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")}
|
||||
target := []string{seekViewOnPath("500.html"), seekViewOnPath("500-dev.html")}
|
||||
if !utils.Exists(target[0]) {
|
||||
fmt.Fprintf(iw, "Target template not found not found %s<br />\n", target[0])
|
||||
fmt.Fprintf(iw, "An error ocurred %s", err.Error())
|
||||
return
|
||||
return
|
||||
}
|
||||
var revelError *utils.Error
|
||||
switch e := err.(type) {
|
||||
@@ -108,16 +108,11 @@ func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err erro
|
||||
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)
|
||||
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.
|
||||
@@ -192,7 +187,6 @@ func NewHarness(c *model.CommandConfig, paths *model.RevelContainer, runMode str
|
||||
useProxy: !noProxy,
|
||||
config: c,
|
||||
runMode: runMode,
|
||||
|
||||
}
|
||||
|
||||
if paths.HTTPSsl {
|
||||
@@ -219,9 +213,17 @@ func (h *Harness) Refresh() (err *utils.Error) {
|
||||
}
|
||||
|
||||
utils.Logger.Info("Rebuild Called")
|
||||
h.app, err = Build(h.config, h.paths)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Build detected an error", "error", err)
|
||||
var newErr error
|
||||
h.app, newErr = Build(h.config, h.paths)
|
||||
if newErr != nil {
|
||||
utils.Logger.Error("Build detected an error", "error", newErr)
|
||||
if castErr, ok := newErr.(*utils.Error); ok {
|
||||
return castErr
|
||||
}
|
||||
err = &utils.Error{
|
||||
Title: "App failed to start up",
|
||||
Description: err.Error(),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -269,7 +271,7 @@ func (h *Harness) Run() {
|
||||
if h.useProxy {
|
||||
go func() {
|
||||
// Check the port to start on a random port
|
||||
if h.paths.HTTPPort==0 {
|
||||
if h.paths.HTTPPort == 0 {
|
||||
h.paths.HTTPPort = getFreePort()
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/*
|
||||
Package logger contains filters and handles for the logging utilities in Revel.
|
||||
These facilities all currently use the logging library called log15 at
|
||||
@@ -7,5 +6,5 @@
|
||||
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
|
||||
|
||||
@@ -121,6 +121,7 @@ func SetDefaultLog(fromLog MultiLogger) {
|
||||
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...))
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"gopkg.in/stack.v0"
|
||||
"github.com/revel/config"
|
||||
"github.com/revel/log15"
|
||||
"gopkg.in/stack.v0"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -43,15 +43,14 @@ func GetLogger(name string, logger MultiLogger) (l *log.Logger) {
|
||||
// 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 config != nil && config.BoolDefault("testModeFlag", false) {
|
||||
config.SetOption("log.info.output", "off")
|
||||
config.SetOption("log.debug.output", "off")
|
||||
config.SetOption("log.warn.output", "off")
|
||||
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()
|
||||
|
||||
|
||||
@@ -2,11 +2,14 @@ package model
|
||||
|
||||
// The constants
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/revel/cmd/logger"
|
||||
"github.com/revel/cmd/utils"
|
||||
"go/build"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"github.com/revel/cmd/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -25,58 +28,58 @@ type (
|
||||
|
||||
// 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"`
|
||||
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 (relative to a GOPATH)
|
||||
GoPath string // The GoPath
|
||||
GoCmd string // The full path to the go executable
|
||||
SrcRoot string // The source root
|
||||
AppPath string // The application path (absolute)
|
||||
AppName string // The application name
|
||||
PackageResolver func(pkgName string) error // a packge resolver for the config
|
||||
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"`
|
||||
ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"`
|
||||
SkeletonPath 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"`
|
||||
TargetPath string `short:"t" long:"target-path" description:"Path to target folder. Folder will be completely deleted if it exists" required:"false"`
|
||||
ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"`
|
||||
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
|
||||
CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"`
|
||||
} `command:"build"`
|
||||
// The run command
|
||||
Run struct {
|
||||
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
|
||||
ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"`
|
||||
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"`
|
||||
Port int `short:"p" long:"port" default:"-1" 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 {
|
||||
TargetPath string `short:"t" long:"target-path" description:"Full path and filename of target package to deploy" required:"false"`
|
||||
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"`
|
||||
ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"`
|
||||
CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"`
|
||||
} `command:"package"`
|
||||
// The clean command
|
||||
Clean struct {
|
||||
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
|
||||
ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"`
|
||||
} `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"`
|
||||
ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"`
|
||||
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"`
|
||||
ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"`
|
||||
} `command:"version"`
|
||||
}
|
||||
)
|
||||
@@ -115,13 +118,14 @@ func (c *CommandConfig) UpdateImportPath() bool {
|
||||
currentPath, _ = filepath.Abs(importPath)
|
||||
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
for _, path := range strings.Split(build.Default.GOPATH, string(filepath.ListSeparator)) {
|
||||
utils.Logger.Infof("Checking import path %s with %s", currentPath, path)
|
||||
if strings.HasPrefix(currentPath, path) {
|
||||
importPath = currentPath[len(path) + 1:]
|
||||
if strings.HasPrefix(currentPath, path) && len(currentPath) > len(path)+1 {
|
||||
importPath = currentPath[len(path)+1:]
|
||||
// Remove the source from the path if it is there
|
||||
if len(importPath)>4 && strings.ToLower(importPath[0:4]) == "src/" {
|
||||
if len(importPath) > 4 && strings.ToLower(importPath[0:4]) == "src/" {
|
||||
importPath = importPath[4:]
|
||||
} else if importPath == "src" {
|
||||
importPath = ""
|
||||
@@ -133,6 +137,122 @@ func (c *CommandConfig) UpdateImportPath() bool {
|
||||
}
|
||||
|
||||
c.ImportPath = importPath
|
||||
utils.Logger.Info("Returned import path", "path", importPath, "buildpath",build.Default.GOPATH)
|
||||
utils.Logger.Info("Returned import path", "path", importPath, "buildpath", build.Default.GOPATH)
|
||||
return (len(importPath) > 0 || !required)
|
||||
}
|
||||
|
||||
// Used to initialize the package resolver
|
||||
func (c *CommandConfig) InitPackageResolver() {
|
||||
useVendor := utils.DirExists(filepath.Join(c.AppPath, "vendor"))
|
||||
if c.Index == NEW && c.New.Vendored {
|
||||
useVendor = true
|
||||
}
|
||||
utils.Logger.Info("InitPackageResolver", "useVendor", useVendor, "path", c.AppPath)
|
||||
|
||||
var (
|
||||
depPath string
|
||||
err error
|
||||
)
|
||||
|
||||
if useVendor {
|
||||
utils.Logger.Info("Vendor folder detected, scanning for deps in path")
|
||||
depPath, err = exec.LookPath("dep")
|
||||
if err != nil {
|
||||
// Do not halt build unless a new package needs to be imported
|
||||
utils.Logger.Fatal("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. " +
|
||||
"You can install the `dep` tool by doing a `go get -u github.com/golang/dep/cmd/dep`")
|
||||
}
|
||||
}
|
||||
|
||||
// This should get called when needed
|
||||
c.PackageResolver = func(pkgName string) error {
|
||||
//useVendor := utils.DirExists(filepath.Join(c.AppPath, "vendor"))
|
||||
|
||||
var getCmd *exec.Cmd
|
||||
utils.Logger.Info("Request for package ", "package", pkgName, "use vendor", useVendor)
|
||||
if useVendor {
|
||||
utils.Logger.Info("Using dependency manager to import package", "package", pkgName)
|
||||
|
||||
if depPath == "" {
|
||||
utils.Logger.Error("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, " +
|
||||
"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")
|
||||
utils.Logger.Error("Missing package", "package", pkgName)
|
||||
return fmt.Errorf("Missing package %s", pkgName)
|
||||
}
|
||||
getCmd = exec.Command(depPath, "ensure", "-add", pkgName)
|
||||
} else {
|
||||
utils.Logger.Info("No vendor folder detected, not using dependency manager to import package", "package", pkgName)
|
||||
getCmd = exec.Command(c.GoCmd, "get", pkgName)
|
||||
}
|
||||
|
||||
utils.CmdInit(getCmd, c.AppPath)
|
||||
utils.Logger.Info("Go get command ", "exec", getCmd.Path, "dir", getCmd.Dir, "args", getCmd.Args, "env", getCmd.Env, "package", pkgName)
|
||||
output, err := getCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("Error stack %v\n", logger.NewCallStack())
|
||||
utils.Logger.Error("Failed to import package", "error", err, "gopath", build.Default.GOPATH, "GO-ROOT", build.Default.GOROOT, "output", string(output))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// lookup and set Go related variables
|
||||
func (c *CommandConfig) InitGoPaths() {
|
||||
// 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
|
||||
|
||||
// What we want to do is to add the import to the end of the
|
||||
// gopath, and discover which import exists - If none exist this is an error except in the case
|
||||
// where we are dealing with new which is a special case where we will attempt to target the working directory first
|
||||
workingDir, _ := os.Getwd()
|
||||
goPathList := filepath.SplitList(c.GoPath)
|
||||
bestpath := ""
|
||||
for _, path := range goPathList {
|
||||
if c.Index == NEW {
|
||||
// If the GOPATH is part of the working dir this is the most likely target
|
||||
if strings.HasPrefix(workingDir, path) {
|
||||
bestpath = path
|
||||
}
|
||||
} else {
|
||||
if utils.Exists(filepath.Join(path, "src", c.ImportPath)) {
|
||||
c.SrcRoot = path
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.SrcRoot)==0 && len(bestpath) > 0 {
|
||||
c.SrcRoot = bestpath
|
||||
}
|
||||
utils.Logger.Info("Source root", "path", c.SrcRoot, "cwd", workingDir, "gopath", c.GoPath)
|
||||
|
||||
// If source root is empty and this isn't a version then skip it
|
||||
if len(c.SrcRoot) == 0 {
|
||||
if c.Index != VERSION {
|
||||
utils.Logger.Fatal("Abort: could not create a Revel application outside of GOPATH.")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// set go src path
|
||||
c.SrcRoot = filepath.Join(c.SrcRoot, "src")
|
||||
|
||||
c.AppPath = filepath.Join(c.SrcRoot, filepath.FromSlash(c.ImportPath))
|
||||
utils.Logger.Info("Set application path", "path", c.AppPath)
|
||||
}
|
||||
|
||||
@@ -8,4 +8,4 @@ type EmbeddedTypeName struct {
|
||||
// Convert the type to a properly formatted import line
|
||||
func (s *EmbeddedTypeName) String() string {
|
||||
return s.ImportPath + "." + s.StructName
|
||||
}
|
||||
}
|
||||
|
||||
62
model/event.go
Normal file
62
model/event.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package model
|
||||
|
||||
type (
|
||||
// The event type
|
||||
Event int
|
||||
// The event response
|
||||
EventResponse int
|
||||
// The handler signature
|
||||
EventHandler func(typeOf Event, value interface{}) (responseOf EventResponse)
|
||||
RevelCallback interface {
|
||||
FireEvent(key Event, value interface{}) (response EventResponse)
|
||||
PackageResolver(pkgName string) error
|
||||
}
|
||||
)
|
||||
|
||||
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 Event = 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 before module loads, events thrown to handlers added to AddInitEventHandler
|
||||
REVEL_BEFORE_MODULE_LOADED
|
||||
// Event type after module loads, events thrown to handlers added to AddInitEventHandler
|
||||
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
|
||||
|
||||
// Fired when a panic is caught during the startup process
|
||||
REVEL_FAILURE
|
||||
)
|
||||
|
||||
var initEventList = []EventHandler{} // Event handler list for receiving events
|
||||
|
||||
// Fires system events from revel
|
||||
func RaiseEvent(key Event, value interface{}) (response EventResponse) {
|
||||
for _, handler := range initEventList {
|
||||
response |= handler(key, value)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add event handler to listen for all system events
|
||||
func AddInitEventHandler(handler EventHandler) {
|
||||
initEventList = append(initEventList, handler)
|
||||
return
|
||||
}
|
||||
24
model/event_test.go
Normal file
24
model/event_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package model_test
|
||||
|
||||
import (
|
||||
"github.com/revel/revel"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test that the event handler can be attached and it dispatches the event received
|
||||
func TestEventHandler(t *testing.T) {
|
||||
counter := 0
|
||||
newListener := func(typeOf revel.Event, value interface{}) (responseOf revel.EventResponse) {
|
||||
if typeOf == revel.REVEL_FAILURE {
|
||||
counter++
|
||||
}
|
||||
return
|
||||
}
|
||||
// Attach the same handlder twice so we expect to see the response twice as well
|
||||
revel.AddInitEventHandler(newListener)
|
||||
revel.AddInitEventHandler(newListener)
|
||||
revel.RaiseEvent(revel.REVEL_AFTER_MODULES_LOADED, nil)
|
||||
revel.RaiseEvent(revel.REVEL_FAILURE, nil)
|
||||
assert.Equal(t, counter, 2, "Expected event handler to have been called")
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
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 {
|
||||
@@ -22,4 +21,3 @@ type MethodArg struct {
|
||||
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.
|
||||
}
|
||||
|
||||
|
||||
@@ -6,92 +6,96 @@ import (
|
||||
"github.com/revel/config"
|
||||
"go/build"
|
||||
|
||||
"os"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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
|
||||
BuildPaths struct {
|
||||
Revel string
|
||||
}
|
||||
Paths struct {
|
||||
Import string
|
||||
Source string
|
||||
Base string
|
||||
App string
|
||||
Views string
|
||||
Code []string
|
||||
Template []string
|
||||
Config []string
|
||||
}
|
||||
PackageInfo struct {
|
||||
Config config.Context
|
||||
Packaged bool
|
||||
DevMode bool
|
||||
Vendor bool
|
||||
}
|
||||
Application struct {
|
||||
Name string
|
||||
Root string
|
||||
}
|
||||
|
||||
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)
|
||||
WrappedRevelCallback struct {
|
||||
FireEventFunction func(key Event, value interface{}) (response EventResponse)
|
||||
ImportFunction func(pkgName string) error
|
||||
}
|
||||
doNothingRevelCallback struct {
|
||||
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
// Simple callback to pass to the RevelCallback that does nothing
|
||||
var DoNothingRevelCallback = RevelCallback(&doNothingRevelCallback{})
|
||||
// Simple Wrapped RevelCallback
|
||||
func NewWrappedRevelCallback(fe func(key Event, value interface{}) (response EventResponse), ie func(pkgName string) error) RevelCallback {
|
||||
return &WrappedRevelCallback{fe, ie}
|
||||
}
|
||||
|
||||
func (_ *doNothingRevelCallback) FireEvent(key int, value interface{}) (response int) {
|
||||
// Function to implement the FireEvent
|
||||
func (w *WrappedRevelCallback) FireEvent(key Event, value interface{}) (response EventResponse) {
|
||||
if w.FireEventFunction != nil {
|
||||
response = w.FireEventFunction(key, value)
|
||||
}
|
||||
return
|
||||
}
|
||||
func (w *WrappedRevelCallback) PackageResolver(pkgName string) error {
|
||||
return w.ImportFunction(pkgName)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp *RevelContainer, err error) {
|
||||
rp = &RevelContainer{ModulePathMap: map[string]string{}}
|
||||
// Ignore trailing slashes.
|
||||
rp.ImportPath = strings.TrimRight(importPath, "/")
|
||||
@@ -101,17 +105,20 @@ func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp
|
||||
// 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)
|
||||
rp.SourcePath, revelSourcePath, err = utils.FindSrcPaths(importPath, RevelImportPath, callback.PackageResolver)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} 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.PackageInfo.Vendor = utils.Exists(filepath.Join(rp.BasePath, "vendor"))
|
||||
rp.AppPath = filepath.Join(rp.BasePath, "app")
|
||||
rp.ViewsPath = filepath.Join(rp.AppPath, "views")
|
||||
|
||||
@@ -133,11 +140,9 @@ func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp
|
||||
},
|
||||
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)
|
||||
return rp, fmt.Errorf("Unable to load configuartion file %s", err)
|
||||
}
|
||||
|
||||
// Ensure that the selected runmode appears in app.conf.
|
||||
@@ -146,7 +151,7 @@ func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp
|
||||
mode = config.DefaultSection
|
||||
}
|
||||
if !rp.Config.HasSection(mode) {
|
||||
utils.Logger.Fatal("app.conf: No mode found:","run-mode", mode)
|
||||
return rp, fmt.Errorf("app.conf: No mode found: %s %s", "run-mode", mode)
|
||||
}
|
||||
rp.Config.SetSection(mode)
|
||||
|
||||
@@ -159,10 +164,10 @@ func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp
|
||||
rp.HTTPSslKey = rp.Config.StringDefault("http.sslkey", "")
|
||||
if rp.HTTPSsl {
|
||||
if rp.HTTPSslCert == "" {
|
||||
utils.Logger.Fatal("No http.sslcert provided.")
|
||||
return rp, errors.New("No http.sslcert provided.")
|
||||
}
|
||||
if rp.HTTPSslKey == "" {
|
||||
utils.Logger.Fatal("No http.sslkey provided.")
|
||||
return rp, errors.New("No http.sslkey provided.")
|
||||
}
|
||||
}
|
||||
//
|
||||
@@ -173,21 +178,23 @@ func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp
|
||||
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)
|
||||
if err := rp.loadModules(callback); err != nil {
|
||||
return rp, err
|
||||
}
|
||||
|
||||
callback.FireEvent(REVEL_AFTER_MODULES_LOADED, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// LoadMimeConfig load mime-types.conf on init.
|
||||
func (rp *RevelContainer) LoadMimeConfig() {
|
||||
var err error
|
||||
func (rp *RevelContainer) LoadMimeConfig() (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)
|
||||
return fmt.Errorf("Failed to load mime type config: %s %s", "error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Loads modules based on the configuration setup.
|
||||
@@ -195,7 +202,7 @@ func (rp *RevelContainer) LoadMimeConfig() {
|
||||
// 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) {
|
||||
func (rp *RevelContainer) loadModules(callback RevelCallback) (err error) {
|
||||
keys := []string{}
|
||||
for _, key := range rp.Config.Options("module.") {
|
||||
keys = append(keys, key)
|
||||
@@ -211,7 +218,12 @@ func (rp *RevelContainer) loadModules(callback RevelCallback) {
|
||||
|
||||
modulePath, err := rp.ResolveImportPath(moduleImportPath)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Failed to load module. Import of path failed", "modulePath", moduleImportPath, "error", err)
|
||||
utils.Logger.Info("Missing module ", "module", moduleImportPath, "error",err)
|
||||
callback.PackageResolver(moduleImportPath)
|
||||
modulePath, err = rp.ResolveImportPath(moduleImportPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load module. Import of path failed %s:%s %s:%s ", "modulePath", moduleImportPath, "error", err)
|
||||
}
|
||||
}
|
||||
// Drop anything between module.???.<name of module>
|
||||
name := key[len("module."):]
|
||||
@@ -222,6 +234,7 @@ func (rp *RevelContainer) loadModules(callback RevelCallback) {
|
||||
rp.addModulePaths(name, moduleImportPath, modulePath)
|
||||
callback.FireEvent(REVEL_AFTER_MODULE_LOADED, []interface{}{rp, name, moduleImportPath, modulePath})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Adds a module paths to the container object
|
||||
@@ -252,43 +265,12 @@ func (rp *RevelContainer) ResolveImportPath(importPath string) (string, error) {
|
||||
return filepath.Join(rp.SourcePath, importPath), nil
|
||||
}
|
||||
|
||||
modPkg, err := build.Import(importPath, rp.RevelPath, build.FindOnly)
|
||||
modPkg, err := build.Import(importPath, rp.AppPath, build.FindOnly)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if rp.PackageInfo.Vendor && !strings.HasPrefix(modPkg.Dir,rp.BasePath) {
|
||||
return "", fmt.Errorf("Module %s was found outside of path %s.",importPath, modPkg.Dir)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ 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
|
||||
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.
|
||||
}
|
||||
|
||||
@@ -13,4 +13,3 @@ type TypeInfo struct {
|
||||
func (s *TypeInfo) String() string {
|
||||
return s.ImportPath + "." + s.StructName
|
||||
}
|
||||
|
||||
|
||||
223
parser/appends.go
Normal file
223
parser/appends.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"github.com/revel/cmd/utils"
|
||||
"github.com/revel/cmd/model"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
// If this Decl is a struct type definition, it is summarized and added to specs.
|
||||
// Else, specs is returned unchanged.
|
||||
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.
|
||||
spec, found := getStructTypeDecl(decl, fset)
|
||||
if !found {
|
||||
return specs
|
||||
}
|
||||
|
||||
structType := spec.Type.(*ast.StructType)
|
||||
|
||||
// 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.
|
||||
// Add it provisionally to the Controller list -- it's later filtered using field info.
|
||||
controllerSpec := &model.TypeInfo{
|
||||
StructName: spec.Name.Name,
|
||||
ImportPath: pkgImportPath,
|
||||
PackageName: pkg.Name,
|
||||
}
|
||||
|
||||
for _, field := range structType.Fields.List {
|
||||
// If field.Names is set, it's not an embedded type.
|
||||
if field.Names != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// A direct "sub-type" has an ast.Field as either:
|
||||
// Ident { "AppController" }
|
||||
// SelectorExpr { "rev", "Controller" }
|
||||
// Additionally, that can be wrapped by StarExprs.
|
||||
fieldType := field.Type
|
||||
pkgName, typeName := func() (string, string) {
|
||||
// Drill through any StarExprs.
|
||||
for {
|
||||
if starExpr, ok := fieldType.(*ast.StarExpr); ok {
|
||||
fieldType = starExpr.X
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// If the embedded type is in the same package, it's an Ident.
|
||||
if ident, ok := fieldType.(*ast.Ident); ok {
|
||||
return "", ident.Name
|
||||
}
|
||||
|
||||
if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
|
||||
if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
|
||||
return pkgIdent.Name, selectorExpr.Sel.Name
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}()
|
||||
|
||||
// If a typename wasn't found, skip it.
|
||||
if typeName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the import path for this type.
|
||||
// If it was referenced without a package name, use the current package import path.
|
||||
// Else, look up the package's import path by name.
|
||||
var importPath string
|
||||
if pkgName == "" {
|
||||
importPath = pkgImportPath
|
||||
} else {
|
||||
var ok bool
|
||||
if importPath, ok = imports[pkgName]; !ok {
|
||||
utils.Logger.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{
|
||||
ImportPath: importPath,
|
||||
StructName: typeName,
|
||||
})
|
||||
}
|
||||
|
||||
return append(specs, controllerSpec)
|
||||
}
|
||||
|
||||
// If decl is a Method declaration, it is summarized and added to the array
|
||||
// underneath its receiver type.
|
||||
// e.g. "Login" => {MethodSpec, MethodSpec, ..}
|
||||
func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPath, pkgName string, imports map[string]string) {
|
||||
// Func declaration?
|
||||
funcDecl, ok := decl.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Have a receiver?
|
||||
if funcDecl.Recv == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Is it public?
|
||||
if !funcDecl.Name.IsExported() {
|
||||
return
|
||||
}
|
||||
|
||||
// Does it return a Result?
|
||||
if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 {
|
||||
return
|
||||
}
|
||||
selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if selExpr.Sel.Name != "Result" {
|
||||
return
|
||||
}
|
||||
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != model.RevelImportPath {
|
||||
return
|
||||
}
|
||||
|
||||
method := &model.MethodSpec{
|
||||
Name: funcDecl.Name.Name,
|
||||
}
|
||||
|
||||
// Add a description of the arguments to the method.
|
||||
for _, field := range funcDecl.Type.Params.List {
|
||||
for _, name := range field.Names {
|
||||
var importPath string
|
||||
typeExpr := model.NewTypeExprFromAst(pkgName, field.Type)
|
||||
if !typeExpr.Valid {
|
||||
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.
|
||||
}
|
||||
// Local object
|
||||
if typeExpr.PkgName == pkgName {
|
||||
importPath = pkgImportPath
|
||||
} else if typeExpr.PkgName != "" {
|
||||
var ok bool
|
||||
if importPath, ok = imports[typeExpr.PkgName]; !ok {
|
||||
utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
|
||||
}
|
||||
}
|
||||
method.Args = append(method.Args, &model.MethodArg{
|
||||
Name: name.Name,
|
||||
TypeExpr: typeExpr,
|
||||
ImportPath: importPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Add a description of the calls to Render from the method.
|
||||
// Inspect every node (e.g. always return true).
|
||||
method.RenderCalls = []*model.MethodCall{}
|
||||
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||
// Is it a function call?
|
||||
callExpr, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// Is it calling (*Controller).Render?
|
||||
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// The type of the receiver is not easily available, so just store every
|
||||
// call to any method called Render.
|
||||
if selExpr.Sel.Name != "Render" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Add this call's args to the renderArgs.
|
||||
pos := fset.Position(callExpr.Lparen)
|
||||
methodCall := &model.MethodCall{
|
||||
Line: pos.Line,
|
||||
Names: []string{},
|
||||
}
|
||||
for _, arg := range callExpr.Args {
|
||||
argIdent, ok := arg.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
methodCall.Names = append(methodCall.Names, argIdent.Name)
|
||||
}
|
||||
method.RenderCalls = append(method.RenderCalls, methodCall)
|
||||
return true
|
||||
})
|
||||
|
||||
var recvTypeName string
|
||||
var recvType = funcDecl.Recv.List[0].Type
|
||||
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||
recvTypeName = recvStarType.X.(*ast.Ident).Name
|
||||
} else {
|
||||
recvTypeName = recvType.(*ast.Ident).Name
|
||||
}
|
||||
|
||||
mm[recvTypeName] = append(mm[recvTypeName], method)
|
||||
}
|
||||
|
||||
// Combine the 2 source info models into one
|
||||
func appendSourceInfo(srcInfo1, srcInfo2 *model.SourceInfo) *model.SourceInfo {
|
||||
if srcInfo1 == nil {
|
||||
return srcInfo2
|
||||
}
|
||||
|
||||
srcInfo1.StructSpecs = append(srcInfo1.StructSpecs, srcInfo2.StructSpecs...)
|
||||
srcInfo1.InitImportPaths = append(srcInfo1.InitImportPaths, srcInfo2.InitImportPaths...)
|
||||
for k, v := range srcInfo2.ValidationKeys {
|
||||
if _, ok := srcInfo1.ValidationKeys[k]; ok {
|
||||
utils.Logger.Warn("Warn: Key conflict when scanning validation calls:", "key", k)
|
||||
continue
|
||||
}
|
||||
srcInfo1.ValidationKeys[k] = v
|
||||
}
|
||||
return srcInfo1
|
||||
}
|
||||
82
parser/imports.go
Normal file
82
parser/imports.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/revel/cmd/utils"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Add imports to the map from the source dir
|
||||
func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if genDecl.Tok != token.IMPORT {
|
||||
return
|
||||
}
|
||||
|
||||
for _, spec := range genDecl.Specs {
|
||||
importSpec := spec.(*ast.ImportSpec)
|
||||
var pkgAlias string
|
||||
if importSpec.Name != nil {
|
||||
pkgAlias = importSpec.Name.Name
|
||||
if pkgAlias == "_" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\""
|
||||
fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes
|
||||
|
||||
// If the package was not aliased (common case), we have to import it
|
||||
// to see what the package name is.
|
||||
// TODO: Can improve performance here a lot:
|
||||
// 1. Do not import everything over and over again. Keep a cache.
|
||||
// 2. Exempt the standard library; their directories always match the package name.
|
||||
// 3. Can use build.FindOnly and then use parser.ParseDir with mode PackageClauseOnly
|
||||
if pkgAlias == "" {
|
||||
utils.Logger.Debug("Reading from build", "path", fullPath, "srcPath", srcDir, "gopath", build.Default.GOPATH)
|
||||
pkg, err := build.Import(fullPath, srcDir, 0)
|
||||
if err != nil {
|
||||
// We expect this to happen for apps using reverse routing (since we
|
||||
// have not yet generated the routes). Don't log that.
|
||||
if !strings.HasSuffix(fullPath, "/app/routes") {
|
||||
utils.Logger.Error("Could not find import:", "path", fullPath, "srcPath", srcDir, "error", err)
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
utils.Logger.Debug("Found package in dir", "dir", pkg.Dir, "name", pkg.ImportPath)
|
||||
}
|
||||
pkgAlias = pkg.Name
|
||||
}
|
||||
|
||||
imports[pkgAlias] = fullPath
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a valid import string from the path
|
||||
// using the build.Defaul.GOPATH to determine the root
|
||||
func importPathFromPath(root string) string {
|
||||
if vendorIdx := strings.Index(root, "/vendor/"); vendorIdx != -1 {
|
||||
return filepath.ToSlash(root[vendorIdx+8:])
|
||||
}
|
||||
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
|
||||
srcPath := filepath.Join(gopath, "src")
|
||||
if strings.HasPrefix(root, srcPath) {
|
||||
return filepath.ToSlash(root[len(srcPath)+1:])
|
||||
}
|
||||
}
|
||||
|
||||
srcPath := filepath.Join(build.Default.GOROOT, "src", "pkg")
|
||||
if strings.HasPrefix(root, srcPath) {
|
||||
utils.Logger.Warn("Code path should be in GOPATH, but is in GOROOT:", "path", root)
|
||||
return filepath.ToSlash(root[len(srcPath)+1:])
|
||||
}
|
||||
|
||||
utils.Logger.Error("Unexpected! Code path is not in GOPATH:", "path", root)
|
||||
return ""
|
||||
}
|
||||
@@ -9,7 +9,6 @@ package parser
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
@@ -21,6 +20,13 @@ import (
|
||||
"github.com/revel/cmd/utils"
|
||||
)
|
||||
|
||||
// A container used to support the reflection package
|
||||
type processContainer struct {
|
||||
root, rootImportPath string // The paths
|
||||
paths *model.RevelContainer // The Revel paths
|
||||
srcInfo *model.SourceInfo // The source information container
|
||||
}
|
||||
|
||||
// Maps a controller simple name (e.g. "Login") to the methods for which it is a
|
||||
// receiver.
|
||||
type methodMap map[string][]*model.MethodSpec
|
||||
@@ -28,130 +34,116 @@ type methodMap map[string][]*model.MethodSpec
|
||||
// ProcessSource parses the app controllers directory and
|
||||
// returns a list of the controller types found.
|
||||
// Otherwise CompileError if the parsing fails.
|
||||
func ProcessSource(paths *model.RevelContainer) (*model.SourceInfo, *utils.Error) {
|
||||
var (
|
||||
srcInfo *model.SourceInfo
|
||||
compileError *utils.Error
|
||||
)
|
||||
|
||||
func ProcessSource(paths *model.RevelContainer) (_ *model.SourceInfo, compileError error) {
|
||||
pc := &processContainer{paths: paths}
|
||||
for _, root := range paths.CodePaths {
|
||||
rootImportPath := importPathFromPath(root)
|
||||
if rootImportPath == "" {
|
||||
utils.Logger.Info("Skipping empty code path", "path", root)
|
||||
continue
|
||||
}
|
||||
pc.root, pc.rootImportPath = root, rootImportPath
|
||||
|
||||
// Start walking the directory tree.
|
||||
_ = utils.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
utils.Logger.Error("Error scanning app source:", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !info.IsDir() || info.Name() == "tmp" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the import path of the package.
|
||||
pkgImportPath := rootImportPath
|
||||
if root != path {
|
||||
pkgImportPath = rootImportPath + "/" + filepath.ToSlash(path[len(root)+1:])
|
||||
}
|
||||
|
||||
// Parse files within the path.
|
||||
var pkgs map[string]*ast.Package
|
||||
fset := token.NewFileSet()
|
||||
pkgs, err = parser.ParseDir(fset, path, func(f os.FileInfo) bool {
|
||||
return !f.IsDir() && !strings.HasPrefix(f.Name(), ".") && strings.HasSuffix(f.Name(), ".go")
|
||||
}, 0)
|
||||
if err != nil {
|
||||
if errList, ok := err.(scanner.ErrorList); ok {
|
||||
var pos = errList[0].Pos
|
||||
compileError = &utils.Error{
|
||||
SourceType: ".go source",
|
||||
Title: "Go Compilation Error",
|
||||
Path: pos.Filename,
|
||||
Description: errList[0].Msg,
|
||||
Line: pos.Line,
|
||||
Column: pos.Column,
|
||||
SourceLines: utils.MustReadLines(pos.Filename),
|
||||
}
|
||||
|
||||
errorLink := paths.Config.StringDefault("error.link", "")
|
||||
|
||||
if errorLink != "" {
|
||||
compileError.SetLink(errorLink)
|
||||
}
|
||||
|
||||
return compileError
|
||||
}
|
||||
|
||||
// This is exception, err already checked above. Here just a print
|
||||
ast.Print(nil, err)
|
||||
utils.Logger.Fatal("Failed to parse dir", "error", err)
|
||||
}
|
||||
|
||||
// Skip "main" packages.
|
||||
delete(pkgs, "main")
|
||||
|
||||
// Ignore packages that end with _test
|
||||
// These cannot be included in source code that is not generated specifically as a test
|
||||
for i := range pkgs {
|
||||
if len(i) > 6 {
|
||||
if string(i[len(i)-5:]) == "_test" {
|
||||
delete(pkgs, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no code in this directory, skip it.
|
||||
if len(pkgs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// There should be only one package in this directory.
|
||||
if len(pkgs) > 1 {
|
||||
for i := range pkgs {
|
||||
println("Found package ", i)
|
||||
}
|
||||
utils.Logger.Fatal("Most unexpected! Multiple packages in a single directory:", "packages", pkgs)
|
||||
}
|
||||
|
||||
|
||||
var pkg *ast.Package
|
||||
for _, v := range pkgs {
|
||||
pkg = v
|
||||
}
|
||||
|
||||
if pkg != nil {
|
||||
srcInfo = appendSourceInfo(srcInfo, processPackage(fset, pkgImportPath, path, pkg))
|
||||
} else {
|
||||
utils.Logger.Info("Ignoring package, because it contained no packages", "path", path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
compileError = utils.Walk(root, pc.processPath)
|
||||
}
|
||||
|
||||
return srcInfo, compileError
|
||||
return pc.srcInfo, compileError
|
||||
}
|
||||
|
||||
func appendSourceInfo(srcInfo1, srcInfo2 *model.SourceInfo) *model.SourceInfo {
|
||||
if srcInfo1 == nil {
|
||||
return srcInfo2
|
||||
// Called during the "walk process"
|
||||
func (pc *processContainer) processPath(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
utils.Logger.Error("Error scanning app source:", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
srcInfo1.StructSpecs = append(srcInfo1.StructSpecs, srcInfo2.StructSpecs...)
|
||||
srcInfo1.InitImportPaths = append(srcInfo1.InitImportPaths, srcInfo2.InitImportPaths...)
|
||||
for k, v := range srcInfo2.ValidationKeys {
|
||||
if _, ok := srcInfo1.ValidationKeys[k]; ok {
|
||||
utils.Logger.Warn("Warn: Key conflict when scanning validation calls:", "key", k)
|
||||
continue
|
||||
if !info.IsDir() || info.Name() == "tmp" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the import path of the package.
|
||||
pkgImportPath := pc.rootImportPath
|
||||
if pc.root != path {
|
||||
pkgImportPath = pc.rootImportPath + "/" + filepath.ToSlash(path[len(pc.root)+1:])
|
||||
}
|
||||
|
||||
// Parse files within the path.
|
||||
var pkgs map[string]*ast.Package
|
||||
fset := token.NewFileSet()
|
||||
pkgs, err = parser.ParseDir(
|
||||
fset,
|
||||
path,
|
||||
func(f os.FileInfo) bool {
|
||||
return !f.IsDir() && !strings.HasPrefix(f.Name(), ".") && strings.HasSuffix(f.Name(), ".go")
|
||||
},
|
||||
0)
|
||||
|
||||
if err != nil {
|
||||
if errList, ok := err.(scanner.ErrorList); ok {
|
||||
var pos = errList[0].Pos
|
||||
newError := &utils.Error{
|
||||
SourceType: ".go source",
|
||||
Title: "Go Compilation Error",
|
||||
Path: pos.Filename,
|
||||
Description: errList[0].Msg,
|
||||
Line: pos.Line,
|
||||
Column: pos.Column,
|
||||
SourceLines: utils.MustReadLines(pos.Filename),
|
||||
}
|
||||
|
||||
errorLink := pc.paths.Config.StringDefault("error.link", "")
|
||||
if errorLink != "" {
|
||||
newError.SetLink(errorLink)
|
||||
}
|
||||
return newError
|
||||
}
|
||||
srcInfo1.ValidationKeys[k] = v
|
||||
|
||||
// This is exception, err already checked above. Here just a print
|
||||
ast.Print(nil, err)
|
||||
utils.Logger.Fatal("Failed to parse dir", "error", err)
|
||||
}
|
||||
return srcInfo1
|
||||
|
||||
// Skip "main" packages.
|
||||
delete(pkgs, "main")
|
||||
|
||||
// Ignore packages that end with _test
|
||||
// These cannot be included in source code that is not generated specifically as a test
|
||||
for i := range pkgs {
|
||||
if len(i) > 6 {
|
||||
if string(i[len(i)-5:]) == "_test" {
|
||||
delete(pkgs, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no code in this directory, skip it.
|
||||
if len(pkgs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// There should be only one package in this directory.
|
||||
if len(pkgs) > 1 {
|
||||
for i := range pkgs {
|
||||
println("Found package ", i)
|
||||
}
|
||||
utils.Logger.Fatal("Most unexpected! Multiple packages in a single directory:", "packages", pkgs)
|
||||
}
|
||||
|
||||
var pkg *ast.Package
|
||||
for _, v := range pkgs {
|
||||
pkg = v
|
||||
}
|
||||
|
||||
if pkg != nil {
|
||||
pc.srcInfo = appendSourceInfo(pc.srcInfo, processPackage(fset, pkgImportPath, path, pkg))
|
||||
} else {
|
||||
utils.Logger.Info("Ignoring package, because it contained no packages", "path", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Process a single package within a file
|
||||
func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast.Package) *model.SourceInfo {
|
||||
var (
|
||||
structSpecs []*model.TypeInfo
|
||||
@@ -228,355 +220,6 @@ func getFuncName(funcDecl *ast.FuncDecl) string {
|
||||
return prefix + funcDecl.Name.Name
|
||||
}
|
||||
|
||||
func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if genDecl.Tok != token.IMPORT {
|
||||
return
|
||||
}
|
||||
|
||||
for _, spec := range genDecl.Specs {
|
||||
importSpec := spec.(*ast.ImportSpec)
|
||||
var pkgAlias string
|
||||
if importSpec.Name != nil {
|
||||
pkgAlias = importSpec.Name.Name
|
||||
if pkgAlias == "_" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\""
|
||||
fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes
|
||||
|
||||
// If the package was not aliased (common case), we have to import it
|
||||
// to see what the package name is.
|
||||
// TODO: Can improve performance here a lot:
|
||||
// 1. Do not import everything over and over again. Keep a cache.
|
||||
// 2. Exempt the standard library; their directories always match the package name.
|
||||
// 3. Can use build.FindOnly and then use parser.ParseDir with mode PackageClauseOnly
|
||||
if pkgAlias == "" {
|
||||
pkg, err := build.Import(fullPath, srcDir, 0)
|
||||
if err != nil {
|
||||
// We expect this to happen for apps using reverse routing (since we
|
||||
// have not yet generated the routes). Don't log that.
|
||||
if !strings.HasSuffix(fullPath, "/app/routes") {
|
||||
utils.Logger.Info("Debug: Could not find import:", "path", fullPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
pkgAlias = pkg.Name
|
||||
}
|
||||
|
||||
imports[pkgAlias] = fullPath
|
||||
}
|
||||
}
|
||||
|
||||
// If this Decl is a struct type definition, it is summarized and added to specs.
|
||||
// Else, specs is returned unchanged.
|
||||
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.
|
||||
spec, found := getStructTypeDecl(decl, fset)
|
||||
if !found {
|
||||
return specs
|
||||
}
|
||||
|
||||
structType := spec.Type.(*ast.StructType)
|
||||
|
||||
// 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.
|
||||
// Add it provisionally to the Controller list -- it's later filtered using field info.
|
||||
controllerSpec := &model.TypeInfo{
|
||||
StructName: spec.Name.Name,
|
||||
ImportPath: pkgImportPath,
|
||||
PackageName: pkg.Name,
|
||||
}
|
||||
|
||||
for _, field := range structType.Fields.List {
|
||||
// If field.Names is set, it's not an embedded type.
|
||||
if field.Names != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// A direct "sub-type" has an ast.Field as either:
|
||||
// Ident { "AppController" }
|
||||
// SelectorExpr { "rev", "Controller" }
|
||||
// Additionally, that can be wrapped by StarExprs.
|
||||
fieldType := field.Type
|
||||
pkgName, typeName := func() (string, string) {
|
||||
// Drill through any StarExprs.
|
||||
for {
|
||||
if starExpr, ok := fieldType.(*ast.StarExpr); ok {
|
||||
fieldType = starExpr.X
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// If the embedded type is in the same package, it's an Ident.
|
||||
if ident, ok := fieldType.(*ast.Ident); ok {
|
||||
return "", ident.Name
|
||||
}
|
||||
|
||||
if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
|
||||
if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
|
||||
return pkgIdent.Name, selectorExpr.Sel.Name
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}()
|
||||
|
||||
// If a typename wasn't found, skip it.
|
||||
if typeName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the import path for this type.
|
||||
// If it was referenced without a package name, use the current package import path.
|
||||
// Else, look up the package's import path by name.
|
||||
var importPath string
|
||||
if pkgName == "" {
|
||||
importPath = pkgImportPath
|
||||
} else {
|
||||
var ok bool
|
||||
if importPath, ok = imports[pkgName]; !ok {
|
||||
utils.Logger.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{
|
||||
ImportPath: importPath,
|
||||
StructName: typeName,
|
||||
})
|
||||
}
|
||||
|
||||
return append(specs, controllerSpec)
|
||||
}
|
||||
|
||||
// If decl is a Method declaration, it is summarized and added to the array
|
||||
// underneath its receiver type.
|
||||
// e.g. "Login" => {MethodSpec, MethodSpec, ..}
|
||||
func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPath, pkgName string, imports map[string]string) {
|
||||
// Func declaration?
|
||||
funcDecl, ok := decl.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Have a receiver?
|
||||
if funcDecl.Recv == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Is it public?
|
||||
if !funcDecl.Name.IsExported() {
|
||||
return
|
||||
}
|
||||
|
||||
// Does it return a Result?
|
||||
if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 {
|
||||
return
|
||||
}
|
||||
selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if selExpr.Sel.Name != "Result" {
|
||||
return
|
||||
}
|
||||
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != model.RevelImportPath {
|
||||
return
|
||||
}
|
||||
|
||||
method := &model.MethodSpec{
|
||||
Name: funcDecl.Name.Name,
|
||||
}
|
||||
|
||||
// Add a description of the arguments to the method.
|
||||
for _, field := range funcDecl.Type.Params.List {
|
||||
for _, name := range field.Names {
|
||||
var importPath string
|
||||
typeExpr := model.NewTypeExprFromAst(pkgName, field.Type)
|
||||
if !typeExpr.Valid {
|
||||
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.
|
||||
}
|
||||
// Local object
|
||||
if typeExpr.PkgName == pkgName {
|
||||
importPath = pkgImportPath
|
||||
} else if typeExpr.PkgName != "" {
|
||||
var ok bool
|
||||
if importPath, ok = imports[typeExpr.PkgName]; !ok {
|
||||
utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
|
||||
}
|
||||
}
|
||||
method.Args = append(method.Args, &model.MethodArg{
|
||||
Name: name.Name,
|
||||
TypeExpr: typeExpr,
|
||||
ImportPath: importPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Add a description of the calls to Render from the method.
|
||||
// Inspect every node (e.g. always return true).
|
||||
method.RenderCalls = []*model.MethodCall{}
|
||||
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||
// Is it a function call?
|
||||
callExpr, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// Is it calling (*Controller).Render?
|
||||
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// The type of the receiver is not easily available, so just store every
|
||||
// call to any method called Render.
|
||||
if selExpr.Sel.Name != "Render" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Add this call's args to the renderArgs.
|
||||
pos := fset.Position(callExpr.Lparen)
|
||||
methodCall := &model.MethodCall{
|
||||
Line: pos.Line,
|
||||
Names: []string{},
|
||||
}
|
||||
for _, arg := range callExpr.Args {
|
||||
argIdent, ok := arg.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
methodCall.Names = append(methodCall.Names, argIdent.Name)
|
||||
}
|
||||
method.RenderCalls = append(method.RenderCalls, methodCall)
|
||||
return true
|
||||
})
|
||||
|
||||
var recvTypeName string
|
||||
var recvType = funcDecl.Recv.List[0].Type
|
||||
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||
recvTypeName = recvStarType.X.(*ast.Ident).Name
|
||||
} else {
|
||||
recvTypeName = recvType.(*ast.Ident).Name
|
||||
}
|
||||
|
||||
mm[recvTypeName] = append(mm[recvTypeName], method)
|
||||
}
|
||||
|
||||
// Scan app source code for calls to X.Y(), where X is of type *Validation.
|
||||
//
|
||||
// Recognize these scenarios:
|
||||
// - "Y" = "Validation" and is a member of the receiver.
|
||||
// (The common case for inline validation)
|
||||
// - "X" is passed in to the func as a parameter.
|
||||
// (For structs implementing Validated)
|
||||
//
|
||||
// The line number to which a validation call is attributed is that of the
|
||||
// surrounding ExprStmt. This is so that it matches what runtime.Callers()
|
||||
// reports.
|
||||
//
|
||||
// The end result is that we can set the default validation key for each call to
|
||||
// be the same as the local variable.
|
||||
func GetValidationKeys(fname string, fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[string]string) map[int]string {
|
||||
var (
|
||||
lineKeys = make(map[int]string)
|
||||
|
||||
// Check the func parameters and the receiver's members for the *revel.Validation type.
|
||||
validationParam = getValidationParameter(funcDecl, imports)
|
||||
)
|
||||
|
||||
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||
// e.g. c.Validation.Required(arg) or v.Required(arg)
|
||||
callExpr, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// e.g. c.Validation.Required or v.Required
|
||||
funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
switch x := funcSelector.X.(type) {
|
||||
case *ast.SelectorExpr: // e.g. c.Validation
|
||||
if x.Sel.Name != "Validation" {
|
||||
return true
|
||||
}
|
||||
|
||||
case *ast.Ident: // e.g. v
|
||||
if validationParam == nil || x.Obj != validationParam {
|
||||
return true
|
||||
}
|
||||
|
||||
default:
|
||||
return true
|
||||
}
|
||||
|
||||
if len(callExpr.Args) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Given the validation expression, extract the key.
|
||||
key := callExpr.Args[0]
|
||||
switch expr := key.(type) {
|
||||
case *ast.BinaryExpr:
|
||||
// If the argument is a binary expression, take the first expression.
|
||||
// (e.g. c.Validation.Required(myName != ""))
|
||||
key = expr.X
|
||||
case *ast.UnaryExpr:
|
||||
// If the argument is a unary expression, drill in.
|
||||
// (e.g. c.Validation.Required(!myBool)
|
||||
key = expr.X
|
||||
case *ast.BasicLit:
|
||||
// If it's a literal, skip it.
|
||||
return true
|
||||
}
|
||||
|
||||
if typeExpr := model.NewTypeExprFromAst("", key); typeExpr.Valid {
|
||||
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 lineKeys
|
||||
}
|
||||
|
||||
// Check to see if there is a *revel.Validation as an argument.
|
||||
func getValidationParameter(funcDecl *ast.FuncDecl, imports map[string]string) *ast.Object {
|
||||
for _, field := range funcDecl.Type.Params.List {
|
||||
starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == model.RevelImportPath {
|
||||
return field.Names[0].Obj
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getStructTypeDecl checks if the given decl is a type declaration for a
|
||||
// struct. If so, the TypeSpec is returned.
|
||||
func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) {
|
||||
@@ -599,24 +242,3 @@ func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec,
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func importPathFromPath(root string) string {
|
||||
if vendorIdx := strings.Index(root, "/vendor/"); vendorIdx != -1 {
|
||||
return filepath.ToSlash(root[vendorIdx+8:])
|
||||
}
|
||||
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
|
||||
srcPath := filepath.Join(gopath, "src")
|
||||
if strings.HasPrefix(root, srcPath) {
|
||||
return filepath.ToSlash(root[len(srcPath)+1:])
|
||||
}
|
||||
}
|
||||
|
||||
srcPath := filepath.Join(build.Default.GOROOT, "src", "pkg")
|
||||
if strings.HasPrefix(root, srcPath) {
|
||||
utils.Logger.Warn("Code path should be in GOPATH, but is in GOROOT:", "path", root)
|
||||
return filepath.ToSlash(root[len(srcPath)+1:])
|
||||
}
|
||||
|
||||
utils.Logger.Error("Unexpected! Code path is not in GOPATH:", "path", root)
|
||||
return ""
|
||||
}
|
||||
|
||||
115
parser/validation.go
Normal file
115
parser/validation.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/utils"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
// Scan app source code for calls to X.Y(), where X is of type *Validation.
|
||||
//
|
||||
// Recognize these scenarios:
|
||||
// - "Y" = "Validation" and is a member of the receiver.
|
||||
// (The common case for inline validation)
|
||||
// - "X" is passed in to the func as a parameter.
|
||||
// (For structs implementing Validated)
|
||||
//
|
||||
// The line number to which a validation call is attributed is that of the
|
||||
// surrounding ExprStmt. This is so that it matches what runtime.Callers()
|
||||
// reports.
|
||||
//
|
||||
// The end result is that we can set the default validation key for each call to
|
||||
// be the same as the local variable.
|
||||
func GetValidationKeys(fname string, fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[string]string) map[int]string {
|
||||
var (
|
||||
lineKeys = make(map[int]string)
|
||||
|
||||
// Check the func parameters and the receiver's members for the *revel.Validation type.
|
||||
validationParam = getValidationParameter(funcDecl, imports)
|
||||
)
|
||||
|
||||
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||
// e.g. c.Validation.Required(arg) or v.Required(arg)
|
||||
callExpr, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// e.g. c.Validation.Required or v.Required
|
||||
funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
switch x := funcSelector.X.(type) {
|
||||
case *ast.SelectorExpr: // e.g. c.Validation
|
||||
if x.Sel.Name != "Validation" {
|
||||
return true
|
||||
}
|
||||
|
||||
case *ast.Ident: // e.g. v
|
||||
if validationParam == nil || x.Obj != validationParam {
|
||||
return true
|
||||
}
|
||||
|
||||
default:
|
||||
return true
|
||||
}
|
||||
|
||||
if len(callExpr.Args) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Given the validation expression, extract the key.
|
||||
key := callExpr.Args[0]
|
||||
switch expr := key.(type) {
|
||||
case *ast.BinaryExpr:
|
||||
// If the argument is a binary expression, take the first expression.
|
||||
// (e.g. c.Validation.Required(myName != ""))
|
||||
key = expr.X
|
||||
case *ast.UnaryExpr:
|
||||
// If the argument is a unary expression, drill in.
|
||||
// (e.g. c.Validation.Required(!myBool)
|
||||
key = expr.X
|
||||
case *ast.BasicLit:
|
||||
// If it's a literal, skip it.
|
||||
return true
|
||||
}
|
||||
|
||||
if typeExpr := model.NewTypeExprFromAst("", key); typeExpr.Valid {
|
||||
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 lineKeys
|
||||
}
|
||||
|
||||
// Check to see if there is a *revel.Validation as an argument.
|
||||
func getValidationParameter(funcDecl *ast.FuncDecl, imports map[string]string) *ast.Object {
|
||||
for _, field := range funcDecl.Type.Params.List {
|
||||
starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == model.RevelImportPath {
|
||||
return field.Names[0].Obj
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
154
revel/build.go
154
revel/build.go
@@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
var cmdBuild = &Command{
|
||||
UsageLine: "build -i [import path] -t [target path] -r [run mode]",
|
||||
UsageLine: "revel build [-r [run mode]] [import path] [target path] ",
|
||||
Short: "build a Revel application (e.g. for deployment)",
|
||||
Long: `
|
||||
Build the Revel web application named by the given import path.
|
||||
@@ -25,7 +25,7 @@ This allows it to be deployed and run on a machine that lacks a Go installation.
|
||||
|
||||
For example:
|
||||
|
||||
revel build -a github.com/revel/examples/chat -t /tmp/chat
|
||||
revel build github.com/revel/examples/chat /tmp/chat
|
||||
|
||||
`,
|
||||
}
|
||||
@@ -38,10 +38,12 @@ func init() {
|
||||
// The update config updates the configuration command so that it can run
|
||||
func updateBuildConfig(c *model.CommandConfig, args []string) bool {
|
||||
c.Index = model.BUILD
|
||||
// If arguments were passed in then there must be two
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long)
|
||||
return false
|
||||
}
|
||||
|
||||
c.Build.ImportPath = args[0]
|
||||
c.Build.TargetPath = args[1]
|
||||
if len(args) > 2 {
|
||||
@@ -51,75 +53,101 @@ func updateBuildConfig(c *model.CommandConfig, args []string) bool {
|
||||
}
|
||||
|
||||
// The main entry point to build application from command line
|
||||
func buildApp(c *model.CommandConfig) {
|
||||
func buildApp(c *model.CommandConfig) (err error) {
|
||||
appImportPath, destPath, mode := c.ImportPath, c.Build.TargetPath, DefaultRunMode
|
||||
if len(c.Build.Mode) > 0 {
|
||||
mode = c.Build.Mode
|
||||
}
|
||||
|
||||
// Convert target to absolute path
|
||||
destPath, _ = filepath.Abs(destPath)
|
||||
c.Build.TargetPath, _ = filepath.Abs(destPath)
|
||||
c.Build.Mode = mode
|
||||
|
||||
revel_paths := model.NewRevelPaths(mode, appImportPath, "", model.DoNothingRevelCallback)
|
||||
|
||||
// First, verify that it is either already empty or looks like a previous
|
||||
// build (to avoid clobbering anything)
|
||||
if utils.Exists(destPath) && !utils.Empty(destPath) && !utils.Exists(filepath.Join(destPath, "run.sh")) {
|
||||
utils.Logger.Errorf("Abort: %s exists and does not look like a build directory.", destPath)
|
||||
revel_paths, err := model.NewRevelPaths(mode, appImportPath, "", model.NewWrappedRevelCallback(nil, c.PackageResolver))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
|
||||
utils.Logger.Error("Remove all error", "error", err)
|
||||
return
|
||||
}
|
||||
buildSafetyCheck(destPath)
|
||||
|
||||
if err := os.MkdirAll(destPath, 0777); err != nil {
|
||||
utils.Logger.Error("makedir error", "error", err)
|
||||
return
|
||||
// Ensure the application can be built, this generates the main file
|
||||
app, err := harness.Build(c, revel_paths)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
app, reverr := harness.Build(c, revel_paths)
|
||||
if reverr != nil {
|
||||
utils.Logger.Error("Failed to build application", "error", reverr)
|
||||
return
|
||||
}
|
||||
|
||||
// Copy files
|
||||
// Included are:
|
||||
// - run scripts
|
||||
// - binary
|
||||
// - revel
|
||||
// - app
|
||||
|
||||
packageFolders, err := buildCopyFiles(c, app, revel_paths)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = buildCopyModules(c, revel_paths, packageFolders)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = buildWriteScripts(c, app)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Copy the files to the target
|
||||
func buildCopyFiles(c *model.CommandConfig, app *harness.App, revel_paths *model.RevelContainer) (packageFolders []string, err error) {
|
||||
appImportPath, destPath := c.ImportPath, c.Build.TargetPath
|
||||
|
||||
// Revel and the app are in a directory structure mirroring import path
|
||||
srcPath := filepath.Join(destPath, "src")
|
||||
destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath))
|
||||
tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(model.RevelImportPath))
|
||||
utils.MustCopyFile(destBinaryPath, app.BinaryPath)
|
||||
if err = utils.CopyFile(destBinaryPath, app.BinaryPath); err != nil {
|
||||
return
|
||||
}
|
||||
utils.MustChmod(destBinaryPath, 0755)
|
||||
|
||||
// Copy the templates from the revel
|
||||
_ = utils.MustCopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revel_paths.RevelPath, "conf"), nil)
|
||||
_ = utils.MustCopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revel_paths.RevelPath, "templates"), nil)
|
||||
if err = utils.CopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revel_paths.RevelPath, "conf"), nil); err != nil {
|
||||
return
|
||||
}
|
||||
if err = utils.CopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revel_paths.RevelPath, "templates"), nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the folders to be packaged
|
||||
packageFolders := strings.Split(revel_paths.Config.StringDefault("package.folders", "conf,public,app/views"), ",")
|
||||
for i,p:=range packageFolders {
|
||||
packageFolders = strings.Split(revel_paths.Config.StringDefault("package.folders", "conf,public,app/views"), ",")
|
||||
for i, p := range packageFolders {
|
||||
// Clean spaces, reformat slash to filesystem
|
||||
packageFolders[i]=filepath.FromSlash(strings.TrimSpace(p))
|
||||
packageFolders[i] = filepath.FromSlash(strings.TrimSpace(p))
|
||||
}
|
||||
|
||||
if c.Build.CopySource {
|
||||
_ = utils.MustCopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revel_paths.BasePath, nil)
|
||||
err = utils.CopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revel_paths.BasePath, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
for _, folder := range packageFolders {
|
||||
_ = utils.MustCopyDir(
|
||||
err = utils.CopyDir(
|
||||
filepath.Join(srcPath, filepath.FromSlash(appImportPath), folder),
|
||||
filepath.Join(revel_paths.BasePath, folder),
|
||||
nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Based on the section copy over the build modules
|
||||
func buildCopyModules(c *model.CommandConfig, revel_paths *model.RevelContainer, packageFolders []string) (err error) {
|
||||
destPath := filepath.Join(c.Build.TargetPath, "src")
|
||||
// Find all the modules used and copy them over.
|
||||
config := revel_paths.Config.Raw()
|
||||
modulePaths := make(map[string]string) // import path => filesystem path
|
||||
@@ -151,40 +179,76 @@ func buildApp(c *model.CommandConfig) {
|
||||
|
||||
// Copy the the paths for each of the modules
|
||||
for importPath, fsPath := range modulePaths {
|
||||
utils.Logger.Info("Copy files ", "to", filepath.Join(srcPath, importPath), "from", fsPath)
|
||||
utils.Logger.Info("Copy files ", "to", filepath.Join(destPath, importPath), "from", fsPath)
|
||||
if c.Build.CopySource {
|
||||
_ = utils.MustCopyDir(filepath.Join(srcPath, importPath), fsPath, nil)
|
||||
err = utils.CopyDir(filepath.Join(destPath, importPath), fsPath, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
for _, folder := range packageFolders {
|
||||
_ = utils.MustCopyDir(
|
||||
filepath.Join(srcPath, importPath, folder),
|
||||
err = utils.CopyDir(
|
||||
filepath.Join(destPath, importPath, folder),
|
||||
filepath.Join(fsPath, folder),
|
||||
nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Write the run scripts for the build
|
||||
func buildWriteScripts(c *model.CommandConfig, app *harness.App) (err error) {
|
||||
tmplData := map[string]interface{}{
|
||||
"BinName": filepath.Base(app.BinaryPath),
|
||||
"ImportPath": appImportPath,
|
||||
"Mode": mode,
|
||||
"ImportPath": c.Build.ImportPath,
|
||||
"Mode": c.Build.Mode,
|
||||
}
|
||||
|
||||
utils.MustGenerateTemplate(
|
||||
filepath.Join(destPath, "run.sh"),
|
||||
err = utils.GenerateTemplate(
|
||||
filepath.Join(c.Build.TargetPath, "run.sh"),
|
||||
PACKAGE_RUN_SH,
|
||||
tmplData,
|
||||
)
|
||||
utils.MustChmod(filepath.Join(destPath, "run.sh"), 0755)
|
||||
utils.MustGenerateTemplate(
|
||||
filepath.Join(destPath, "run.bat"),
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
utils.MustChmod(filepath.Join(c.Build.TargetPath, "run.sh"), 0755)
|
||||
err = utils.GenerateTemplate(
|
||||
filepath.Join(c.Build.TargetPath, "run.bat"),
|
||||
PACKAGE_RUN_BAT,
|
||||
tmplData,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Your application has been built in:", destPath)
|
||||
fmt.Println("Your application has been built in:", c.Build.TargetPath)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Checks to see if the target folder exists and can be created
|
||||
func buildSafetyCheck(destPath string) error {
|
||||
|
||||
// First, verify that it is either already empty or looks like a previous
|
||||
// build (to avoid clobbering anything)
|
||||
if utils.Exists(destPath) && !utils.Empty(destPath) && !utils.Exists(filepath.Join(destPath, "run.sh")) {
|
||||
return utils.NewBuildError("Abort: %s exists and does not look like a build directory.", "path", destPath)
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
|
||||
return utils.NewBuildIfError(err, "Remove all error", "path", destPath)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(destPath, 0777); err != nil {
|
||||
return utils.NewBuildIfError(err, "MkDir all error", "path", destPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const PACKAGE_RUN_SH = `#!/bin/sh
|
||||
|
||||
34
revel/build_test.go
Normal file
34
revel/build_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/revel"
|
||||
"github.com/revel/cmd/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// test the commands
|
||||
func TestBuild(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
gopath := setup("revel-test-build", a)
|
||||
|
||||
t.Run("Build", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("build-test", model.NEW, nil, a)
|
||||
main.Commands[model.NEW].RunWith(c)
|
||||
c.Index = model.BUILD
|
||||
c.Build.TargetPath = filepath.Join(gopath, "build-test", "target")
|
||||
c.Build.ImportPath = c.ImportPath
|
||||
a.Nil(main.Commands[model.BUILD].RunWith(c), "Failed to run build-test")
|
||||
a.True(utils.Exists(filepath.Join(gopath, "build-test", "target")))
|
||||
})
|
||||
|
||||
if !t.Failed() {
|
||||
if err := os.RemoveAll(gopath); err != nil {
|
||||
a.Fail("Failed to remove test path")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,14 @@ import (
|
||||
)
|
||||
|
||||
var cmdClean = &Command{
|
||||
UsageLine: "clean -i [import path]",
|
||||
UsageLine: "clean [import path]",
|
||||
Short: "clean a Revel application's temp files",
|
||||
Long: `
|
||||
Clean the Revel web application named by the given import path.
|
||||
|
||||
For example:
|
||||
|
||||
revel clean -a github.com/revel/examples/chat
|
||||
revel clean github.com/revel/examples/chat
|
||||
|
||||
It removes the app/tmp and app/routes directory.
|
||||
|
||||
@@ -46,7 +46,7 @@ func updateCleanConfig(c *model.CommandConfig, args []string) bool {
|
||||
}
|
||||
|
||||
// Clean the source directory of generated files
|
||||
func cleanApp(c *model.CommandConfig) {
|
||||
func cleanApp(c *model.CommandConfig) (err error) {
|
||||
appPkg, err := build.Import(c.ImportPath, "", build.FindOnly)
|
||||
if err != nil {
|
||||
utils.Logger.Fatal("Abort: Failed to find import path:", "error", err)
|
||||
@@ -65,4 +65,5 @@ func cleanApp(c *model.CommandConfig) {
|
||||
return
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
37
revel/clean_test.go
Normal file
37
revel/clean_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/revel"
|
||||
"github.com/revel/cmd/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// test the commands
|
||||
func TestClean(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
gopath := setup("revel-test-clean", a)
|
||||
|
||||
|
||||
t.Run("Clean", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("clean-test", model.NEW, nil, a)
|
||||
main.Commands[model.NEW].RunWith(c)
|
||||
c.Index = model.TEST
|
||||
main.Commands[model.TEST].RunWith(c)
|
||||
a.True(utils.Exists(filepath.Join(gopath, "src", "clean-test", "app", "tmp", "main.go")),
|
||||
"Missing main from path "+filepath.Join(gopath, "src", "clean-test", "app", "tmp", "main.go"))
|
||||
c.Clean.ImportPath = c.ImportPath
|
||||
a.Nil(main.Commands[model.CLEAN].RunWith(c), "Failed to run clean-test")
|
||||
a.False(utils.Exists(filepath.Join(gopath, "src", "clean-test", "app", "tmp", "main.go")),
|
||||
"Did not remove main from path "+filepath.Join(gopath, "src", "clean-test", "app", "tmp", "main.go"))
|
||||
})
|
||||
if !t.Failed() {
|
||||
if err := os.RemoveAll(gopath); err != nil {
|
||||
a.Fail("Failed to remove test path")
|
||||
}
|
||||
}
|
||||
}
|
||||
74
revel/command_test.go
Normal file
74
revel/command_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"github.com/revel/cmd/logger"
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go/build"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Test that the event handler can be attached and it dispatches the event received
|
||||
func setup(suffix string, a *assert.Assertions) (string) {
|
||||
temp := os.TempDir()
|
||||
wd, _ := os.Getwd()
|
||||
utils.InitLogger(wd, logger.LvlInfo)
|
||||
gopath := filepath.Join(temp, "revel-test",suffix)
|
||||
if utils.Exists(gopath) {
|
||||
utils.Logger.Info("Removing test path", "path", gopath)
|
||||
if err := os.RemoveAll(gopath); err != nil {
|
||||
a.Fail("Failed to remove test path")
|
||||
}
|
||||
}
|
||||
err := os.MkdirAll(gopath, os.ModePerm)
|
||||
a.Nil(err, "Failed to create gopath "+gopath)
|
||||
|
||||
// So this is the issue, on the mac when folders are created in a temp folder they are returned like
|
||||
// /var/folders/nz/vv4_9tw56nv9k3tkvyszvwg80000gn/T/revel-test/revel-test-build
|
||||
// But if you change into that directory and read the current folder it is
|
||||
// /private/var/folders/nz/vv4_9tw56nv9k3tkvyszvwg80000gn/T/revel-test/revel-test-build
|
||||
// So to make this work on darwin this code was added
|
||||
os.Chdir(gopath)
|
||||
newwd, _ := os.Getwd()
|
||||
gopath = newwd
|
||||
defaultBuild := build.Default
|
||||
defaultBuild.GOPATH = gopath
|
||||
build.Default = defaultBuild
|
||||
utils.Logger.Info("Setup stats", "original wd", wd, "new wd", newwd, "gopath",gopath, "gopath exists", utils.DirExists(gopath), "wd exists", utils.DirExists(newwd))
|
||||
|
||||
return gopath
|
||||
}
|
||||
|
||||
// Create a new app for the name
|
||||
func newApp(name string, command model.COMMAND, precall func(c *model.CommandConfig), a *assert.Assertions) *model.CommandConfig {
|
||||
c := &model.CommandConfig{}
|
||||
switch command {
|
||||
case model.NEW:
|
||||
c.New.ImportPath = name
|
||||
case model.BUILD:
|
||||
c.Build.ImportPath = name
|
||||
case model.TEST:
|
||||
c.Test.ImportPath = name
|
||||
case model.PACKAGE:
|
||||
c.Package.ImportPath = name
|
||||
case model.VERSION:
|
||||
c.Version.ImportPath = name
|
||||
case model.CLEAN:
|
||||
c.Clean.ImportPath = name
|
||||
default:
|
||||
a.Fail("Unknown command ", command)
|
||||
}
|
||||
|
||||
c.Index = command
|
||||
if precall != nil {
|
||||
precall(c)
|
||||
}
|
||||
if !c.UpdateImportPath() {
|
||||
a.Fail("Unable to update import path")
|
||||
}
|
||||
c.InitGoPaths()
|
||||
c.InitPackageResolver()
|
||||
return c
|
||||
}
|
||||
289
revel/new.go
289
revel/new.go
@@ -5,7 +5,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"math/rand"
|
||||
@@ -16,6 +15,7 @@ import (
|
||||
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/utils"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var cmdNew = &Command{
|
||||
@@ -51,83 +51,105 @@ func updateNewConfig(c *model.CommandConfig, args []string) bool {
|
||||
return false
|
||||
}
|
||||
c.New.ImportPath = args[0]
|
||||
if len(args)>1 {
|
||||
c.New.Skeleton = args[1]
|
||||
if len(args) > 1 {
|
||||
c.New.SkeletonPath = args[1]
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
// Call to create a new application
|
||||
func newApp(c *model.CommandConfig) {
|
||||
// check for proper args by count
|
||||
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)
|
||||
func newApp(c *model.CommandConfig) (err error) {
|
||||
// Check for an existing folder so we don't clobber it
|
||||
_, err = build.Import(c.ImportPath, "", build.FindOnly)
|
||||
if err == nil || !utils.Empty(c.AppPath) {
|
||||
return utils.NewBuildError("Abort: Import path already exists.", "path", c.ImportPath)
|
||||
}
|
||||
if err := os.MkdirAll(c.AppPath, os.ModePerm); err != nil {
|
||||
return utils.NewBuildError("Abort: Unable to create app path.", "path", c.AppPath)
|
||||
}
|
||||
|
||||
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)
|
||||
utils.Logger.Info("Creating a new vendor app")
|
||||
|
||||
vendorPath := filepath.Join(c.AppPath, "vendor")
|
||||
if !utils.DirExists(vendorPath) {
|
||||
|
||||
if err := os.MkdirAll(vendorPath, os.ModePerm); err != nil {
|
||||
return utils.NewBuildError("Failed to create "+vendorPath, "error", err)
|
||||
}
|
||||
}
|
||||
packageFile := filepath.Join(c.ImportPath,"Gopkg.toml")
|
||||
|
||||
// In order for dep to run there needs to be a source file in the folder
|
||||
tempPath := filepath.Join(c.AppPath, "tmp")
|
||||
utils.Logger.Info("Checking for temp folder for source code", "path", tempPath)
|
||||
if !utils.DirExists(tempPath) {
|
||||
if err := os.MkdirAll(tempPath, os.ModePerm); err != nil {
|
||||
return utils.NewBuildIfError(err, "Failed to create "+vendorPath)
|
||||
}
|
||||
|
||||
if err = utils.GenerateTemplate(filepath.Join(tempPath, "main.go"), NEW_MAIN_FILE, nil); err != nil {
|
||||
return utils.NewBuildIfError(err, "Failed to create main file "+vendorPath)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a package template file if it does not exist
|
||||
packageFile := filepath.Join(c.AppPath, "Gopkg.toml")
|
||||
utils.Logger.Info("Checking for Gopkg.toml", "path", packageFile)
|
||||
if !utils.Exists(packageFile) {
|
||||
utils.MustGenerateTemplate(packageFile,VENDOR_GOPKG,nil)
|
||||
utils.Logger.Info("Generating Gopkg.toml", "path", packageFile)
|
||||
if err := utils.GenerateTemplate(packageFile, VENDOR_GOPKG, nil); err != nil {
|
||||
return utils.NewBuildIfError(err, "Failed to generate template")
|
||||
}
|
||||
} else {
|
||||
utils.Logger.Info("Package file exists in skeleto, skipping adding")
|
||||
}
|
||||
|
||||
getCmd := exec.Command(depPath, "ensure", "-v")
|
||||
getCmd.Dir = c.ImportPath
|
||||
getCmd := exec.Command("dep", "ensure", "-v")
|
||||
utils.CmdInit(getCmd, c.AppPath)
|
||||
utils.Logger.Info("Exec:", "args", getCmd.Args)
|
||||
getOutput, err := getCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return utils.NewBuildIfError(err, string(getOutput))
|
||||
}
|
||||
}
|
||||
|
||||
// checking and setting application
|
||||
|
||||
if err = setApplicationPath(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// checking and setting skeleton
|
||||
if err=setSkeletonPath(c);err!=nil {
|
||||
return
|
||||
}
|
||||
|
||||
// copy files to new app directory
|
||||
if err = copyNewAppFiles(c);err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Rerun the dep tool if vendored
|
||||
if c.New.Vendored {
|
||||
getCmd := exec.Command("dep", "ensure", "-v")
|
||||
utils.CmdInit(getCmd, c.AppPath)
|
||||
utils.Logger.Info("Exec:", "args", getCmd.Args)
|
||||
getCmd.Dir = c.ImportPath
|
||||
getOutput, err := getCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
utils.Logger.Fatal(string(getOutput))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// checking and setting application
|
||||
setApplicationPath(c)
|
||||
|
||||
// checking and setting skeleton
|
||||
setSkeletonPath(c)
|
||||
|
||||
// copy files to new app directory
|
||||
copyNewAppFiles(c)
|
||||
|
||||
|
||||
// goodbye world
|
||||
fmt.Fprintln(os.Stdout, "Your application is ready:\n ", c.AppPath)
|
||||
fmt.Fprintln(os.Stdout, "Your application has been created in:\n ", c.AppPath)
|
||||
// Check to see if it should be run right off
|
||||
if c.New.Run {
|
||||
runApp(c)
|
||||
} else {
|
||||
fmt.Fprintln(os.Stdout, "\nYou can run it with:\n revel run -a ", c.ImportPath)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Used to generate a new secret key
|
||||
@@ -143,7 +165,7 @@ func generateSecret() string {
|
||||
}
|
||||
|
||||
// Sets the applicaiton path
|
||||
func setApplicationPath(c *model.CommandConfig) {
|
||||
func setApplicationPath(c *model.CommandConfig) (err error) {
|
||||
|
||||
// revel/revel#1014 validate relative path, we cannot use built-in functions
|
||||
// since Go import path is valid relative path too.
|
||||
@@ -153,95 +175,134 @@ func setApplicationPath(c *model.CommandConfig) {
|
||||
c.ImportPath)
|
||||
}
|
||||
|
||||
|
||||
// If we are running a vendored version of Revel we do not need to check for it.
|
||||
if !c.New.Vendored {
|
||||
var err error
|
||||
_, err = build.Import(model.RevelImportPath, "", build.FindOnly)
|
||||
if err != nil {
|
||||
// Go get the revel project
|
||||
getCmd := exec.Command(c.GoCmd, "get", model.RevelImportPath)
|
||||
utils.Logger.Info("Exec:" + c.GoCmd, "args", getCmd.Args)
|
||||
getOutput, err := getCmd.CombinedOutput()
|
||||
//// Go get the revel project
|
||||
err = c.PackageResolver(model.RevelImportPath)
|
||||
if err != nil {
|
||||
utils.Logger.Fatal("Failed to fetch revel " + model.RevelImportPath, "getOutput", string(getOutput))
|
||||
return utils.NewBuildIfError(err, "Failed to fetch revel "+model.RevelImportPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.AppName = filepath.Base(c.AppPath)
|
||||
c.BasePath = filepath.ToSlash(filepath.Dir(c.ImportPath))
|
||||
|
||||
if c.BasePath == "." {
|
||||
// we need to remove the a single '.' when
|
||||
// the app is in the $GOROOT/src directory
|
||||
c.BasePath = ""
|
||||
} else {
|
||||
// we need to append a '/' when the app is
|
||||
// is a subdirectory such as $GOROOT/src/path/to/revelapp
|
||||
c.BasePath += "/"
|
||||
}
|
||||
//if c.BasePath == "." {
|
||||
// // we need to remove the a single '.' when
|
||||
// // the app is in the $GOROOT/src directory
|
||||
// c.BasePath = ""
|
||||
//} else {
|
||||
// // we need to append a '/' when the app is
|
||||
// // is a subdirectory such as $GOROOT/src/path/to/revelapp
|
||||
// c.BasePath += "/"
|
||||
//}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set the skeleton path
|
||||
func setSkeletonPath(c *model.CommandConfig) {
|
||||
var err error
|
||||
if len(c.SkeletonPath) > 0 { // user specified
|
||||
|
||||
_, err = build.Import(c.SkeletonPath, "", build.FindOnly)
|
||||
if err != nil {
|
||||
// Execute "go get <pkg>"
|
||||
getCmd := exec.Command(c.GoCmd, "get", "-d", c.SkeletonPath)
|
||||
fmt.Println("Exec:", getCmd.Args)
|
||||
getOutput, err := getCmd.CombinedOutput()
|
||||
|
||||
// check getOutput for no buildible string
|
||||
bpos := bytes.Index(getOutput, []byte("no buildable Go source files in"))
|
||||
if err != nil && bpos == -1 {
|
||||
utils.Logger.Fatalf("Abort: Could not find or 'go get' Skeleton source code: %s\n%s\n", getOutput, c.SkeletonPath)
|
||||
}
|
||||
}
|
||||
// use the
|
||||
c.SkeletonPath = filepath.Join(c.SrcRoot, c.SkeletonPath)
|
||||
|
||||
} else {
|
||||
// use the revel default
|
||||
revelCmdPkg, err := build.Import(RevelCmdImportPath, "", build.FindOnly)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
// Go get the revel project
|
||||
getCmd := exec.Command(c.GoCmd, "get", RevelCmdImportPath + "/revel")
|
||||
utils.Logger.Info("Exec:" + c.GoCmd, "args", getCmd.Args)
|
||||
getOutput, err := getCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
utils.Logger.Fatal("Failed to fetch revel cmd " + RevelCmdImportPath, "getOutput", string(getOutput))
|
||||
}
|
||||
revelCmdPkg, err = build.Import(RevelCmdImportPath, "", build.FindOnly)
|
||||
if err!= nil {
|
||||
utils.Logger.Fatal("Failed to find source of revel cmd " + RevelCmdImportPath, "getOutput", string(getOutput), "error",err, "dir", revelCmdPkg.Dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.SkeletonPath = filepath.Join(revelCmdPkg.Dir, "revel", "skeleton")
|
||||
func setSkeletonPath(c *model.CommandConfig) (err error) {
|
||||
if len(c.New.SkeletonPath) == 0 {
|
||||
c.New.SkeletonPath = RevelCmdImportPath + ":skeleton"
|
||||
}
|
||||
|
||||
// First check to see the protocol of the string
|
||||
if sp, err := url.Parse(c.New.SkeletonPath); err == nil {
|
||||
utils.Logger.Info("Detected skeleton path", "path", sp)
|
||||
|
||||
switch strings.ToLower(sp.Scheme) {
|
||||
// TODO Add support for https, http, ftp, file
|
||||
case "git":
|
||||
if err := newLoadFromGit(c, sp); err != nil {
|
||||
return err
|
||||
}
|
||||
case "":
|
||||
if err := newLoadFromGo(c, sp); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
utils.Logger.Fatal("Unsupported")
|
||||
|
||||
}
|
||||
// TODO check to see if the path needs to be extracted
|
||||
} else {
|
||||
utils.Logger.Fatal("Invalid skeleton path format", "path", c.New.SkeletonPath)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func copyNewAppFiles(c *model.CommandConfig) {
|
||||
var err error
|
||||
err = os.MkdirAll(c.AppPath, 0777)
|
||||
utils.PanicOnError(err, "Failed to create directory "+c.AppPath)
|
||||
// Load skeleton from git
|
||||
func newLoadFromGit(c *model.CommandConfig, sp *url.URL) (err error) {
|
||||
// This method indicates we need to fetch from a repository using git
|
||||
// Execute "git clone get <pkg>"
|
||||
targetPath := filepath.Join(os.TempDir(), "revel", "skeleton")
|
||||
os.RemoveAll(targetPath)
|
||||
pathpart := strings.Split(sp.Path, ":")
|
||||
getCmd := exec.Command("git", "clone", sp.Scheme+"://"+sp.Host+pathpart[0], targetPath)
|
||||
utils.Logger.Info("Exec:", "args", getCmd.Args)
|
||||
getOutput, err := getCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
utils.Logger.Fatalf("Abort: could not clone the Skeleton source code: \n%s\n%s\n", getOutput, c.New.SkeletonPath)
|
||||
}
|
||||
outputPath := targetPath
|
||||
if len(pathpart) > 1 {
|
||||
outputPath = filepath.Join(targetPath, filepath.Join(strings.Split(pathpart[1], string('/'))...))
|
||||
}
|
||||
outputPath, _ = filepath.Abs(outputPath)
|
||||
if !strings.HasPrefix(outputPath, targetPath) {
|
||||
utils.Logger.Fatal("Unusual target path outside root path", "target", outputPath, "root", targetPath)
|
||||
}
|
||||
|
||||
_ = utils.MustCopyDir(c.AppPath, c.SkeletonPath, map[string]interface{}{
|
||||
c.New.SkeletonPath = outputPath
|
||||
return
|
||||
}
|
||||
|
||||
// Load from GO
|
||||
func newLoadFromGo(c *model.CommandConfig, sp *url.URL) (err error) {
|
||||
// Find the source paths, download packages automatically
|
||||
pathpart := strings.Split(sp.Path, ":")
|
||||
_, skeletonImportPath , err := utils.FindSrcPaths(c.ImportPath,sp.Host+pathpart[0],c.PackageResolver)
|
||||
if err!=nil {
|
||||
return
|
||||
}
|
||||
|
||||
skeletonImportPath = filepath.Join(skeletonImportPath,sp.Host,pathpart[0])
|
||||
// Add in anything after the "Root" path
|
||||
if len(pathpart) > 1 {
|
||||
pathpart[0] = skeletonImportPath
|
||||
newdir, _ := filepath.Abs(filepath.Join(pathpart...))
|
||||
if !strings.HasPrefix(newdir, skeletonImportPath) {
|
||||
utils.Logger.Fatal("Unusual target path outside root path", "target", newdir, "root", skeletonImportPath)
|
||||
}
|
||||
skeletonImportPath = newdir
|
||||
}
|
||||
|
||||
c.New.SkeletonPath = skeletonImportPath
|
||||
return
|
||||
}
|
||||
|
||||
func copyNewAppFiles(c *model.CommandConfig) (err error) {
|
||||
err = os.MkdirAll(c.AppPath, 0777)
|
||||
if err != nil {
|
||||
return utils.NewBuildIfError(err, "MKDIR failed")
|
||||
}
|
||||
|
||||
err = utils.CopyDir(c.AppPath, c.New.SkeletonPath, map[string]interface{}{
|
||||
// app.conf
|
||||
"AppName": c.AppName,
|
||||
"BasePath": c.BasePath,
|
||||
"BasePath": c.AppPath,
|
||||
"Secret": generateSecret(),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("err %v", err)
|
||||
return utils.NewBuildIfError(err, "Copy Dir failed")
|
||||
}
|
||||
|
||||
// Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore.
|
||||
gitignore := ".gitignore"
|
||||
utils.MustCopyFile(filepath.Join(c.AppPath, gitignore), filepath.Join(c.SkeletonPath, gitignore))
|
||||
return utils.CopyFile(filepath.Join(c.AppPath, gitignore), filepath.Join(c.New.SkeletonPath, gitignore))
|
||||
|
||||
}
|
||||
|
||||
@@ -299,4 +360,4 @@ required = ["github.com/revel/cmd/revel"]
|
||||
NEW_MAIN_FILE = `package main
|
||||
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
107
revel/new_test.go
Normal file
107
revel/new_test.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/revel"
|
||||
"github.com/revel/cmd/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// test the commands
|
||||
func TestNew(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
gopath := setup("revel-test-new", a)
|
||||
|
||||
t.Run("New", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("new-test", model.NEW, nil, a)
|
||||
a.Nil(main.Commands[model.NEW].RunWith(c), "New failed")
|
||||
})
|
||||
t.Run("Path", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("new/test/a", model.NEW, nil, a)
|
||||
a.Nil(main.Commands[model.NEW].RunWith(c), "New path failed")
|
||||
})
|
||||
t.Run("Path-Duplicate", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("new/test/b", model.NEW, nil, a)
|
||||
a.Nil(main.Commands[model.NEW].RunWith(c), "New path failed")
|
||||
c = newApp("new/test/b", model.NEW, nil, a)
|
||||
a.NotNil(main.Commands[model.NEW].RunWith(c), "Duplicate path Did Not failed")
|
||||
})
|
||||
t.Run("Skeleton-Git", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("new/test/c/1", model.NEW, nil, a)
|
||||
c.New.SkeletonPath = "git://github.com/revel/cmd:skeleton2"
|
||||
a.NotNil(main.Commands[model.NEW].RunWith(c), "Expected Failed to run with new")
|
||||
// We need to pick a different path
|
||||
c = newApp("new/test/c/2", model.NEW, nil, a)
|
||||
c.New.SkeletonPath = "git://github.com/revel/cmd:skeleton"
|
||||
a.Nil(main.Commands[model.NEW].RunWith(c), "Failed to run with new skeleton git")
|
||||
})
|
||||
t.Run("Skeleton-Go", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("new/test/d", model.NEW, nil, a)
|
||||
c.New.SkeletonPath = "github.com/revel/cmd:skeleton"
|
||||
a.Nil(main.Commands[model.NEW].RunWith(c), "Failed to run with new from go")
|
||||
})
|
||||
if !t.Failed() {
|
||||
if err := os.RemoveAll(gopath); err != nil {
|
||||
a.Fail("Failed to remove test path")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test the commands
|
||||
func TestNewVendor(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
gopath := setup("revel-test-new-vendor", a)
|
||||
precall := func(c *model.CommandConfig) {
|
||||
c.New.Vendored = true
|
||||
}
|
||||
t.Run("New", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("onlyone/v/a", model.NEW, precall, a)
|
||||
c.New.Vendored = true
|
||||
a.Nil(main.Commands[model.NEW].RunWith(c), "New failed")
|
||||
})
|
||||
t.Run("Test", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("onlyone/v/a", model.TEST, nil, a)
|
||||
a.Nil(main.Commands[model.TEST].RunWith(c), "Test failed")
|
||||
})
|
||||
t.Run("Build", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("onlyone/v/a", model.BUILD, nil, a)
|
||||
c.Index = model.BUILD
|
||||
c.Build.TargetPath = filepath.Join(gopath, "src/onlyone/v/a", "target")
|
||||
a.Nil(main.Commands[model.BUILD].RunWith(c), " Build failed")
|
||||
a.True(utils.DirExists(c.Build.TargetPath), "Target folder not made", c.Build.TargetPath)
|
||||
})
|
||||
t.Run("Package", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("onlyone/v/a", model.PACKAGE, nil, a)
|
||||
c.Package.TargetPath = filepath.Join(gopath, "src/onlyone/v/a", "target.tar.gz")
|
||||
a.Nil(main.Commands[model.PACKAGE].RunWith(c), "Package Failed")
|
||||
a.True(utils.Exists(c.Package.TargetPath), "Target package not made", c.Package.TargetPath)
|
||||
})
|
||||
t.Run("TestVendorDir", func(t *testing.T) {
|
||||
// Check to see that no additional packages were downloaded outside the vendor folder
|
||||
files, err := ioutil.ReadDir(gopath)
|
||||
a.Nil(err, "Failed to read gopath folder")
|
||||
// bin/ onlyone/ pkg/ src/
|
||||
a.Equal(3, len(files), "Expected single file in "+gopath)
|
||||
files, err = ioutil.ReadDir(filepath.Join(gopath, "src"))
|
||||
a.Nil(err, "Failed to read src folder")
|
||||
a.Equal(1, len(files), "Expected single file in source folder", filepath.Join(gopath, "src"))
|
||||
})
|
||||
if !t.Failed() {
|
||||
if err := os.RemoveAll(gopath); err != nil {
|
||||
a.Fail("Failed to remove test path")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
var cmdPackage = &Command{
|
||||
UsageLine: "package -i [import path] -r [run mode]",
|
||||
UsageLine: "package [-r [run mode]] [application] ",
|
||||
Short: "package a Revel application (e.g. for deployment)",
|
||||
Long: `
|
||||
Package the Revel web application named by the given import path.
|
||||
@@ -28,7 +28,7 @@ Run mode defaults to "dev".
|
||||
|
||||
For example:
|
||||
|
||||
revel package -i github.com/revel/examples/chat
|
||||
revel package github.com/revel/examples/chat
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -40,19 +40,16 @@ func init() {
|
||||
// Called when unable to parse the command line automatically and assumes an old launch
|
||||
func updatePackageConfig(c *model.CommandConfig, args []string) bool {
|
||||
c.Index = model.PACKAGE
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintf(os.Stderr, cmdPackage.Long)
|
||||
return false
|
||||
}
|
||||
c.Package.ImportPath = args[0]
|
||||
if len(args)>1 {
|
||||
if len(args) > 1 {
|
||||
c.Package.Mode = args[1]
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
func packageApp(c *model.CommandConfig) {
|
||||
// Called to package the app
|
||||
func packageApp(c *model.CommandConfig) (err error) {
|
||||
|
||||
// Determine the run mode.
|
||||
mode := DefaultRunMode
|
||||
@@ -61,13 +58,22 @@ func packageApp(c *model.CommandConfig) {
|
||||
}
|
||||
|
||||
appImportPath := c.ImportPath
|
||||
revel_paths := model.NewRevelPaths(mode, appImportPath, "", model.DoNothingRevelCallback)
|
||||
revel_paths, err := model.NewRevelPaths(mode, appImportPath, "", model.NewWrappedRevelCallback(nil, c.PackageResolver))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Remove the archive if it already exists.
|
||||
destFile := filepath.Base(revel_paths.BasePath) + ".tar.gz"
|
||||
destFile := filepath.Join(c.AppPath, filepath.Base(revel_paths.BasePath)+".tar.gz")
|
||||
if c.Package.TargetPath != "" {
|
||||
if filepath.IsAbs(c.Package.TargetPath) {
|
||||
destFile = c.Package.TargetPath
|
||||
} else {
|
||||
destFile = filepath.Join(c.AppPath, c.Package.TargetPath)
|
||||
}
|
||||
}
|
||||
if err := os.Remove(destFile); err != nil && !os.IsNotExist(err) {
|
||||
utils.Logger.Error("Unable to remove target file","error",err,"file",destFile)
|
||||
os.Exit(1)
|
||||
return utils.NewBuildError("Unable to remove target file", "error", err, "file", destFile)
|
||||
}
|
||||
|
||||
// Collect stuff in a temp directory.
|
||||
@@ -83,7 +89,12 @@ func packageApp(c *model.CommandConfig) {
|
||||
buildApp(c)
|
||||
|
||||
// Create the zip file.
|
||||
archiveName := utils.MustTarGzDir(destFile, tmpDir)
|
||||
|
||||
archiveName, err := utils.TarGzDir(destFile, tmpDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Your archive is ready:", archiveName)
|
||||
return
|
||||
}
|
||||
|
||||
30
revel/package_test.go
Normal file
30
revel/package_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/revel"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// test the commands
|
||||
func TestPackage(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
gopath := setup("revel-test-package", a)
|
||||
|
||||
t.Run("Package", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("package-test", model.NEW, nil, a)
|
||||
main.Commands[model.NEW].RunWith(c)
|
||||
c.Index = model.PACKAGE
|
||||
c.Package.ImportPath = c.ImportPath
|
||||
a.Nil(main.Commands[model.PACKAGE].RunWith(c), "Failed to run package-test")
|
||||
})
|
||||
|
||||
if !t.Failed() {
|
||||
if err := os.RemoveAll(gopath); err != nil {
|
||||
a.Fail("Failed to remove test path")
|
||||
}
|
||||
}
|
||||
}
|
||||
265
revel/revel.go
265
revel/revel.go
@@ -8,23 +8,18 @@ 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/logger"
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/utils"
|
||||
"github.com/revel/cmd/logger"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"go/build"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -37,8 +32,8 @@ const (
|
||||
|
||||
// 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)
|
||||
UpdateConfig func(c *model.CommandConfig, args []string) bool
|
||||
RunWith func(c *model.CommandConfig) error
|
||||
UsageLine, Short, Long string
|
||||
}
|
||||
|
||||
@@ -53,7 +48,7 @@ func (cmd *Command) Name() string {
|
||||
}
|
||||
|
||||
// The commands
|
||||
var commands = []*Command{
|
||||
var Commands = []*Command{
|
||||
nil, // Safety net, prevent missing index from running
|
||||
cmdNew,
|
||||
cmdRun,
|
||||
@@ -63,43 +58,61 @@ var commands = []*Command{
|
||||
cmdTest,
|
||||
cmdVersion,
|
||||
}
|
||||
|
||||
func main() {
|
||||
if runtime.GOOS == "windows" {
|
||||
gocolorize.SetPlain(true)
|
||||
}
|
||||
c := &model.CommandConfig{}
|
||||
wd,_ := os.Getwd()
|
||||
wd, _ := os.Getwd()
|
||||
|
||||
utils.InitLogger(wd,logger.LvlError)
|
||||
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)
|
||||
parser := flags.NewParser(c, flags.HelpFlag|flags.PassDoubleDash)
|
||||
if err := ParseArgs(c, parser, os.Args[1:]); err != nil {
|
||||
fmt.Fprint(os.Stderr, err.Error())
|
||||
parser.WriteHelp(os.Stdout)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Switch based on the verbose flag
|
||||
if len(c.Verbose)>1 {
|
||||
utils.InitLogger(wd, logger.LvlDebug)
|
||||
} else if len(c.Verbose)>0 {
|
||||
utils.InitLogger(wd, logger.LvlInfo)
|
||||
} else {
|
||||
utils.InitLogger(wd, logger.LvlWarn)
|
||||
}
|
||||
|
||||
if !c.UpdateImportPath() {
|
||||
utils.Logger.Fatal("Unable to determine application path")
|
||||
}
|
||||
|
||||
command := Commands[c.Index]
|
||||
println("Revel executing:", command.Short)
|
||||
|
||||
// Setting go paths
|
||||
c.InitGoPaths()
|
||||
|
||||
// Setup package resolver
|
||||
c.InitPackageResolver()
|
||||
|
||||
if err := command.RunWith(c); err != nil {
|
||||
utils.Logger.Error("Unable to execute","error",err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the arguments passed into the model.CommandConfig
|
||||
func ParseArgs(c *model.CommandConfig, parser *flags.Parser, args []string) (err error) {
|
||||
var extraArgs []string
|
||||
if ini := flag.String("ini", "none", ""); *ini != "none" {
|
||||
if err = flags.NewIniParser(parser).ParseFile(*ini); err != nil {
|
||||
return
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
if extraArgs, err = parser.ParseArgs(args); err != nil {
|
||||
return
|
||||
} else {
|
||||
switch parser.Active.Name {
|
||||
case "new":
|
||||
@@ -120,182 +133,18 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Switch based on the verbose flag
|
||||
if c.Verbose {
|
||||
utils.InitLogger(wd, logger.LvlDebug)
|
||||
} else {
|
||||
utils.InitLogger(wd, logger.LvlWarn)
|
||||
}
|
||||
|
||||
if c.Index==0 {
|
||||
utils.Logger.Fatal("Unknown command line arguements")
|
||||
}
|
||||
if !c.UpdateImportPath() {
|
||||
utils.Logger.Fatal("Unable to determine application path")
|
||||
}
|
||||
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:])
|
||||
if c.Index == 0 {
|
||||
err = fmt.Errorf("Unknown command %v", extraArgs)
|
||||
} else if len(extraArgs) > 0 {
|
||||
utils.Logger.Info("Found additional arguements, setting them")
|
||||
if !Commands[c.Index].UpdateConfig(c, extraArgs) {
|
||||
err = fmt.Errorf("Invalid command line arguements %v", extraArgs)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
if c.Index != model.VERSION {
|
||||
utils.Logger.Fatal("Abort: could not create a Revel application outside of GOPATH.")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// set go src path
|
||||
c.SrcRoot = filepath.Join(c.SrcRoot, "src")
|
||||
}
|
||||
46
revel/run.go
46
revel/run.go
@@ -15,23 +15,23 @@ import (
|
||||
)
|
||||
|
||||
var cmdRun = &Command{
|
||||
UsageLine: "run [import path] [run mode] [port]",
|
||||
UsageLine: "run [-m [run mode] -p [port]] [import path] ",
|
||||
Short: "run a Revel application",
|
||||
Long: `
|
||||
Run the Revel web application named by the given import path.
|
||||
|
||||
For example, to run the chat room sample application:
|
||||
|
||||
revel run github.com/revel/examples/chat dev
|
||||
revel run -m dev github.com/revel/examples/chat
|
||||
|
||||
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".
|
||||
|
||||
You can set a port as an optional third parameter. For example:
|
||||
You can set a port as well. For example:
|
||||
|
||||
revel run github.com/revel/examples/chat prod 8080`,
|
||||
revel run -m prod -p 8080 github.com/revel/examples/chat `,
|
||||
}
|
||||
|
||||
// RunArgs holds revel run parameters
|
||||
@@ -47,14 +47,23 @@ func init() {
|
||||
}
|
||||
|
||||
func updateRunConfig(c *model.CommandConfig, args []string) bool {
|
||||
|
||||
convertPort := func(value string) int {
|
||||
if value != "" {
|
||||
port, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
utils.Logger.Fatalf("Failed to parse port as integer: %s", c.Run.Port)
|
||||
}
|
||||
return port
|
||||
}
|
||||
return 0
|
||||
}
|
||||
switch len(args) {
|
||||
case 3:
|
||||
// Possible combinations
|
||||
// revel run [import-path] [run-mode] [port]
|
||||
c.Run.ImportPath = args[0]
|
||||
c.Run.Mode = args[1]
|
||||
c.Run.Port = args[2]
|
||||
c.Run.Port = convertPort(args[2])
|
||||
case 2:
|
||||
// Possible combinations
|
||||
// 1. revel run [import-path] [run-mode]
|
||||
@@ -68,7 +77,7 @@ func updateRunConfig(c *model.CommandConfig, args []string) bool {
|
||||
|
||||
if _, err := strconv.Atoi(args[1]); err == nil {
|
||||
// 2nd arg is the port number
|
||||
c.Run.Port = args[1]
|
||||
c.Run.Port = convertPort(args[1])
|
||||
} else {
|
||||
// 2nd arg is the run mode
|
||||
c.Run.Mode = args[1]
|
||||
@@ -76,7 +85,7 @@ func updateRunConfig(c *model.CommandConfig, args []string) bool {
|
||||
} else {
|
||||
// 1st arg is the run mode
|
||||
c.Run.Mode = args[0]
|
||||
c.Run.Port = args[1]
|
||||
c.Run.Port = convertPort(args[1])
|
||||
}
|
||||
case 1:
|
||||
// Possible combinations
|
||||
@@ -93,7 +102,7 @@ func updateRunConfig(c *model.CommandConfig, args []string) bool {
|
||||
c.Run.ImportPath = args[0]
|
||||
} else if _, err := strconv.Atoi(args[0]); err == nil {
|
||||
// 1st arg is the port number
|
||||
c.Run.Port = args[0]
|
||||
c.Run.Port = convertPort(args[0])
|
||||
} else {
|
||||
// 1st arg is the run mode
|
||||
c.Run.Mode = args[0]
|
||||
@@ -105,18 +114,20 @@ func updateRunConfig(c *model.CommandConfig, args []string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func runApp(c *model.CommandConfig) {
|
||||
// Called to run the app
|
||||
func runApp(c *model.CommandConfig) (err error) {
|
||||
if c.Run.Mode == "" {
|
||||
c.Run.Mode = "dev"
|
||||
}
|
||||
|
||||
revel_path := model.NewRevelPaths(c.Run.Mode, c.ImportPath, "", model.DoNothingRevelCallback)
|
||||
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
|
||||
revel_path, err := model.NewRevelPaths(c.Run.Mode, c.ImportPath, "", model.NewWrappedRevelCallback(nil, c.PackageResolver))
|
||||
if err != nil {
|
||||
return utils.NewBuildIfError(err, "Revel paths")
|
||||
}
|
||||
if c.Run.Port > -1 {
|
||||
revel_path.HTTPPort = c.Run.Port
|
||||
} else {
|
||||
c.Run.Port = revel_path.HTTPPort
|
||||
}
|
||||
|
||||
utils.Logger.Infof("Running %s (%s) in %s mode\n", revel_path.AppName, revel_path.ImportPath, revel_path.RunMode)
|
||||
@@ -145,4 +156,5 @@ func runApp(c *model.CommandConfig) {
|
||||
runMode = revel_path.RunMode
|
||||
}
|
||||
app.Cmd(runMode).Run()
|
||||
return
|
||||
}
|
||||
|
||||
21
revel/run_test.go
Normal file
21
revel/run_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// test the commands
|
||||
func TestRun(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
gopath := setup("revel-test-run", a)
|
||||
|
||||
// TODO Testing run
|
||||
|
||||
if !t.Failed() {
|
||||
if err := os.RemoveAll(gopath); err != nil {
|
||||
a.Fail("Failed to remove test path")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
var cmdTest = &Command{
|
||||
UsageLine: "test [import path] [run mode] [suite.method]",
|
||||
UsageLine: "test <import path> [<run mode> <suite.method>]",
|
||||
Short: "run all tests from the command-line",
|
||||
Long: `
|
||||
Run all tests for the Revel app named by the given import path.
|
||||
@@ -71,38 +71,38 @@ func updateTestConfig(c *model.CommandConfig, args []string) bool {
|
||||
}
|
||||
|
||||
// Called to test the application
|
||||
func testApp(c *model.CommandConfig) {
|
||||
var err error
|
||||
|
||||
func testApp(c *model.CommandConfig) (err error) {
|
||||
mode := DefaultRunMode
|
||||
if c.Test.Mode != "" {
|
||||
mode = c.Test.Mode
|
||||
}
|
||||
|
||||
// Find and parse app.conf
|
||||
revel_path := model.NewRevelPaths(mode, c.ImportPath, "", model.DoNothingRevelCallback)
|
||||
revel_path, err := model.NewRevelPaths(mode, c.ImportPath, "", model.NewWrappedRevelCallback(nil, c.PackageResolver))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure that the testrunner is loaded in this mode.
|
||||
// todo checkTestRunner()
|
||||
// todo Ensure that the testrunner is loaded in this mode.
|
||||
|
||||
// Create a directory to hold the test result files.
|
||||
resultPath := filepath.Join(revel_path.BasePath, "test-results")
|
||||
if err = os.RemoveAll(resultPath); err != nil {
|
||||
utils.Logger.Errorf("Failed to remove test result directory %s: %s", resultPath, err)
|
||||
return utils.NewBuildError("Failed to remove test result directory ", "path", resultPath, "error", err)
|
||||
}
|
||||
if err = os.Mkdir(resultPath, 0777); err != nil {
|
||||
utils.Logger.Errorf("Failed to create test result directory %s: %s", resultPath, err)
|
||||
return utils.NewBuildError("Failed to create test result directory ", "path", resultPath, "error", err)
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
utils.Logger.Errorf("Failed to create test result log file: %s", err)
|
||||
return utils.NewBuildError("Failed to create test result log file: ", "error", err)
|
||||
}
|
||||
|
||||
app, reverr := harness.Build(c, revel_path)
|
||||
if reverr != nil {
|
||||
utils.Logger.Errorf("Error building: %s", reverr)
|
||||
return utils.NewBuildIfError(reverr, "Error building: ")
|
||||
}
|
||||
runMode := fmt.Sprintf(`{"mode":"%s","testModeFlag":true, "specialUseFlag":%v}`, app.Paths.RunMode, c.Verbose)
|
||||
if c.HistoricMode {
|
||||
@@ -115,7 +115,7 @@ func testApp(c *model.CommandConfig) {
|
||||
|
||||
// Start the app...
|
||||
if err := cmd.Start(c); err != nil {
|
||||
utils.Logger.Errorf("%s", err)
|
||||
return utils.NewBuildError("Unable to start server", "error", err)
|
||||
}
|
||||
defer cmd.Kill()
|
||||
|
||||
@@ -164,6 +164,8 @@ func testApp(c *model.CommandConfig) {
|
||||
writeResultFile(resultPath, "result.failed", "failed")
|
||||
utils.Logger.Errorf("Some tests failed. See file://%s for results.", resultPath)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Outputs the results to a file
|
||||
@@ -287,7 +289,7 @@ func runTestSuites(paths *model.RevelContainer, baseURL, resultPath string, test
|
||||
err = json.NewDecoder(resp.Body).Decode(&testResult)
|
||||
if err == nil && !testResult.Passed {
|
||||
suiteResult.Passed = false
|
||||
utils.Logger.Error("Test Failed","suite", suite.Name, "test", test.Name)
|
||||
utils.Logger.Error("Test Failed", "suite", suite.Name, "test", test.Name)
|
||||
fmt.Printf(" %s.%s : FAILED\n", suite.Name, test.Name)
|
||||
} else {
|
||||
fmt.Printf(" %s.%s : PASSED\n", suite.Name, test.Name)
|
||||
@@ -306,7 +308,9 @@ func runTestSuites(paths *model.RevelContainer, baseURL, resultPath string, test
|
||||
// Create the result HTML file.
|
||||
suiteResultFilename := filepath.Join(resultPath,
|
||||
fmt.Sprintf("%s.%s.html", suite.Name, strings.ToLower(suiteResultStr)))
|
||||
utils.MustRenderTemplate(suiteResultFilename, resultFilePath, suiteResult)
|
||||
if err := utils.RenderTemplate(suiteResultFilename, resultFilePath, suiteResult); err != nil {
|
||||
utils.Logger.Error("Failed to render template", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &failedResults, overallSuccess
|
||||
|
||||
31
revel/test_test.go
Normal file
31
revel/test_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/revel"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
// test the commands
|
||||
func TestRevelTest(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
gopath := setup("revel-test-test", a)
|
||||
|
||||
t.Run("Test", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("test-test", model.NEW, nil, a)
|
||||
a.Nil(main.Commands[model.NEW].RunWith(c), "Failed to run test-test")
|
||||
c.Index = model.TEST
|
||||
c.Test.ImportPath = c.ImportPath
|
||||
a.Nil(main.Commands[model.TEST].RunWith(c), "Failed to run test-test")
|
||||
})
|
||||
|
||||
if !t.Failed() {
|
||||
if err := os.RemoveAll(gopath); err != nil {
|
||||
a.Fail("Failed to remove test path")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,67 +12,73 @@ import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/revel/cmd/model"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"go/parser"
|
||||
"go/ast"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"github.com/revel/cmd/utils"
|
||||
"github.com/revel/cmd"
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/utils"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var cmdVersion = &Command{
|
||||
UsageLine: "version",
|
||||
UsageLine: "revel version",
|
||||
Short: "displays the Revel Framework and Go version",
|
||||
Long: `
|
||||
Displays the Revel Framework and Go version.
|
||||
|
||||
For example:
|
||||
|
||||
revel version
|
||||
revel version [<application path>]
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdVersion.RunWith = versionApp
|
||||
cmdVersion.UpdateConfig = updateVersionConfig
|
||||
}
|
||||
|
||||
// Update the version
|
||||
func updateVersionConfig(c *model.CommandConfig, args []string) bool {
|
||||
if len(args) > 0 {
|
||||
c.Version.ImportPath = args[0]
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Displays the version of go and Revel
|
||||
func versionApp(c *model.CommandConfig) {
|
||||
func versionApp(c *model.CommandConfig) (err error) {
|
||||
|
||||
var (
|
||||
revelPkg *build.Package
|
||||
err error
|
||||
)
|
||||
if len(c.ImportPath)>0 {
|
||||
appPkg, err := build.Import(c.ImportPath, "", build.FindOnly)
|
||||
if err != nil {
|
||||
utils.Logger.Fatal("Failed to import " + c.ImportPath + " with error:", "error", err)
|
||||
}
|
||||
revelPkg, err = build.Import(model.RevelImportPath, appPkg.Dir, build.FindOnly)
|
||||
} else {
|
||||
revelPkg, err = build.Import(model.RevelImportPath, "" , build.FindOnly)
|
||||
var revelPath, appPath string
|
||||
|
||||
|
||||
appPath, revelPath, err = utils.FindSrcPaths(c.ImportPath, model.RevelImportPath, c.PackageResolver)
|
||||
if err != nil {
|
||||
return utils.NewBuildError("Failed to import "+c.ImportPath+" with error:", "error", err)
|
||||
}
|
||||
revelPath = revelPath + model.RevelImportPath
|
||||
|
||||
fmt.Println("\nRevel Framework")
|
||||
fmt.Println("\nRevel Framework",revelPath, appPath )
|
||||
if err != nil {
|
||||
utils.Logger.Info("Failed to find Revel in GOPATH with error:", "error", err, "gopath", build.Default.GOPATH)
|
||||
fmt.Println("Information not available (not on GOPATH)")
|
||||
} else {
|
||||
utils.Logger.Info("Fullpath to revel", revelPkg.Dir)
|
||||
utils.Logger.Info("Fullpath to revel", "dir", revelPath)
|
||||
fset := token.NewFileSet() // positions are relative to fset
|
||||
|
||||
version, err := ioutil.ReadFile(filepath.Join(revelPkg.Dir, "version.go"))
|
||||
version, err := ioutil.ReadFile(filepath.Join(revelPath, "version.go"))
|
||||
if err != nil {
|
||||
utils.Logger.Errorf("Failed to find Revel version:", "error", err)
|
||||
utils.Logger.Error("Failed to find Revel version:", "error", err, "path", revelPath)
|
||||
}
|
||||
|
||||
// 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)
|
||||
return utils.NewBuildError("Failed to parse Revel version error:", "error", err)
|
||||
}
|
||||
|
||||
// Print the imports from the file's AST.
|
||||
@@ -96,5 +102,19 @@ func versionApp(c *model.CommandConfig) {
|
||||
fmt.Println("Build Date", cmd.BuildDate)
|
||||
fmt.Println("Minimum Go Version", cmd.MinimumGoVersion)
|
||||
|
||||
fmt.Printf("\n %s %s/%s\n\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
fmt.Printf("Compiled By %s %s/%s\n\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
// Extract the goversion detected
|
||||
if len(c.GoCmd) > 0 {
|
||||
cmd := exec.Command(c.GoCmd, "version")
|
||||
cmd.Stdout = os.Stdout
|
||||
if e := cmd.Start(); e != nil {
|
||||
fmt.Println("Go command error ", e)
|
||||
} else {
|
||||
cmd.Wait()
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Go command not found ")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
41
revel/version_test.go
Normal file
41
revel/version_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/revel"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// test the commands
|
||||
func TestVersion(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
gopath := setup("revel-test-version", a)
|
||||
|
||||
t.Run("Version", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("version-test", model.NEW, nil, a)
|
||||
a.Nil(main.Commands[model.NEW].RunWith(c), "Check new")
|
||||
c.Build.ImportPath = c.ImportPath
|
||||
c.Build.TargetPath = filepath.Join(gopath, "build-test", "target")
|
||||
a.Nil(main.Commands[model.BUILD].RunWith(c), "Failed to run build")
|
||||
c.Index = model.VERSION
|
||||
c.Version.ImportPath = c.ImportPath
|
||||
a.Nil(main.Commands[model.VERSION].RunWith(c), "Failed to run version-test")
|
||||
})
|
||||
t.Run("Version-Nobuild", func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
c := newApp("version-test2", model.NEW, nil, a)
|
||||
a.Nil(main.Commands[model.NEW].RunWith(c), "Check new")
|
||||
c.Index = model.VERSION
|
||||
c.Version.ImportPath = c.ImportPath
|
||||
a.Nil(main.Commands[model.VERSION].RunWith(c), "Failed to run version-test")
|
||||
})
|
||||
if !t.Failed() {
|
||||
if err := os.RemoveAll(gopath); err != nil {
|
||||
a.Fail("Failed to remove test path")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
@@ -13,7 +13,6 @@ import (
|
||||
"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 {
|
||||
@@ -147,7 +146,6 @@ func errorSummary(err *utils.Error) (message string) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
//sortbySuiteName sorts the testsuites by name.
|
||||
type sortBySuiteName []interface{}
|
||||
|
||||
|
||||
45
utils/build_error.go
Normal file
45
utils/build_error.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/revel/cmd/logger"
|
||||
)
|
||||
|
||||
type (
|
||||
BuildError struct {
|
||||
Stack interface{}
|
||||
Message string
|
||||
Args []interface{}
|
||||
}
|
||||
)
|
||||
|
||||
// Returns a new builed error
|
||||
func NewBuildError(message string, args ...interface{}) (b *BuildError) {
|
||||
Logger.Info(message, args...)
|
||||
b = &BuildError{}
|
||||
b.Message = message
|
||||
b.Args = args
|
||||
b.Stack = logger.NewCallStack()
|
||||
Logger.Info("Stack", "stack", b.Stack)
|
||||
return b
|
||||
}
|
||||
|
||||
// Returns a new BuildError if err is not nil
|
||||
func NewBuildIfError(err error, message string, args ...interface{}) (b error) {
|
||||
if err != nil {
|
||||
if berr, ok := err.(*BuildError); ok {
|
||||
// This is already a build error so just append the args
|
||||
berr.Args = append(berr.Args, args...)
|
||||
return berr
|
||||
} else {
|
||||
args = append(args, "error", err.Error())
|
||||
b = NewBuildError(message, args...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// BuildError implements Error() string
|
||||
func (b *BuildError) Error() string {
|
||||
return fmt.Sprint(b.Message, b.Args)
|
||||
}
|
||||
27
utils/command.go
Normal file
27
utils/command.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Initialize the command based on the GO environment
|
||||
func CmdInit(c *exec.Cmd, basePath string) {
|
||||
c.Dir = basePath
|
||||
// Go 1.8 fails if we do not include the GOROOT
|
||||
c.Env = []string{"GOPATH=" + build.Default.GOPATH, "PATH=" + GetEnv("PATH"), "GOROOT="+ GetEnv("GOROOT")}
|
||||
|
||||
}
|
||||
|
||||
// Returns an environment variable
|
||||
func GetEnv(name string) string {
|
||||
for _, v := range os.Environ() {
|
||||
split := strings.Split(v, "=")
|
||||
if split[0] == name {
|
||||
return strings.Join(split[1:], "")
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -7,15 +7,22 @@ import (
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
||||
SourceLine struct {
|
||||
Source string
|
||||
Line int
|
||||
IsError bool
|
||||
}
|
||||
)
|
||||
|
||||
// Creates a link based on the configuration setting "errors.link"
|
||||
func (e *Error) SetLink(errorLink string) {
|
||||
@@ -50,6 +57,7 @@ func (e *Error) Error() string {
|
||||
}
|
||||
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 {
|
||||
@@ -72,10 +80,3 @@ func (e *Error) ContextSource() []SourceLine {
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
// SourceLine structure to hold the per-source-line details.
|
||||
type SourceLine struct {
|
||||
Source string
|
||||
Line int
|
||||
IsError bool
|
||||
}
|
||||
|
||||
186
utils/file.go
186
utils/file.go
@@ -1,21 +1,21 @@
|
||||
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"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DirExists returns true if the given path exists and is a directory.
|
||||
func DirExists(filename string) bool {
|
||||
fileInfo, err := os.Stat(filename)
|
||||
return err == nil && fileInfo.IsDir()
|
||||
@@ -39,49 +39,59 @@ func ReadLines(filename string) ([]string, error) {
|
||||
return strings.Split(string(dataBytes), "\n"), nil
|
||||
}
|
||||
|
||||
func MustCopyFile(destFilename, srcFilename string) {
|
||||
// Copy file returns error
|
||||
func CopyFile(destFilename, srcFilename string) (err error) {
|
||||
|
||||
destFile, err := os.Create(destFilename)
|
||||
PanicOnError(err, "Failed to create file "+destFilename)
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to create file", "file", destFilename)
|
||||
}
|
||||
|
||||
srcFile, err := os.Open(srcFilename)
|
||||
PanicOnError(err, "Failed to open file "+srcFilename)
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to open file", "file", srcFilename)
|
||||
}
|
||||
|
||||
_, err = io.Copy(destFile, srcFile)
|
||||
PanicOnError(err,
|
||||
fmt.Sprintf("Failed to copy data from %s to %s", srcFile.Name(), destFile.Name()))
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to copy data", "fromfile", srcFilename, "tofile", destFilename)
|
||||
}
|
||||
|
||||
err = destFile.Close()
|
||||
PanicOnError(err, "Failed to close file "+destFile.Name())
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to close file", "file", destFilename)
|
||||
}
|
||||
|
||||
err = srcFile.Close()
|
||||
PanicOnError(err, "Failed to close file "+srcFile.Name())
|
||||
}
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to close file", "file", srcFilename)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func GenerateTemplate(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
|
||||
return NewBuildIfError(err, "ExecuteTemplate: Execute failed")
|
||||
}
|
||||
sourceCode := b.String()
|
||||
filePath := filepath.Dir(filename)
|
||||
if !DirExists(filePath) {
|
||||
err = os.MkdirAll(filePath, 0777)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
Logger.Fatal("Failed to make directory","dir", filePath, "error", err)
|
||||
return NewBuildIfError(err, "Failed to make directory", "dir", filePath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create the file
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
Logger.Fatal("Failed to create file","error", err)
|
||||
Logger.Fatal("Failed to create file", "error", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
@@ -96,27 +106,41 @@ func MustGenerateTemplate(filename, templateSource string, args map[string]inter
|
||||
}
|
||||
|
||||
// Given the target path and source path and data. A template
|
||||
func MustRenderTemplate(destPath, srcPath string, data interface{}) {
|
||||
func RenderTemplate(destPath, srcPath string, data interface{}) (err error) {
|
||||
tmpl, err := template.ParseFiles(srcPath)
|
||||
PanicOnError(err, "Failed to parse template "+srcPath)
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to parse template "+srcPath)
|
||||
}
|
||||
|
||||
f, err := os.Create(destPath)
|
||||
PanicOnError(err, "Failed to create "+destPath)
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to create ", "path", destPath)
|
||||
}
|
||||
|
||||
err = tmpl.Execute(f, data)
|
||||
PanicOnError(err, "Failed to render template "+srcPath)
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to Render template "+srcPath)
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
PanicOnError(err, "Failed to close "+f.Name())
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to close file stream "+destPath)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Given the target path and source path and data. A template
|
||||
func MustRenderTemplateToStream(output io.Writer, srcPath []string, data interface{}) {
|
||||
func RenderTemplateToStream(output io.Writer, srcPath []string, data interface{}) (err error) {
|
||||
tmpl, err := template.ParseFiles(srcPath...)
|
||||
PanicOnError(err, "Failed to parse template "+srcPath[0])
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to parse template "+srcPath[0])
|
||||
}
|
||||
|
||||
err = tmpl.Execute(output, data)
|
||||
PanicOnError(err, "Failed to render template "+srcPath[0])
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to render template "+srcPath[0])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func MustChmod(filename string, mode os.FileMode) {
|
||||
@@ -127,8 +151,7 @@ func MustChmod(filename string, mode os.FileMode) {
|
||||
// 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))
|
||||
Logger.Panicf("Abort: %s: %s %s\n", msg, revErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +159,7 @@ func PanicOnError(err error, msg string) {
|
||||
// ".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 {
|
||||
func CopyDir(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.
|
||||
@@ -155,26 +178,29 @@ func MustCopyDir(destDir, srcDir string, data map[string]interface{}) error {
|
||||
if info.IsDir() {
|
||||
err := os.MkdirAll(filepath.Join(destDir, relSrcPath), 0777)
|
||||
if !os.IsExist(err) {
|
||||
PanicOnError(err, "Failed to create directory")
|
||||
return NewBuildIfError(err, "Failed to create directory", "path", destDir+"/"+relSrcPath)
|
||||
}
|
||||
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
|
||||
|
||||
return RenderTemplate(destPath[:len(destPath)-len(".template")], srcPath, data)
|
||||
}
|
||||
|
||||
// Else, just copy it over.
|
||||
MustCopyFile(destPath, srcPath)
|
||||
return nil
|
||||
|
||||
return CopyFile(destPath, srcPath)
|
||||
})
|
||||
}
|
||||
|
||||
// Shortcut to fsWalk
|
||||
func Walk(root string, walkFn filepath.WalkFunc) error {
|
||||
return fsWalk(root,root,walkFn)
|
||||
return fsWalk(root, root, walkFn)
|
||||
}
|
||||
|
||||
// Walk the tree using the function
|
||||
func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
|
||||
fsWalkFunc := func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
@@ -214,9 +240,13 @@ func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func MustTarGzDir(destFilename, srcDir string) string {
|
||||
// Tar gz the folder
|
||||
func TarGzDir(destFilename, srcDir string) (name string, err error) {
|
||||
zipFile, err := os.Create(destFilename)
|
||||
PanicOnError(err, "Failed to create archive")
|
||||
if err != nil {
|
||||
return "", NewBuildIfError(err, "Failed to create archive", "file", destFilename)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = zipFile.Close()
|
||||
}()
|
||||
@@ -231,13 +261,16 @@ func MustTarGzDir(destFilename, srcDir string) string {
|
||||
_ = tarWriter.Close()
|
||||
}()
|
||||
|
||||
_ = fsWalk(srcDir, srcDir, func(srcPath string, info os.FileInfo, err error) error {
|
||||
err = 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")
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to read file", "file", srcPath)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = srcFile.Close()
|
||||
}()
|
||||
@@ -248,17 +281,22 @@ func MustTarGzDir(destFilename, srcDir string) string {
|
||||
Mode: int64(info.Mode()),
|
||||
ModTime: info.ModTime(),
|
||||
})
|
||||
PanicOnError(err, "Failed to write tar entry header")
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to write tar entry header", "file", srcPath)
|
||||
}
|
||||
|
||||
_, err = io.Copy(tarWriter, srcFile)
|
||||
PanicOnError(err, "Failed to copy")
|
||||
if err != nil {
|
||||
return NewBuildIfError(err, "Failed to copy file", "file", srcPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return zipFile.Name()
|
||||
return zipFile.Name(), err
|
||||
}
|
||||
|
||||
// Return true if the file exists
|
||||
func Exists(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
return err == nil
|
||||
@@ -278,8 +316,50 @@ func Empty(dirname string) bool {
|
||||
return len(results) == 0
|
||||
}
|
||||
|
||||
func ImportPathFromCurrentDir() string {
|
||||
pwd, _ := os.Getwd()
|
||||
importPath, _ := filepath.Rel(filepath.Join(build.Default.GOPATH, "src"), pwd)
|
||||
return filepath.ToSlash(importPath)
|
||||
// Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory
|
||||
func FindSrcPaths(appImportPath, revelImportPath string, packageResolver func(pkgName string) error) (appSourcePath, revelSourcePath string, err error) {
|
||||
var (
|
||||
gopaths = filepath.SplitList(build.Default.GOPATH)
|
||||
goroot = build.Default.GOROOT
|
||||
)
|
||||
|
||||
if len(gopaths) == 0 {
|
||||
err = errors.New("GOPATH environment variable is not set. " +
|
||||
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
|
||||
return
|
||||
}
|
||||
|
||||
if ContainsString(gopaths, goroot) {
|
||||
err = fmt.Errorf("GOPATH (%s) must not include your GOROOT (%s). "+
|
||||
"Please refer to http://golang.org/doc/code.html to configure your Go environment. ",
|
||||
build.Default.GOPATH, goroot)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
appPkgDir := ""
|
||||
appPkgSrcDir := ""
|
||||
if len(appImportPath)>0 {
|
||||
Logger.Info("Seeking app package","app",appImportPath)
|
||||
appPkg, err := build.Import(appImportPath, "", build.FindOnly)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to import " + appImportPath + " with error %s", err.Error())
|
||||
return "","",err
|
||||
}
|
||||
appPkgDir,appPkgSrcDir =appPkg.Dir, appPkg.SrcRoot
|
||||
}
|
||||
Logger.Info("Seeking remote package","using",appImportPath, "remote",revelImportPath)
|
||||
revelPkg, err := build.Default.Import(revelImportPath, appPkgDir, build.FindOnly)
|
||||
if err != nil {
|
||||
Logger.Info("Resolved called Seeking remote package","using",appImportPath, "remote",revelImportPath)
|
||||
packageResolver(revelImportPath)
|
||||
revelPkg, err = build.Import(revelImportPath, appPkgDir, build.FindOnly)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to find Revel with error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
revelSourcePath, appSourcePath = revelPkg.Dir[:len(revelPkg.Dir)-len(revelImportPath)], appPkgSrcDir
|
||||
return
|
||||
}
|
||||
|
||||
23
utils/log.go
23
utils/log.go
@@ -1,9 +1,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/revel/cmd/logger"
|
||||
"github.com/revel/config"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
@@ -12,16 +12,21 @@ 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")
|
||||
if logLevel == logger.LvlDebug {
|
||||
newContext.SetOption("log.debug.output", "stdout")
|
||||
println("Debug on")
|
||||
} else {
|
||||
newContext.SetOption("log.debug.output", "off")
|
||||
}
|
||||
newContext.SetOption("log.warn.output","stderr")
|
||||
newContext.SetOption("log.error.output","stderr")
|
||||
newContext.SetOption("log.crit.output","stderr")
|
||||
if logLevel >= logger.LvlInfo {
|
||||
newContext.SetOption("log.info.output", "stdout")
|
||||
} else {
|
||||
newContext.SetOption("log.inf.output", "off")
|
||||
}
|
||||
|
||||
newContext.SetOption("log.warn.output", "stderr")
|
||||
newContext.SetOption("log.error.output", "stderr")
|
||||
newContext.SetOption("log.crit.output", "stderr")
|
||||
Logger.SetHandler(logger.InitializeFromConfig(basePath, newContext))
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ package cmd
|
||||
|
||||
const (
|
||||
// Version current Revel Command version
|
||||
Version = "0.20.0-dev"
|
||||
Version = "0.20.1"
|
||||
|
||||
// BuildDate latest commit/release date
|
||||
BuildDate = "2018-02-06"
|
||||
BuildDate = "2018-09-30"
|
||||
|
||||
// MinimumGoVersion minimum required Go version for Revel
|
||||
MinimumGoVersion = ">= go1.8"
|
||||
|
||||
@@ -52,9 +52,9 @@ type Watcher struct {
|
||||
// Creates a new watched based on the container
|
||||
func NewWatcher(paths *model.RevelContainer, eagerRefresh bool) *Watcher {
|
||||
return &Watcher{
|
||||
forceRefresh: true,
|
||||
lastError: -1,
|
||||
paths: paths,
|
||||
forceRefresh: true,
|
||||
lastError: -1,
|
||||
paths: paths,
|
||||
refreshTimerMS: time.Duration(paths.Config.IntDefault("watch.rebuild.delay", 10)),
|
||||
eagerRefresh: eagerRefresh ||
|
||||
paths.DevMode &&
|
||||
|
||||
Reference in New Issue
Block a user