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:
NotZippy
2018-09-27 21:08:40 -07:00
parent 01ccd695d4
commit f4fb2ec091
65 changed files with 2014 additions and 1281 deletions

View File

@@ -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],

View File

@@ -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

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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...))

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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
View 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
View 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")
}

View File

@@ -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.
}

View File

@@ -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
}

View File

@@ -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
View 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
View 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 ""
}

View File

@@ -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
View 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
}

View File

@@ -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
View 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")
}
}
}

View File

@@ -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
View 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
View 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
}

View File

@@ -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
View 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")
}
}
}

View File

@@ -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
View 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")
}
}
}

View File

@@ -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")
}

View File

@@ -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
View 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")
}
}
}

View File

@@ -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
View 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")
}
}
}

View File

@@ -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
View 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")
}
}
}

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -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
View 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
View 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 ""
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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"

View File

@@ -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 &&