mirror of
https://github.com/kevin-DL/revel-cmd.git
synced 2026-01-12 02:55:16 +00:00
Compare commits
108 Commits
v0.12.0
...
release/v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29e594435c | ||
|
|
29c6237caf | ||
|
|
2d4ccf289c | ||
|
|
f38fb6a15d | ||
|
|
637ccbd250 | ||
|
|
2da4734499 | ||
|
|
aa9e0f8600 | ||
|
|
db4054233b | ||
|
|
3907c6575e | ||
|
|
27e9fab270 | ||
|
|
17e7d40d31 | ||
|
|
54ce8d3699 | ||
|
|
8ab98db556 | ||
|
|
baf5e9f848 | ||
|
|
9d57681ae6 | ||
|
|
3f136726db | ||
|
|
c0a515facf | ||
|
|
01494f75fb | ||
|
|
e6b34786bb | ||
|
|
79b2afb5e5 | ||
|
|
5fcde12193 | ||
|
|
ad68773b9e | ||
|
|
e5255cd373 | ||
|
|
3cf6d5094e | ||
|
|
efcd02de37 | ||
|
|
7eda33eb71 | ||
|
|
1c5fb4a6f8 | ||
|
|
a699dab33d | ||
|
|
e1776bda3c | ||
|
|
d68b27ae81 | ||
|
|
ce84b78204 | ||
|
|
19ca52182d | ||
|
|
bf30aab381 | ||
|
|
bd4663b651 | ||
|
|
b81860de5f | ||
|
|
d2b1730439 | ||
|
|
95d6366c0d | ||
|
|
0f47be728a | ||
|
|
fb1f8c2706 | ||
|
|
da79330cfb | ||
|
|
6ec49296c9 | ||
|
|
0ec369fd48 | ||
|
|
5eac8fae04 | ||
|
|
3dcee9651d | ||
|
|
6573696992 | ||
|
|
a4888fa407 | ||
|
|
8e4203838a | ||
|
|
e0aa1901b8 | ||
|
|
f6c6c931d3 | ||
|
|
8dac9acf77 | ||
|
|
e634cbfc7d | ||
|
|
adcebf9aca | ||
|
|
7b90dfa83e | ||
|
|
d865cfce90 | ||
|
|
52400dbc3d | ||
|
|
d8c5c0732f | ||
|
|
0381636044 | ||
|
|
fb3980ce9d | ||
|
|
62fc677f3a | ||
|
|
0e6d8d56a2 | ||
|
|
86e9258a8f | ||
|
|
faf7e698f3 | ||
|
|
54ef57e56d | ||
|
|
612de1d3a4 | ||
|
|
28c0df2ff3 | ||
|
|
4b46dbb1a7 | ||
|
|
c18d43d25e | ||
|
|
ab1e84cf41 | ||
|
|
a5c8a8da09 | ||
|
|
5a57eaa743 | ||
|
|
514b078c3d | ||
|
|
e485de7e9c | ||
|
|
4b9e74e1ea | ||
|
|
18b9e8f344 | ||
|
|
e3f469739c | ||
|
|
ced303ede6 | ||
|
|
1f8f106573 | ||
|
|
6005ac35f7 | ||
|
|
b85dd76b3e | ||
|
|
b00267450e | ||
|
|
5282ce262b | ||
|
|
4c628ef3db | ||
|
|
6ebd22021e | ||
|
|
688042ffa5 | ||
|
|
2e0697adf2 | ||
|
|
a7fb140b7a | ||
|
|
6d12b806d3 | ||
|
|
c32b5d19b7 | ||
|
|
e75cbc42cb | ||
|
|
6dbd332d6f | ||
|
|
4f0489d0e6 | ||
|
|
63ec7ed487 | ||
|
|
63b4fbe6d9 | ||
|
|
f9ba83270b | ||
|
|
ac4e5473d2 | ||
|
|
04e8dfdcbf | ||
|
|
a6836f3a41 | ||
|
|
6a02449e48 | ||
|
|
4d8dbe0fe1 | ||
|
|
eaf28d397f | ||
|
|
b2ec002b4d | ||
|
|
8a7b1a81ac | ||
|
|
3c658679bc | ||
|
|
f122160a11 | ||
|
|
7792db37e2 | ||
|
|
95d62c1bbd | ||
|
|
3eccd6ae00 | ||
|
|
0fd5bcfcba |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package harness
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/revel/revel"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
// App contains the configuration for running a Revel app. (Not for the app itself)
|
||||
@@ -19,11 +24,12 @@ type App struct {
|
||||
cmd AppCmd // The last cmd returned.
|
||||
}
|
||||
|
||||
// NewApp returns app instance with binary path in it
|
||||
func NewApp(binPath string) *App {
|
||||
return &App{BinaryPath: binPath}
|
||||
}
|
||||
|
||||
// Return a command to run the app server using the current configuration.
|
||||
// Cmd returns a command to run the app server using the current configuration.
|
||||
func (a *App) Cmd() AppCmd {
|
||||
a.cmd = NewAppCmd(a.BinaryPath, a.Port)
|
||||
return a.cmd
|
||||
@@ -40,6 +46,7 @@ type AppCmd struct {
|
||||
*exec.Cmd
|
||||
}
|
||||
|
||||
// NewAppCmd returns the AppCmd with parameters initialized for running app
|
||||
func NewAppCmd(binPath string, port int) AppCmd {
|
||||
cmd := exec.Command(binPath,
|
||||
fmt.Sprintf("-port=%d", port),
|
||||
@@ -51,11 +58,11 @@ func NewAppCmd(binPath string, port int) AppCmd {
|
||||
|
||||
// Start the app server, and wait until it is ready to serve requests.
|
||||
func (cmd AppCmd) Start() error {
|
||||
listeningWriter := startupListeningWriter{os.Stdout, make(chan bool)}
|
||||
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool)}
|
||||
cmd.Stdout = listeningWriter
|
||||
revel.TRACE.Println("Exec app:", cmd.Path, cmd.Args)
|
||||
revel.RevelLog.Debug("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
||||
if err := cmd.Cmd.Start(); err != nil {
|
||||
revel.ERROR.Fatalln("Error running:", err)
|
||||
revel.RevelLog.Fatal("Error running:", "error", err)
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -63,30 +70,33 @@ func (cmd AppCmd) Start() error {
|
||||
return errors.New("revel/harness: app died")
|
||||
|
||||
case <-time.After(30 * time.Second):
|
||||
revel.RevelLog.Debug("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:
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO remove this unreachable code and document it
|
||||
panic("Impossible")
|
||||
}
|
||||
|
||||
// Run the app server inline. Never returns.
|
||||
func (cmd AppCmd) Run() {
|
||||
revel.TRACE.Println("Exec app:", cmd.Path, cmd.Args)
|
||||
revel.RevelLog.Debug("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
||||
if err := cmd.Cmd.Run(); err != nil {
|
||||
revel.ERROR.Fatalln("Error running:", err)
|
||||
revel.RevelLog.Fatal("Error running:", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Terminate the app server if it's running.
|
||||
// Kill terminates the app server if it's running.
|
||||
func (cmd AppCmd) Kill() {
|
||||
if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) {
|
||||
revel.TRACE.Println("Killing revel server pid", cmd.Process.Pid)
|
||||
revel.RevelLog.Debug("Killing revel server pid", "pid", cmd.Process.Pid)
|
||||
err := cmd.Process.Kill()
|
||||
if err != nil {
|
||||
revel.ERROR.Fatalln("Failed to kill revel server:", err)
|
||||
revel.RevelLog.Fatal("Failed to kill revel server:", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +105,7 @@ func (cmd AppCmd) Kill() {
|
||||
func (cmd AppCmd) waitChan() <-chan struct{} {
|
||||
ch := make(chan struct{}, 1)
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
_ = cmd.Wait()
|
||||
ch <- struct{}{}
|
||||
}()
|
||||
return ch
|
||||
@@ -109,7 +119,7 @@ type startupListeningWriter struct {
|
||||
notifyReady chan bool
|
||||
}
|
||||
|
||||
func (w startupListeningWriter) Write(p []byte) (n int, err error) {
|
||||
func (w *startupListeningWriter) Write(p []byte) (n int, err error) {
|
||||
if w.notifyReady != nil && bytes.Contains(p, []byte("Listening")) {
|
||||
w.notifyReady <- true
|
||||
w.notifyReady = nil
|
||||
|
||||
186
harness/build.go
186
harness/build.go
@@ -1,3 +1,7 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package harness
|
||||
|
||||
import (
|
||||
@@ -12,6 +16,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
@@ -34,7 +39,7 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
||||
|
||||
// Add the db.import to the import paths.
|
||||
if dbImportPath, found := revel.Config.String("db.import"); found {
|
||||
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, dbImportPath)
|
||||
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, strings.Split(dbImportPath,",")...)
|
||||
}
|
||||
|
||||
// Generate two source files.
|
||||
@@ -44,8 +49,8 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
||||
"ImportPaths": calcImportAliases(sourceInfo),
|
||||
"TestSuites": sourceInfo.TestSuites(),
|
||||
}
|
||||
genSource("tmp", "main.go", MAIN, templateArgs)
|
||||
genSource("routes", "routes.go", ROUTES, templateArgs)
|
||||
genSource("tmp", "main.go", RevelMainTemplate, templateArgs)
|
||||
genSource("routes", "routes.go", RevelRoutesTemplate, templateArgs)
|
||||
|
||||
// Read build config.
|
||||
buildTags := revel.Config.StringDefault("build.tags", "")
|
||||
@@ -54,16 +59,50 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
||||
// It relies on the user having "go" installed.
|
||||
goPath, err := exec.LookPath("go")
|
||||
if err != nil {
|
||||
revel.ERROR.Fatalf("Go executable not found in PATH.")
|
||||
revel.RevelLog.Fatalf("Go executable not found in PATH.")
|
||||
}
|
||||
|
||||
// Detect if deps tool should be used (is there a vendor folder ?)
|
||||
useVendor := revel.DirExists(filepath.Join(revel.BasePath, "vendor"))
|
||||
basePath := revel.BasePath
|
||||
for !useVendor {
|
||||
basePath = filepath.Dir(basePath)
|
||||
found := false
|
||||
// Check to see if we are still in the GOPATH
|
||||
for _, path := range filepath.SplitList(build.Default.GOPATH) {
|
||||
if strings.HasPrefix(basePath, path) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
break
|
||||
} else {
|
||||
useVendor = revel.DirExists(filepath.Join(basePath, "vendor"))
|
||||
}
|
||||
}
|
||||
|
||||
var depPath string
|
||||
if useVendor {
|
||||
revel.RevelLog.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
|
||||
revel.RevelLog.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 {
|
||||
revel.RevelLog.Info("No vendor folder detected, not using dependency manager to import files")
|
||||
}
|
||||
|
||||
pkg, err := build.Default.Import(revel.ImportPath, "", build.FindOnly)
|
||||
if err != nil {
|
||||
revel.ERROR.Fatalln("Failure importing", revel.ImportPath)
|
||||
revel.RevelLog.Fatal("Failure importing", "path", revel.ImportPath)
|
||||
}
|
||||
|
||||
// Binary path is a combination of $GOBIN/revel.d directory, app's import path and its name.
|
||||
binName := path.Join(pkg.BinDir, "revel.d", revel.ImportPath, path.Base(revel.BasePath))
|
||||
binName := filepath.Join(pkg.BinDir, "revel.d", revel.ImportPath, filepath.Base(revel.BasePath))
|
||||
|
||||
// Change binary path for Windows build
|
||||
goos := runtime.GOOS
|
||||
@@ -77,9 +116,14 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
||||
gotten := make(map[string]struct{})
|
||||
for {
|
||||
appVersion := getAppVersion()
|
||||
versionLinkerFlags := fmt.Sprintf("-X %s/app.APP_VERSION \"%s\"", revel.ImportPath, appVersion)
|
||||
|
||||
buildTime := time.Now().UTC().Format(time.RFC3339)
|
||||
versionLinkerFlags := fmt.Sprintf("-X %s/app.AppVersion=%s -X %s/app.BuildTime=%s",
|
||||
revel.ImportPath, appVersion, revel.ImportPath, buildTime)
|
||||
|
||||
flags := []string{
|
||||
"build",
|
||||
"-i",
|
||||
"-ldflags", versionLinkerFlags,
|
||||
"-tags", buildTags,
|
||||
"-o", binName}
|
||||
@@ -87,44 +131,63 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
||||
// Add in build flags
|
||||
flags = append(flags, buildFlags...)
|
||||
|
||||
// The main path
|
||||
// This is Go main path
|
||||
// Note: It's not applicable for filepath.* usage
|
||||
flags = append(flags, path.Join(revel.ImportPath, "app", "tmp"))
|
||||
|
||||
buildCmd := exec.Command(goPath, flags...)
|
||||
revel.TRACE.Println("Exec:", buildCmd.Args)
|
||||
revel.RevelLog.Debug("Exec:", "args", buildCmd.Args)
|
||||
output, err := buildCmd.CombinedOutput()
|
||||
|
||||
// If the build succeeded, we're done.
|
||||
if err == nil {
|
||||
return NewApp(binName), nil
|
||||
}
|
||||
revel.ERROR.Println(string(output))
|
||||
revel.RevelLog.Error(string(output))
|
||||
|
||||
// See if it was an import error that we can go get.
|
||||
matches := importErrorPattern.FindStringSubmatch(string(output))
|
||||
matches := importErrorPattern.FindAllStringSubmatch(string(output), -1)
|
||||
if matches == nil {
|
||||
return nil, newCompileError(output)
|
||||
}
|
||||
for _, match := range matches {
|
||||
// Ensure we haven't already tried to go get it.
|
||||
pkgName := match[1]
|
||||
if _, alreadyTried := gotten[pkgName]; alreadyTried {
|
||||
return nil, newCompileError(output)
|
||||
}
|
||||
gotten[pkgName] = struct{}{}
|
||||
|
||||
// Ensure we haven't already tried to go get it.
|
||||
pkgName := matches[1]
|
||||
if _, alreadyTried := gotten[pkgName]; alreadyTried {
|
||||
return nil, newCompileError(output)
|
||||
}
|
||||
gotten[pkgName] = struct{}{}
|
||||
|
||||
// Execute "go get <pkg>"
|
||||
getCmd := exec.Command(goPath, "get", pkgName)
|
||||
revel.TRACE.Println("Exec:", getCmd.Args)
|
||||
getOutput, err := getCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
revel.ERROR.Println(string(getOutput))
|
||||
return nil, newCompileError(output)
|
||||
// Execute "go get <pkg>"
|
||||
// Or dep `dep ensure -add <pkg>` if it is there
|
||||
var getCmd *exec.Cmd
|
||||
if useVendor {
|
||||
if depPath == "" {
|
||||
revel.RevelLog.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")
|
||||
for _, pkg := range matches {
|
||||
revel.RevelLog.Error("Missing package", "package", pkg[1])
|
||||
}
|
||||
}
|
||||
getCmd = exec.Command(depPath, "ensure", "-add", pkgName)
|
||||
} else {
|
||||
getCmd = exec.Command(goPath, "get", pkgName)
|
||||
}
|
||||
revel.RevelLog.Debug("Exec:", "args", getCmd.Args)
|
||||
getOutput, err := getCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
revel.RevelLog.Error(string(getOutput))
|
||||
return nil, newCompileError(output)
|
||||
}
|
||||
}
|
||||
|
||||
// Success getting the import, attempt to build again.
|
||||
}
|
||||
revel.ERROR.Fatalf("Not reachable")
|
||||
|
||||
// TODO remove this unreachable code and document it
|
||||
revel.RevelLog.Fatalf("Not reachable")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -142,17 +205,17 @@ func getAppVersion() string {
|
||||
// Check for the git binary
|
||||
if gitPath, err := exec.LookPath("git"); err == nil {
|
||||
// Check for the .git directory
|
||||
gitDir := path.Join(revel.BasePath, ".git")
|
||||
gitDir := filepath.Join(revel.BasePath, ".git")
|
||||
info, err := os.Stat(gitDir)
|
||||
if (err != nil && os.IsNotExist(err)) || !info.IsDir() {
|
||||
return ""
|
||||
}
|
||||
gitCmd := exec.Command(gitPath, "--git-dir="+gitDir, "describe", "--always", "--dirty")
|
||||
revel.TRACE.Println("Exec:", gitCmd.Args)
|
||||
revel.RevelLog.Debug("Exec:", "args", gitCmd.Args)
|
||||
output, err := gitCmd.Output()
|
||||
|
||||
if err != nil {
|
||||
revel.WARN.Println("Cannot determine git repository version:", err)
|
||||
revel.RevelLog.Warn("Cannot determine git repository version:", "error", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -169,28 +232,35 @@ func cleanSource(dirs ...string) {
|
||||
}
|
||||
|
||||
func cleanDir(dir string) {
|
||||
revel.INFO.Println("Cleaning dir " + dir)
|
||||
tmpPath := path.Join(revel.AppPath, dir)
|
||||
revel.RevelLog.Info("Cleaning dir " + dir)
|
||||
tmpPath := filepath.Join(revel.AppPath, dir)
|
||||
f, err := os.Open(tmpPath)
|
||||
if err != nil {
|
||||
revel.ERROR.Println("Failed to clean dir:", err)
|
||||
if !os.IsNotExist(err) {
|
||||
revel.RevelLog.Error("Failed to clean dir:", "error", err)
|
||||
}
|
||||
} else {
|
||||
defer f.Close()
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
infos, err := f.Readdir(0)
|
||||
if err != nil {
|
||||
revel.ERROR.Println("Failed to clean dir:", err)
|
||||
if !os.IsNotExist(err) {
|
||||
revel.RevelLog.Error("Failed to clean dir:", "error", err)
|
||||
}
|
||||
} else {
|
||||
for _, info := range infos {
|
||||
path := path.Join(tmpPath, info.Name())
|
||||
pathName := filepath.Join(tmpPath, info.Name())
|
||||
if info.IsDir() {
|
||||
err := os.RemoveAll(path)
|
||||
err := os.RemoveAll(pathName)
|
||||
if err != nil {
|
||||
revel.ERROR.Println("Failed to remove dir:", err)
|
||||
revel.RevelLog.Error("Failed to remove dir:", "error", err)
|
||||
}
|
||||
} else {
|
||||
err := os.Remove(path)
|
||||
err := os.Remove(pathName)
|
||||
if err != nil {
|
||||
revel.ERROR.Println("Failed to remove file:", err)
|
||||
revel.RevelLog.Error("Failed to remove file:", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +268,6 @@ func cleanDir(dir string) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// genSource renders the given template to produce source code, which it writes
|
||||
// to the given directory and file.
|
||||
func genSource(dir, filename, templateSource string, args map[string]interface{}) {
|
||||
@@ -208,21 +277,23 @@ func genSource(dir, filename, templateSource string, args map[string]interface{}
|
||||
|
||||
// Create a fresh dir.
|
||||
cleanSource(dir)
|
||||
tmpPath := path.Join(revel.AppPath, dir)
|
||||
tmpPath := filepath.Join(revel.AppPath, dir)
|
||||
err := os.Mkdir(tmpPath, 0777)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
revel.ERROR.Fatalf("Failed to make '%v' directory: %v", dir, err)
|
||||
revel.RevelLog.Fatalf("Failed to make '%v' directory: %v", dir, err)
|
||||
}
|
||||
|
||||
// Create the file
|
||||
file, err := os.Create(path.Join(tmpPath, filename))
|
||||
defer file.Close()
|
||||
file, err := os.Create(filepath.Join(tmpPath, filename))
|
||||
if err != nil {
|
||||
revel.ERROR.Fatalf("Failed to create file: %v", err)
|
||||
revel.RevelLog.Fatalf("Failed to create file: %v", err)
|
||||
}
|
||||
_, err = file.WriteString(sourceCode)
|
||||
if err != nil {
|
||||
revel.ERROR.Fatalf("Failed to write to file: %v", err)
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
if _, err = file.WriteString(sourceCode); err != nil {
|
||||
revel.RevelLog.Fatalf("Failed to write to file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +341,7 @@ func addAlias(aliases map[string]string, importPath, pkgName string) {
|
||||
func makePackageAlias(aliases map[string]string, pkgName string) string {
|
||||
i := 0
|
||||
alias := pkgName
|
||||
for containsValue(aliases, alias) {
|
||||
for containsValue(aliases, alias) || alias == "revel" {
|
||||
alias = fmt.Sprintf("%s%d", pkgName, i)
|
||||
i++
|
||||
}
|
||||
@@ -295,7 +366,7 @@ func newCompileError(output []byte) *revel.Error {
|
||||
errorMatch = regexp.MustCompile(`(?m)^(.*?)\:(\d+)\:\s(.*?)$`).FindSubmatch(output)
|
||||
|
||||
if errorMatch == nil {
|
||||
revel.ERROR.Println("Failed to parse build errors:\n", string(output))
|
||||
revel.RevelLog.Error("Failed to parse build errors", "error", string(output))
|
||||
return &revel.Error{
|
||||
SourceType: "Go code",
|
||||
Title: "Go Compilation Error",
|
||||
@@ -305,7 +376,7 @@ func newCompileError(output []byte) *revel.Error {
|
||||
|
||||
errorMatch = append(errorMatch, errorMatch[3])
|
||||
|
||||
revel.ERROR.Println("Build errors:\n", string(output))
|
||||
revel.RevelLog.Error("Build errors", "errors", string(output))
|
||||
}
|
||||
|
||||
// Read the source for the offending file.
|
||||
@@ -332,7 +403,7 @@ func newCompileError(output []byte) *revel.Error {
|
||||
fileStr, err := revel.ReadLines(absFilename)
|
||||
if err != nil {
|
||||
compileError.MetaError = absFilename + ": " + err.Error()
|
||||
revel.ERROR.Println(compileError.MetaError)
|
||||
revel.RevelLog.Error(compileError.MetaError)
|
||||
return compileError
|
||||
}
|
||||
|
||||
@@ -340,7 +411,8 @@ func newCompileError(output []byte) *revel.Error {
|
||||
return compileError
|
||||
}
|
||||
|
||||
const MAIN = `// GENERATED CODE - DO NOT EDIT
|
||||
// RevelMainTemplate template for app/tmp/main.go
|
||||
const RevelMainTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -364,7 +436,7 @@ var (
|
||||
func main() {
|
||||
flag.Parse()
|
||||
revel.Init(*runMode, *importPath, *srcPath)
|
||||
revel.INFO.Println("Running revel server")
|
||||
revel.AppLog.Info("Running revel server")
|
||||
{{range $i, $c := .Controllers}}
|
||||
revel.RegisterController((*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),
|
||||
[]*revel.MethodType{
|
||||
@@ -394,7 +466,9 @@ func main() {
|
||||
revel.Run(*port)
|
||||
}
|
||||
`
|
||||
const ROUTES = `// GENERATED CODE - DO NOT EDIT
|
||||
|
||||
// RevelRoutesTemplate template for app/conf/routes
|
||||
const RevelRoutesTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||
package routes
|
||||
|
||||
import "github.com/revel/revel"
|
||||
@@ -410,7 +484,7 @@ func (_ t{{$c.StructName}}) {{.Name}}({{range .Args}}
|
||||
args := make(map[string]string)
|
||||
{{range .Args}}
|
||||
revel.Unbind(args, "{{.Name}}", {{.Name}}){{end}}
|
||||
return revel.MainRouter.Reverse("{{$c.StructName}}.{{.Name}}", args).Url
|
||||
return revel.MainRouter.Reverse("{{$c.StructName}}.{{.Name}}", args).URL
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
// The Harness for a Revel program.
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package harness for a Revel Framework.
|
||||
//
|
||||
// It has a couple responsibilities:
|
||||
// It has a following responsibilities:
|
||||
// 1. Parse the user program, generating a main.go file that registers
|
||||
// controller classes and starts the user's server.
|
||||
// 2. Build and run the user program. Show compile errors.
|
||||
// 3. Monitor the user source and re-build / restart the program when necessary.
|
||||
//
|
||||
// Source files are generated in the app/tmp directory.
|
||||
|
||||
package harness
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/revel/revel"
|
||||
"go/build"
|
||||
"io"
|
||||
"net"
|
||||
@@ -22,14 +24,15 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/revel/revel"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
watcher *revel.Watcher
|
||||
doNotWatch = []string{"tmp", "views", "routes"}
|
||||
|
||||
lastRequestHadError int32
|
||||
@@ -42,17 +45,21 @@ type Harness struct {
|
||||
serverHost string
|
||||
port int
|
||||
proxy *httputil.ReverseProxy
|
||||
watcher *revel.Watcher
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
func renderError(w http.ResponseWriter, r *http.Request, err error) {
|
||||
req, resp := revel.NewRequest(r), revel.NewResponse(w)
|
||||
c := revel.NewController(req, resp)
|
||||
c.RenderError(err).Apply(req, resp)
|
||||
func renderError(iw http.ResponseWriter, ir *http.Request, err error) {
|
||||
context := revel.NewGoContext(nil)
|
||||
context.Request.SetRequest(ir)
|
||||
context.Response.SetResponse(iw)
|
||||
c := revel.NewController(context)
|
||||
c.RenderError(err).Apply(c.Request, c.Response)
|
||||
}
|
||||
|
||||
// ServeHTTP handles all requests.
|
||||
// It checks for changes to app, rebuilds if necessary, and forwards the request.
|
||||
func (hp *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Don't rebuild the app for favicon requests.
|
||||
if lastRequestHadError > 0 && r.URL.Path == "/favicon.ico" {
|
||||
return
|
||||
@@ -60,35 +67,43 @@ func (hp *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Flush any change events and rebuild app if necessary.
|
||||
// Render an error page if the rebuild / restart failed.
|
||||
err := watcher.Notify()
|
||||
err := h.watcher.Notify()
|
||||
if err != nil {
|
||||
// In a thread safe manner update the flag so that a request for
|
||||
// /favicon.ico does not trigger a rebuild
|
||||
atomic.CompareAndSwapInt32(&lastRequestHadError, 0, 1)
|
||||
renderError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// In a thread safe manner update the flag so that a request for
|
||||
// /favicon.ico is allowed
|
||||
atomic.CompareAndSwapInt32(&lastRequestHadError, 1, 0)
|
||||
|
||||
// Reverse proxy the request.
|
||||
// (Need special code for websockets, courtesy of bradfitz)
|
||||
if strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
|
||||
proxyWebsocket(w, r, hp.serverHost)
|
||||
proxyWebsocket(w, r, h.serverHost)
|
||||
} else {
|
||||
hp.proxy.ServeHTTP(w, r)
|
||||
h.proxy.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Return a reverse proxy that forwards requests to the given port.
|
||||
// NewHarness method returns a reverse proxy that forwards requests
|
||||
// to the given port.
|
||||
func NewHarness() *Harness {
|
||||
// Get a template loader to render errors.
|
||||
// Prefer the app's views/errors directory, and fall back to the stock error pages.
|
||||
revel.MainTemplateLoader = revel.NewTemplateLoader(
|
||||
[]string{path.Join(revel.RevelPath, "templates")})
|
||||
revel.MainTemplateLoader.Refresh()
|
||||
[]string{filepath.Join(revel.RevelPath, "templates")})
|
||||
if err := revel.MainTemplateLoader.Refresh(); err != nil {
|
||||
revel.RevelLog.Error("Template loader error", "error", err)
|
||||
}
|
||||
|
||||
addr := revel.HttpAddr
|
||||
addr := revel.HTTPAddr
|
||||
port := revel.Config.IntDefault("harness.port", 0)
|
||||
scheme := "http"
|
||||
if revel.HttpSsl {
|
||||
if revel.HTTPSsl {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
@@ -101,29 +116,34 @@ func NewHarness() *Harness {
|
||||
port = getFreePort()
|
||||
}
|
||||
|
||||
serverUrl, _ := url.ParseRequestURI(fmt.Sprintf(scheme+"://%s:%d", addr, port))
|
||||
serverURL, _ := url.ParseRequestURI(fmt.Sprintf(scheme+"://%s:%d", addr, port))
|
||||
|
||||
harness := &Harness{
|
||||
serverHarness := &Harness{
|
||||
port: port,
|
||||
serverHost: serverUrl.String()[len(scheme+"://"):],
|
||||
proxy: httputil.NewSingleHostReverseProxy(serverUrl),
|
||||
serverHost: serverURL.String()[len(scheme+"://"):],
|
||||
proxy: httputil.NewSingleHostReverseProxy(serverURL),
|
||||
mutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
if revel.HttpSsl {
|
||||
harness.proxy.Transport = &http.Transport{
|
||||
if revel.HTTPSsl {
|
||||
serverHarness.proxy.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
}
|
||||
return harness
|
||||
return serverHarness
|
||||
}
|
||||
|
||||
// Rebuild the Revel application and run it on the given port.
|
||||
// Refresh method rebuilds the Revel application and run it on the given port.
|
||||
func (h *Harness) Refresh() (err *revel.Error) {
|
||||
// Allow only one thread to rebuild the process
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if h.app != nil {
|
||||
h.app.Kill()
|
||||
}
|
||||
|
||||
revel.TRACE.Println("Rebuild")
|
||||
revel.RevelLog.Debug("Rebuild Called")
|
||||
h.app, err = Build()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -140,10 +160,14 @@ func (h *Harness) Refresh() (err *revel.Error) {
|
||||
return
|
||||
}
|
||||
|
||||
// WatchDir method returns false to file matches with doNotWatch
|
||||
// otheriwse true
|
||||
func (h *Harness) WatchDir(info os.FileInfo) bool {
|
||||
return !revel.ContainsString(doNotWatch, info.Name())
|
||||
}
|
||||
|
||||
// WatchFile method returns true given filename HasSuffix of ".go"
|
||||
// otheriwse false - implements revel.DiscerningListener
|
||||
func (h *Harness) WatchFile(filename string) bool {
|
||||
return strings.HasSuffix(filename, ".go")
|
||||
}
|
||||
@@ -157,22 +181,26 @@ func (h *Harness) Run() {
|
||||
paths = append(paths, gopaths...)
|
||||
}
|
||||
paths = append(paths, revel.CodePaths...)
|
||||
watcher = revel.NewWatcher()
|
||||
watcher.Listen(h, paths...)
|
||||
h.watcher = revel.NewWatcher()
|
||||
h.watcher.Listen(h, paths...)
|
||||
h.watcher.Notify()
|
||||
|
||||
go func() {
|
||||
addr := fmt.Sprintf("%s:%d", revel.HttpAddr, revel.HttpPort)
|
||||
revel.INFO.Printf("Listening on %s", addr)
|
||||
addr := fmt.Sprintf("%s:%d", revel.HTTPAddr, revel.HTTPPort)
|
||||
revel.RevelLog.Infof("Listening on %s", addr)
|
||||
|
||||
var err error
|
||||
if revel.HttpSsl {
|
||||
err = http.ListenAndServeTLS(addr, revel.HttpSslCert,
|
||||
revel.HttpSslKey, h)
|
||||
if revel.HTTPSsl {
|
||||
err = http.ListenAndServeTLS(
|
||||
addr,
|
||||
revel.HTTPSslCert,
|
||||
revel.HTTPSslKey,
|
||||
h)
|
||||
} else {
|
||||
err = http.ListenAndServe(addr, h)
|
||||
}
|
||||
if err != nil {
|
||||
revel.ERROR.Fatalln("Failed to start reverse proxy:", err)
|
||||
revel.RevelLog.Error("Failed to start reverse proxy:", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -190,13 +218,13 @@ func (h *Harness) Run() {
|
||||
func getFreePort() (port int) {
|
||||
conn, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
revel.ERROR.Fatal(err)
|
||||
revel.RevelLog.Fatal("Unable to fetch a freee port address", "error", err)
|
||||
}
|
||||
|
||||
port = conn.Addr().(*net.TCPAddr).Port
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
revel.ERROR.Fatal(err)
|
||||
revel.RevelLog.Fatal("Unable to close port", "error", err)
|
||||
}
|
||||
return port
|
||||
}
|
||||
@@ -204,10 +232,21 @@ func getFreePort() (port int) {
|
||||
// proxyWebsocket copies data between websocket client and server until one side
|
||||
// closes the connection. (ReverseProxy doesn't work with websocket requests.)
|
||||
func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
|
||||
d, err := net.Dial("tcp", host)
|
||||
var (
|
||||
d net.Conn
|
||||
err error
|
||||
)
|
||||
if revel.HTTPSsl {
|
||||
// since this proxy isn't used in production,
|
||||
// it's OK to set InsecureSkipVerify to true
|
||||
// no need to add another configuration option.
|
||||
d, err = tls.Dial("tcp", host, &tls.Config{InsecureSkipVerify: true})
|
||||
} else {
|
||||
d, err = net.Dial("tcp", host)
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(w, "Error contacting backend server.", 500)
|
||||
revel.ERROR.Printf("Error dialing websocket backend %s: %v", host, err)
|
||||
revel.RevelLog.Error("Error dialing websocket backend ", "host", host, "error", err)
|
||||
return
|
||||
}
|
||||
hj, ok := w.(http.Hijacker)
|
||||
@@ -217,15 +256,21 @@ func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
|
||||
}
|
||||
nc, _, err := hj.Hijack()
|
||||
if err != nil {
|
||||
revel.ERROR.Printf("Hijack error: %v", err)
|
||||
revel.RevelLog.Error("Hijack error", "error", err)
|
||||
return
|
||||
}
|
||||
defer nc.Close()
|
||||
defer d.Close()
|
||||
defer func() {
|
||||
if err = nc.Close(); err != nil {
|
||||
revel.RevelLog.Error("Connection close error", "error", err)
|
||||
}
|
||||
if err = d.Close(); err != nil {
|
||||
revel.RevelLog.Error("Dial close error", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = r.Write(d)
|
||||
if err != nil {
|
||||
revel.ERROR.Printf("Error copying request to target: %v", err)
|
||||
revel.RevelLog.Error("Error copying request to target", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package harness
|
||||
|
||||
// This file handles the app code introspection.
|
||||
@@ -9,12 +13,12 @@ import (
|
||||
"go/parser"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/revel/revel"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// SourceInfo is the top-level struct containing all extracted information
|
||||
@@ -25,7 +29,7 @@ type SourceInfo struct {
|
||||
StructSpecs []*TypeInfo
|
||||
// ValidationKeys provides a two-level lookup. The keys are:
|
||||
// 1. The fully-qualified function name,
|
||||
// e.g. "github.com/revel/samples/chat/app/controllers.(*Application).Action"
|
||||
// e.g. "github.com/revel/examples/chat/app/controllers.(*Application).Action"
|
||||
// 2. Within that func's file, the line number of the (overall) expression statement.
|
||||
// e.g. the line returned from runtime.Caller()
|
||||
// The result of the lookup the name of variable being validated.
|
||||
@@ -44,7 +48,7 @@ type SourceInfo struct {
|
||||
// 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/samples/chat/app/controllers"
|
||||
ImportPath string // e.g. "github.com/revel/examples/chat/app/controllers"
|
||||
PackageName string // e.g. "controllers"
|
||||
MethodSpecs []*MethodSpec
|
||||
|
||||
@@ -60,12 +64,14 @@ type methodCall struct {
|
||||
Names []string
|
||||
}
|
||||
|
||||
// MethodSpec holds the information of one Method
|
||||
type MethodSpec struct {
|
||||
Name string // Name of the method, e.g. "Index"
|
||||
Args []*MethodArg // Argument descriptors
|
||||
RenderCalls []*methodCall // Descriptions of Render() invocations from this Method.
|
||||
}
|
||||
|
||||
// MethodArg holds the information of one argument
|
||||
type MethodArg struct {
|
||||
Name string // Name of the argument.
|
||||
TypeExpr TypeExpr // The name of the type, e.g. "int", "*pkg.UserType"
|
||||
@@ -80,8 +86,9 @@ type embeddedTypeName struct {
|
||||
// receiver.
|
||||
type methodMap map[string][]*MethodSpec
|
||||
|
||||
// Parse the app controllers directory and return a list of the controller types found.
|
||||
// Returns a CompileError if the parsing fails.
|
||||
// ProcessSource parses the app controllers directory and
|
||||
// returns a list of the controller types found.
|
||||
// Otherwise CompileError if the parsing fails.
|
||||
func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
|
||||
var (
|
||||
srcInfo *SourceInfo
|
||||
@@ -91,14 +98,14 @@ func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
|
||||
for _, root := range roots {
|
||||
rootImportPath := importPathFromPath(root)
|
||||
if rootImportPath == "" {
|
||||
revel.WARN.Println("Skipping code path", root)
|
||||
revel.RevelLog.Warn("Skipping empty code path", "path", root)
|
||||
continue
|
||||
}
|
||||
|
||||
// Start walking the directory tree.
|
||||
filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
_ = revel.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Println("Error scanning app source:", err)
|
||||
revel.RevelLog.Error("Error scanning app source:", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -120,7 +127,7 @@ func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
|
||||
}, 0)
|
||||
if err != nil {
|
||||
if errList, ok := err.(scanner.ErrorList); ok {
|
||||
var pos token.Position = errList[0].Pos
|
||||
var pos = errList[0].Pos
|
||||
compileError = &revel.Error{
|
||||
SourceType: ".go source",
|
||||
Title: "Go Compilation Error",
|
||||
@@ -139,8 +146,10 @@ func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
|
||||
|
||||
return compileError
|
||||
}
|
||||
|
||||
// This is exception, err alredy checked above. Here just a print
|
||||
ast.Print(nil, err)
|
||||
log.Fatalf("Failed to parse dir: %s", err)
|
||||
revel.RevelLog.Fatal("Failed to parse dir", "error", err)
|
||||
}
|
||||
|
||||
// Skip "main" packages.
|
||||
@@ -151,9 +160,21 @@ func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ignore packages that end with _test
|
||||
for i := range pkgs {
|
||||
if len(i) > 6 {
|
||||
if string(i[len(i)-5:]) == "_test" {
|
||||
delete(pkgs, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There should be only one package in this directory.
|
||||
if len(pkgs) > 1 {
|
||||
log.Println("Most unexpected! Multiple packages in a single directory:", pkgs)
|
||||
for i := range pkgs {
|
||||
println("Found package ", i)
|
||||
}
|
||||
revel.RevelLog.Error("Most unexpected! Multiple packages in a single directory:", "packages", pkgs)
|
||||
}
|
||||
|
||||
var pkg *ast.Package
|
||||
@@ -178,7 +199,7 @@ func appendSourceInfo(srcInfo1, srcInfo2 *SourceInfo) *SourceInfo {
|
||||
srcInfo1.InitImportPaths = append(srcInfo1.InitImportPaths, srcInfo2.InitImportPaths...)
|
||||
for k, v := range srcInfo2.ValidationKeys {
|
||||
if _, ok := srcInfo1.ValidationKeys[k]; ok {
|
||||
log.Println("Key conflict when scanning validation calls:", k)
|
||||
revel.RevelLog.Warn("Key conflict when scanning validation calls:", "key", k)
|
||||
continue
|
||||
}
|
||||
srcInfo1.ValidationKeys[k] = v
|
||||
@@ -296,7 +317,7 @@ func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
|
||||
// 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") {
|
||||
revel.TRACE.Println("Could not find import:", fullPath)
|
||||
revel.RevelLog.Debug("Could not find import:", "path", fullPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -315,6 +336,7 @@ func appendStruct(specs []*TypeInfo, pkgImportPath string, pkg *ast.Package, dec
|
||||
if !found {
|
||||
return specs
|
||||
}
|
||||
|
||||
structType := spec.Type.(*ast.StructType)
|
||||
|
||||
// At this point we know it's a type declaration for a struct.
|
||||
@@ -374,7 +396,7 @@ func appendStruct(specs []*TypeInfo, pkgImportPath string, pkg *ast.Package, dec
|
||||
} else {
|
||||
var ok bool
|
||||
if importPath, ok = imports[pkgName]; !ok {
|
||||
log.Print("Failed to find import path for ", pkgName, ".", typeName)
|
||||
revel.RevelLog.Error("Failed to find import path for ", "package", pkgName, "type", typeName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -419,7 +441,7 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
|
||||
if selExpr.Sel.Name != "Result" {
|
||||
return
|
||||
}
|
||||
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != revel.REVEL_IMPORT_PATH {
|
||||
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != revel.RevelImportPath {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -433,12 +455,16 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
|
||||
var importPath string
|
||||
typeExpr := NewTypeExpr(pkgName, field.Type)
|
||||
if !typeExpr.Valid {
|
||||
return // We didn't understand one of the args. Ignore this action. (Already logged)
|
||||
revel.RevelLog.Warnf("Didn't understand argument '%s' of action %s. Ignoring.", name, getFuncName(funcDecl))
|
||||
return // We didn't understand one of the args. Ignore this action.
|
||||
}
|
||||
if typeExpr.PkgName != "" {
|
||||
// Local object
|
||||
if typeExpr.PkgName == pkgName {
|
||||
importPath = pkgImportPath
|
||||
} else if typeExpr.PkgName != "" {
|
||||
var ok bool
|
||||
if importPath, ok = imports[typeExpr.PkgName]; !ok {
|
||||
log.Println("Failed to find import for arg of type:", typeExpr.TypeName(""))
|
||||
revel.RevelLog.Errorf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
|
||||
}
|
||||
}
|
||||
method.Args = append(method.Args, &MethodArg{
|
||||
@@ -472,7 +498,7 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
|
||||
}
|
||||
|
||||
// Add this call's args to the renderArgs.
|
||||
pos := fset.Position(callExpr.Rparen)
|
||||
pos := fset.Position(callExpr.Lparen)
|
||||
methodCall := &methodCall{
|
||||
Line: pos.Line,
|
||||
Names: []string{},
|
||||
@@ -489,7 +515,7 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
|
||||
})
|
||||
|
||||
var recvTypeName string
|
||||
var recvType ast.Expr = funcDecl.Recv.List[0].Type
|
||||
var recvType = funcDecl.Recv.List[0].Type
|
||||
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||
recvTypeName = recvStarType.X.(*ast.Ident).Name
|
||||
} else {
|
||||
@@ -596,7 +622,7 @@ func getValidationParameter(funcDecl *ast.FuncDecl, imports map[string]string) *
|
||||
continue
|
||||
}
|
||||
|
||||
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == revel.REVEL_IMPORT_PATH {
|
||||
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == revel.RevelImportPath {
|
||||
return field.Names[0].Obj
|
||||
}
|
||||
}
|
||||
@@ -624,7 +650,7 @@ func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec,
|
||||
}
|
||||
|
||||
if len(genDecl.Specs) == 0 {
|
||||
revel.WARN.Printf("Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
|
||||
revel.RevelLog.Warnf("Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -637,7 +663,7 @@ func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec,
|
||||
// TypesThatEmbed returns all types that (directly or indirectly) embed the
|
||||
// target type, which must be a fully qualified type name,
|
||||
// e.g. "github.com/revel/revel.Controller"
|
||||
func (s *SourceInfo) TypesThatEmbed(targetType string) (filtered []*TypeInfo) {
|
||||
func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered []*TypeInfo) {
|
||||
// Do a search in the "embedded type graph", starting with the target type.
|
||||
var (
|
||||
nodeQueue = []string{targetType}
|
||||
@@ -669,19 +695,54 @@ func (s *SourceInfo) TypesThatEmbed(targetType string) (filtered []*TypeInfo) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Strip out any specifications that contain a lower case
|
||||
for exit := false; !exit; exit = true {
|
||||
for i, filteredItem := range filtered {
|
||||
if unicode.IsLower([]rune(filteredItem.StructName)[0]) {
|
||||
revel.RevelLog.Debug("Skipping adding spec for unexported type",
|
||||
"type", filteredItem.StructName,
|
||||
"package", filteredItem.ImportPath)
|
||||
filtered = append(filtered[:i], filtered[i+1:]...)
|
||||
exit = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any missed types that where from expected packages
|
||||
for _, spec := range s.StructSpecs {
|
||||
if spec.PackageName == packageFilter {
|
||||
found := false
|
||||
for _, filteredItem := range filtered {
|
||||
if filteredItem.StructName == spec.StructName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
revel.RevelLog.Warn("Type found in package: "+packageFilter+
|
||||
", but did not embed from: "+filepath.Base(targetType),
|
||||
"name", spec.StructName, "path", spec.ImportPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ControllerSpecs returns the all the contollers that embeds
|
||||
// `revel.Controller`
|
||||
func (s *SourceInfo) ControllerSpecs() []*TypeInfo {
|
||||
if s.controllerSpecs == nil {
|
||||
s.controllerSpecs = s.TypesThatEmbed(revel.REVEL_IMPORT_PATH + ".Controller")
|
||||
s.controllerSpecs = s.TypesThatEmbed(revel.RevelImportPath+".Controller", "controllers")
|
||||
}
|
||||
return s.controllerSpecs
|
||||
}
|
||||
|
||||
// TestSuites returns the all the Application tests that embeds
|
||||
// `testing.TestSuite`
|
||||
func (s *SourceInfo) TestSuites() []*TypeInfo {
|
||||
if s.testSuites == nil {
|
||||
s.testSuites = s.TypesThatEmbed(revel.REVEL_IMPORT_PATH + "/testing.TestSuite")
|
||||
s.testSuites = s.TypesThatEmbed(revel.RevelImportPath+"/testing.TestSuite", "testsuite")
|
||||
}
|
||||
return s.testSuites
|
||||
}
|
||||
@@ -704,7 +765,7 @@ func (e TypeExpr) TypeName(pkgOverride string) string {
|
||||
return e.Expr[:e.pkgIndex] + pkgName + "." + e.Expr[e.pkgIndex:]
|
||||
}
|
||||
|
||||
// This returns the syntactic expression for referencing this type in Go.
|
||||
// NewTypeExpr returns the syntactic expression for referencing this type in Go.
|
||||
func NewTypeExpr(pkgName string, expr ast.Expr) TypeExpr {
|
||||
switch t := expr.(type) {
|
||||
case *ast.Ident:
|
||||
@@ -725,40 +786,45 @@ func NewTypeExpr(pkgName string, expr ast.Expr) TypeExpr {
|
||||
e := NewTypeExpr(pkgName, t.Elt)
|
||||
return TypeExpr{"[]" + e.Expr, e.PkgName, e.pkgIndex + 2, e.Valid}
|
||||
default:
|
||||
log.Println("Failed to generate name for field. Make sure the field name is valid.")
|
||||
revel.RevelLog.Error("Failed to generate name for field. Make sure the field name is valid.")
|
||||
}
|
||||
return TypeExpr{Valid: false}
|
||||
}
|
||||
|
||||
var _BUILTIN_TYPES = map[string]struct{}{
|
||||
"bool": struct{}{},
|
||||
"byte": struct{}{},
|
||||
"complex128": struct{}{},
|
||||
"complex64": struct{}{},
|
||||
"error": struct{}{},
|
||||
"float32": struct{}{},
|
||||
"float64": struct{}{},
|
||||
"int": struct{}{},
|
||||
"int16": struct{}{},
|
||||
"int32": struct{}{},
|
||||
"int64": struct{}{},
|
||||
"int8": struct{}{},
|
||||
"rune": struct{}{},
|
||||
"string": struct{}{},
|
||||
"uint": struct{}{},
|
||||
"uint16": struct{}{},
|
||||
"uint32": struct{}{},
|
||||
"uint64": struct{}{},
|
||||
"uint8": struct{}{},
|
||||
"uintptr": struct{}{},
|
||||
var builtInTypes = map[string]struct{}{
|
||||
"bool": {},
|
||||
"byte": {},
|
||||
"complex128": {},
|
||||
"complex64": {},
|
||||
"error": {},
|
||||
"float32": {},
|
||||
"float64": {},
|
||||
"int": {},
|
||||
"int16": {},
|
||||
"int32": {},
|
||||
"int64": {},
|
||||
"int8": {},
|
||||
"rune": {},
|
||||
"string": {},
|
||||
"uint": {},
|
||||
"uint16": {},
|
||||
"uint32": {},
|
||||
"uint64": {},
|
||||
"uint8": {},
|
||||
"uintptr": {},
|
||||
}
|
||||
|
||||
// IsBuiltinType checks the given type is built-in types of Go
|
||||
func IsBuiltinType(name string) bool {
|
||||
_, ok := _BUILTIN_TYPES[name]
|
||||
_, ok := builtInTypes[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func importPathFromPath(root string) string {
|
||||
vendoringPath := revel.BasePath + "/vendor/"
|
||||
if strings.HasPrefix(root, vendoringPath) {
|
||||
return filepath.ToSlash(root[len(vendoringPath):])
|
||||
}
|
||||
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
|
||||
srcPath := filepath.Join(gopath, "src")
|
||||
if strings.HasPrefix(root, srcPath) {
|
||||
@@ -768,10 +834,10 @@ func importPathFromPath(root string) string {
|
||||
|
||||
srcPath := filepath.Join(build.Default.GOROOT, "src", "pkg")
|
||||
if strings.HasPrefix(root, srcPath) {
|
||||
revel.WARN.Println("Code path should be in GOPATH, but is in GOROOT:", root)
|
||||
revel.RevelLog.Warn("Code path should be in GOPATH, but is in GOROOT:", "path", root)
|
||||
return filepath.ToSlash(root[len(srcPath)+1:])
|
||||
}
|
||||
|
||||
revel.ERROR.Println("Unexpected! Code path is not in GOPATH:", root)
|
||||
revel.RevelLog.Error("Unexpected! Code path is not in GOPATH:", "path", root)
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package harness
|
||||
|
||||
import (
|
||||
"github.com/revel/revel"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
const validationKeysSource = `
|
||||
@@ -77,7 +80,7 @@ func TestGetValidationKeys(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, decl := range file.Decls {
|
||||
lineKeys := getValidationKeys(fset, decl.(*ast.FuncDecl), map[string]string{"revel": revel.REVEL_IMPORT_PATH})
|
||||
lineKeys := getValidationKeys(fset, decl.(*ast.FuncDecl), map[string]string{"revel": revel.RevelImportPath})
|
||||
for k, v := range expectedValidationKeys[i] {
|
||||
if lineKeys[k] != v {
|
||||
t.Errorf("Not found - %d: %v - Actual Map: %v", k, v, lineKeys)
|
||||
@@ -91,18 +94,18 @@ func TestGetValidationKeys(t *testing.T) {
|
||||
}
|
||||
|
||||
var TypeExprs = map[string]TypeExpr{
|
||||
"int": TypeExpr{"int", "", 0, true},
|
||||
"*int": TypeExpr{"*int", "", 1, true},
|
||||
"[]int": TypeExpr{"[]int", "", 2, true},
|
||||
"...int": TypeExpr{"[]int", "", 2, true},
|
||||
"[]*int": TypeExpr{"[]*int", "", 3, true},
|
||||
"...*int": TypeExpr{"[]*int", "", 3, true},
|
||||
"MyType": TypeExpr{"MyType", "pkg", 0, true},
|
||||
"*MyType": TypeExpr{"*MyType", "pkg", 1, true},
|
||||
"[]MyType": TypeExpr{"[]MyType", "pkg", 2, true},
|
||||
"...MyType": TypeExpr{"[]MyType", "pkg", 2, true},
|
||||
"[]*MyType": TypeExpr{"[]*MyType", "pkg", 3, true},
|
||||
"...*MyType": TypeExpr{"[]*MyType", "pkg", 3, true},
|
||||
"int": {"int", "", 0, true},
|
||||
"*int": {"*int", "", 1, true},
|
||||
"[]int": {"[]int", "", 2, true},
|
||||
"...int": {"[]int", "", 2, true},
|
||||
"[]*int": {"[]*int", "", 3, true},
|
||||
"...*int": {"[]*int", "", 3, true},
|
||||
"MyType": {"MyType", "pkg", 0, true},
|
||||
"*MyType": {"*MyType", "pkg", 1, true},
|
||||
"[]MyType": {"[]MyType", "pkg", 2, true},
|
||||
"...MyType": {"[]MyType", "pkg", 2, true},
|
||||
"[]*MyType": {"[]*MyType", "pkg", 3, true},
|
||||
"...*MyType": {"[]*MyType", "pkg", 3, true},
|
||||
}
|
||||
|
||||
func TestTypeExpr(t *testing.T) {
|
||||
@@ -125,10 +128,10 @@ func TestTypeExpr(t *testing.T) {
|
||||
}
|
||||
|
||||
if array {
|
||||
expr = &ast.ArrayType{expr.Pos(), nil, expr}
|
||||
expr = &ast.ArrayType{Lbrack: expr.Pos(), Len: nil, Elt: expr}
|
||||
}
|
||||
if ellipsis {
|
||||
expr = &ast.Ellipsis{expr.Pos(), expr}
|
||||
expr = &ast.Ellipsis{Ellipsis: expr.Pos(), Elt: expr}
|
||||
}
|
||||
|
||||
actual := NewTypeExpr("pkg", expr)
|
||||
@@ -139,17 +142,17 @@ func TestTypeExpr(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProcessBookingSource(t *testing.T) {
|
||||
revel.Init("prod", "github.com/revel/samples/booking", "")
|
||||
revel.Init("prod", "github.com/revel/examples/booking", "")
|
||||
sourceInfo, err := ProcessSource([]string{revel.AppPath})
|
||||
if err != nil {
|
||||
t.Fatal("Failed to process booking source with error:", err)
|
||||
}
|
||||
|
||||
CONTROLLER_PKG := "github.com/revel/samples/booking/app/controllers"
|
||||
controllerPackage := "github.com/revel/examples/booking/app/controllers"
|
||||
expectedControllerSpecs := []*TypeInfo{
|
||||
{"GorpController", CONTROLLER_PKG, "controllers", nil, nil},
|
||||
{"Application", CONTROLLER_PKG, "controllers", nil, nil},
|
||||
{"Hotels", CONTROLLER_PKG, "controllers", nil, nil},
|
||||
{"GorpController", controllerPackage, "controllers", nil, nil},
|
||||
{"Application", controllerPackage, "controllers", nil, nil},
|
||||
{"Hotels", controllerPackage, "controllers", nil, nil},
|
||||
}
|
||||
if len(sourceInfo.ControllerSpecs()) != len(expectedControllerSpecs) {
|
||||
t.Errorf("Unexpected number of controllers found. Expected %d, Found %d",
|
||||
@@ -177,8 +180,8 @@ NEXT_TEST:
|
||||
}
|
||||
|
||||
func BenchmarkProcessBookingSource(b *testing.B) {
|
||||
revel.Init("", "github.com/revel/samples/booking", "")
|
||||
revel.TRACE = log.New(ioutil.Discard, "", 0)
|
||||
revel.Init("", "github.com/revel/examples/booking", "")
|
||||
revel.GetRootLogHandler().Disable()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
@@ -1,28 +1,36 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/revel/revel"
|
||||
"github.com/revel/cmd/harness"
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var cmdBuild = &Command{
|
||||
UsageLine: "build [import path] [target path]",
|
||||
UsageLine: "build [import path] [target path] [run mode]",
|
||||
Short: "build a Revel application (e.g. for deployment)",
|
||||
Long: `
|
||||
Build the Revel web application named by the given import path.
|
||||
This allows it to be deployed and run on a machine that lacks a Go installation.
|
||||
|
||||
The run mode is used to select which set of app.conf configuration should
|
||||
apply and may be used to determine logic in the application itself.
|
||||
|
||||
Run mode defaults to "dev".
|
||||
|
||||
WARNING: The target path will be completely deleted, if it already exists!
|
||||
|
||||
For example:
|
||||
|
||||
revel build github.com/revel/samples/chat /tmp/chat
|
||||
revel build github.com/revel/examples/chat /tmp/chat
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -31,24 +39,33 @@ func init() {
|
||||
}
|
||||
|
||||
func buildApp(args []string) {
|
||||
if len(args) != 2 {
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long)
|
||||
return
|
||||
}
|
||||
|
||||
appImportPath, destPath := args[0], args[1]
|
||||
appImportPath, destPath, mode := args[0], args[1], DefaultRunMode
|
||||
if len(args) >= 3 {
|
||||
mode = args[2]
|
||||
}
|
||||
|
||||
if !revel.Initialized {
|
||||
revel.Init("", appImportPath, "")
|
||||
revel.Init(mode, appImportPath, "")
|
||||
}
|
||||
|
||||
// First, verify that it is either already empty or looks like a previous
|
||||
// build (to avoid clobbering anything)
|
||||
if exists(destPath) && !empty(destPath) && !exists(path.Join(destPath, "run.sh")) {
|
||||
if exists(destPath) && !empty(destPath) && !exists(filepath.Join(destPath, "run.sh")) {
|
||||
errorf("Abort: %s exists and does not look like a build directory.", destPath)
|
||||
}
|
||||
|
||||
os.RemoveAll(destPath)
|
||||
os.MkdirAll(destPath, 0777)
|
||||
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
|
||||
revel.RevelLog.Fatal("Remove all error","error", err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(destPath, 0777); err != nil {
|
||||
revel.RevelLog.Fatal("makedir error","error",err)
|
||||
}
|
||||
|
||||
app, reverr := harness.Build()
|
||||
panicOnError(reverr, "Failed to build")
|
||||
@@ -60,14 +77,14 @@ func buildApp(args []string) {
|
||||
// - app
|
||||
|
||||
// Revel and the app are in a directory structure mirroring import path
|
||||
srcPath := path.Join(destPath, "src")
|
||||
destBinaryPath := path.Join(destPath, filepath.Base(app.BinaryPath))
|
||||
tmpRevelPath := path.Join(srcPath, filepath.FromSlash(revel.REVEL_IMPORT_PATH))
|
||||
srcPath := filepath.Join(destPath, "src")
|
||||
destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath))
|
||||
tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(revel.RevelImportPath))
|
||||
mustCopyFile(destBinaryPath, app.BinaryPath)
|
||||
mustChmod(destBinaryPath, 0755)
|
||||
mustCopyDir(path.Join(tmpRevelPath, "conf"), path.Join(revel.RevelPath, "conf"), nil)
|
||||
mustCopyDir(path.Join(tmpRevelPath, "templates"), path.Join(revel.RevelPath, "templates"), nil)
|
||||
mustCopyDir(path.Join(srcPath, filepath.FromSlash(appImportPath)), revel.BasePath, nil)
|
||||
_ = mustCopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revel.RevelPath, "conf"), nil)
|
||||
_ = mustCopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revel.RevelPath, "templates"), nil)
|
||||
_ = mustCopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revel.BasePath, nil)
|
||||
|
||||
// Find all the modules used and copy them over.
|
||||
config := revel.Config.Raw()
|
||||
@@ -84,19 +101,20 @@ func buildApp(args []string) {
|
||||
}
|
||||
modulePath, err := revel.ResolveImportPath(moduleImportPath)
|
||||
if err != nil {
|
||||
revel.ERROR.Fatalln("Failed to load module %s: %s", key[len("module."):], err)
|
||||
revel.RevelLog.Fatalf("Failed to load module %s: %s", key[len("module."):], err)
|
||||
}
|
||||
modulePaths[moduleImportPath] = modulePath
|
||||
}
|
||||
}
|
||||
for importPath, fsPath := range modulePaths {
|
||||
mustCopyDir(path.Join(srcPath, importPath), fsPath, nil)
|
||||
_ = mustCopyDir(filepath.Join(srcPath, importPath), fsPath, nil)
|
||||
}
|
||||
|
||||
tmplData, runShPath := map[string]interface{}{
|
||||
"BinName": filepath.Base(app.BinaryPath),
|
||||
"ImportPath": appImportPath,
|
||||
}, path.Join(destPath, "run.sh")
|
||||
"Mode": mode,
|
||||
}, filepath.Join(destPath, "run.sh")
|
||||
|
||||
mustRenderTemplate(
|
||||
runShPath,
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var cmdClean = &Command{
|
||||
@@ -15,9 +19,9 @@ Clean the Revel web application named by the given import path.
|
||||
|
||||
For example:
|
||||
|
||||
revel clean github.com/revel/samples/chat
|
||||
revel clean github.com/revel/examples/chat
|
||||
|
||||
It removes the app/tmp directory.
|
||||
It removes the app/tmp and app/routes directory.
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -37,12 +41,17 @@ func cleanApp(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Remove the app/tmp directory.
|
||||
tmpDir := path.Join(appPkg.Dir, "app", "tmp")
|
||||
fmt.Println("Removing:", tmpDir)
|
||||
err = os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Abort:", err)
|
||||
return
|
||||
purgeDirs := []string{
|
||||
filepath.Join(appPkg.Dir, "app", "tmp"),
|
||||
filepath.Join(appPkg.Dir, "app", "routes"),
|
||||
}
|
||||
|
||||
for _, dir := range purgeDirs {
|
||||
fmt.Println("Removing:", dir)
|
||||
err = os.RemoveAll(dir)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Abort:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
53
revel/new.go
53
revel/new.go
@@ -1,3 +1,7 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -8,6 +12,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
@@ -44,6 +49,7 @@ var (
|
||||
|
||||
// revel related paths
|
||||
revelPkg *build.Package
|
||||
revelCmdPkg *build.Package
|
||||
appPath string
|
||||
appName string
|
||||
basePath string
|
||||
@@ -96,9 +102,6 @@ func initGoPaths() {
|
||||
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
|
||||
}
|
||||
|
||||
// set go src path
|
||||
srcRoot = filepath.Join(filepath.SplitList(gopath)[0], "src")
|
||||
|
||||
// check for go executable
|
||||
var err error
|
||||
gocmd, err = exec.LookPath("go")
|
||||
@@ -106,27 +109,54 @@ func initGoPaths() {
|
||||
errorf("Go executable not found in PATH.")
|
||||
}
|
||||
|
||||
// revel/revel#1004 choose go path relative to current working directory
|
||||
workingDir, _ := os.Getwd()
|
||||
goPathList := filepath.SplitList(gopath)
|
||||
for _, path := range goPathList {
|
||||
if strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
|
||||
srcRoot = path
|
||||
break
|
||||
}
|
||||
|
||||
path, _ = filepath.EvalSymlinks(path)
|
||||
if len(path) > 0 && strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
|
||||
srcRoot = path
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(srcRoot) == 0 {
|
||||
revel.RevelLog.Fatal("Abort: could not create a Revel application outside of GOPATH.")
|
||||
}
|
||||
|
||||
// set go src path
|
||||
srcRoot = filepath.Join(srcRoot, "src")
|
||||
}
|
||||
|
||||
func setApplicationPath(args []string) {
|
||||
var err error
|
||||
importPath = args[0]
|
||||
if filepath.IsAbs(importPath) {
|
||||
|
||||
// revel/revel#1014 validate relative path, we cannot use built-in functions
|
||||
// since Go import path is valid relative path too.
|
||||
// so check basic part of the path, which is "."
|
||||
if filepath.IsAbs(importPath) || strings.HasPrefix(importPath, ".") {
|
||||
errorf("Abort: '%s' looks like a directory. Please provide a Go import path instead.",
|
||||
importPath)
|
||||
}
|
||||
|
||||
appPath = filepath.Join(srcRoot, filepath.FromSlash(importPath))
|
||||
|
||||
_, err = build.Import(importPath, "", build.FindOnly)
|
||||
if err == nil {
|
||||
if err == nil && !empty(appPath) {
|
||||
errorf("Abort: Import path %s already exists.\n", importPath)
|
||||
}
|
||||
|
||||
revelPkg, err = build.Import(revel.REVEL_IMPORT_PATH, "", build.FindOnly)
|
||||
revelPkg, err = build.Import(revel.RevelImportPath, "", build.FindOnly)
|
||||
if err != nil {
|
||||
errorf("Abort: Could not find Revel source code: %s\n", err)
|
||||
}
|
||||
|
||||
appPath = filepath.Join(srcRoot, filepath.FromSlash(importPath))
|
||||
appName = filepath.Base(appPath)
|
||||
basePath = filepath.ToSlash(filepath.Dir(importPath))
|
||||
|
||||
@@ -163,7 +193,12 @@ func setSkeletonPath(args []string) {
|
||||
|
||||
} else {
|
||||
// use the revel default
|
||||
skeletonPath = filepath.Join(revelPkg.Dir, "skeleton")
|
||||
revelCmdPkg, err = build.Import(RevelCmdImportPath, "", build.FindOnly)
|
||||
if err != nil {
|
||||
errorf("Abort: Could not find Revel Cmd source code: %s\n", err)
|
||||
}
|
||||
|
||||
skeletonPath = filepath.Join(revelCmdPkg.Dir, "revel", "skeleton")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +207,7 @@ func copyNewAppFiles() {
|
||||
err = os.MkdirAll(appPath, 0777)
|
||||
panicOnError(err, "Failed to create directory "+appPath)
|
||||
|
||||
mustCopyDir(appPath, skeletonPath, map[string]interface{}{
|
||||
_ = mustCopyDir(appPath, skeletonPath, map[string]interface{}{
|
||||
// app.conf
|
||||
"AppName": appName,
|
||||
"BasePath": basePath,
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/revel/revel"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var cmdPackage = &Command{
|
||||
UsageLine: "package [import path]",
|
||||
UsageLine: "package [import path] [run mode]",
|
||||
Short: "package a Revel application (e.g. for deployment)",
|
||||
Long: `
|
||||
Package the Revel web application named by the given import path.
|
||||
This allows it to be deployed and run on a machine that lacks a Go installation.
|
||||
|
||||
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".
|
||||
|
||||
For example:
|
||||
|
||||
revel package github.com/revel/samples/chat
|
||||
revel package github.com/revel/examples/chat
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -31,18 +41,26 @@ func packageApp(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine the run mode.
|
||||
mode := DefaultRunMode
|
||||
if len(args) >= 2 {
|
||||
mode = args[1]
|
||||
}
|
||||
|
||||
appImportPath := args[0]
|
||||
revel.Init("", appImportPath, "")
|
||||
revel.Init(mode, appImportPath, "")
|
||||
|
||||
// Remove the archive if it already exists.
|
||||
destFile := filepath.Base(revel.BasePath) + ".tar.gz"
|
||||
os.Remove(destFile)
|
||||
if err := os.Remove(destFile); err != nil && !os.IsNotExist(err) {
|
||||
revel.RevelLog.Fatal("Unable to remove target file","error",err,"file",destFile)
|
||||
}
|
||||
|
||||
// Collect stuff in a temp directory.
|
||||
tmpDir, err := ioutil.TempDir("", filepath.Base(revel.BasePath))
|
||||
panicOnError(err, "Failed to get temp dir")
|
||||
|
||||
buildApp([]string{args[0], tmpDir})
|
||||
buildApp([]string{args[0], tmpDir, mode})
|
||||
|
||||
// Create the zip file.
|
||||
archiveName := mustTarGzDir(destFile, tmpDir)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
{{.BinName}} -importPath {{.ImportPath}} -srcPath %CD%\src -runMode prod
|
||||
{{.BinName}} -importPath {{.ImportPath}} -srcPath %CD%\src -runMode {{.Mode}}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
||||
"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode prod
|
||||
"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}
|
||||
|
||||
19
revel/rev.go
19
revel/rev.go
@@ -1,10 +1,13 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The command line tool for running Revel apps.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/agtorre/gocolorize"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
@@ -12,14 +15,25 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/agtorre/gocolorize"
|
||||
)
|
||||
|
||||
// Cribbed from the genius organization of the "go" command.
|
||||
const (
|
||||
// RevelCmdImportPath Revel framework cmd tool import path
|
||||
RevelCmdImportPath = "github.com/revel/cmd"
|
||||
|
||||
// DefaultRunMode for revel's application
|
||||
DefaultRunMode = "dev"
|
||||
)
|
||||
|
||||
// Command structure cribbed from the genius organization of the "go" command.
|
||||
type Command struct {
|
||||
Run func(args []string)
|
||||
UsageLine, Short, Long string
|
||||
}
|
||||
|
||||
// Name returns command name from usage line
|
||||
func (cmd *Command) Name() string {
|
||||
name := cmd.UsageLine
|
||||
i := strings.Index(name, " ")
|
||||
@@ -36,6 +50,7 @@ var commands = []*Command{
|
||||
cmdPackage,
|
||||
cmdClean,
|
||||
cmdTest,
|
||||
cmdVersion,
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
125
revel/run.go
125
revel/run.go
@@ -1,9 +1,15 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/revel/revel"
|
||||
"github.com/revel/cmd/harness"
|
||||
"go/build"
|
||||
"strconv"
|
||||
|
||||
"github.com/revel/cmd/harness"
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var cmdRun = &Command{
|
||||
@@ -14,7 +20,7 @@ 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/samples/chat dev
|
||||
revel run github.com/revel/examples/chat dev
|
||||
|
||||
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.
|
||||
@@ -23,53 +29,114 @@ Run mode defaults to "dev".
|
||||
|
||||
You can set a port as an optional third parameter. For example:
|
||||
|
||||
revel run github.com/revel/samples/chat prod 8080`,
|
||||
revel run github.com/revel/examples/chat prod 8080`,
|
||||
}
|
||||
|
||||
// RunArgs holds revel run parameters
|
||||
type RunArgs struct {
|
||||
ImportPath string
|
||||
Mode string
|
||||
Port int
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRun.Run = runApp
|
||||
}
|
||||
|
||||
func runApp(args []string) {
|
||||
if len(args) == 0 {
|
||||
errorf("No import path given.\nRun 'revel help run' for usage.\n")
|
||||
func parseRunArgs(args []string) *RunArgs {
|
||||
inputArgs := RunArgs{
|
||||
ImportPath: importPathFromCurrentDir(),
|
||||
Mode: DefaultRunMode,
|
||||
Port: revel.HTTPPort,
|
||||
}
|
||||
|
||||
// Determine the run mode.
|
||||
mode := "dev"
|
||||
if len(args) >= 2 {
|
||||
mode = args[1]
|
||||
}
|
||||
|
||||
// Find and parse app.conf
|
||||
revel.Init(mode, args[0], "")
|
||||
revel.LoadMimeConfig()
|
||||
|
||||
// Determine the override port, if any.
|
||||
port := revel.HttpPort
|
||||
if len(args) == 3 {
|
||||
var err error
|
||||
if port, err = strconv.Atoi(args[2]); err != nil {
|
||||
switch len(args) {
|
||||
case 3:
|
||||
// Possible combinations
|
||||
// revel run [import-path] [run-mode] [port]
|
||||
port, err := strconv.Atoi(args[2])
|
||||
if err != nil {
|
||||
errorf("Failed to parse port as integer: %s", args[2])
|
||||
}
|
||||
inputArgs.ImportPath = args[0]
|
||||
inputArgs.Mode = args[1]
|
||||
inputArgs.Port = port
|
||||
case 2:
|
||||
// Possible combinations
|
||||
// 1. revel run [import-path] [run-mode]
|
||||
// 2. revel run [import-path] [port]
|
||||
// 3. revel run [run-mode] [port]
|
||||
if _, err := build.Import(args[0], "", build.FindOnly); err == nil {
|
||||
// 1st arg is the import path
|
||||
inputArgs.ImportPath = args[0]
|
||||
if port, err := strconv.Atoi(args[1]); err == nil {
|
||||
// 2nd arg is the port number
|
||||
inputArgs.Port = port
|
||||
} else {
|
||||
// 2nd arg is the run mode
|
||||
inputArgs.Mode = args[1]
|
||||
}
|
||||
} else {
|
||||
// 1st arg is the run mode
|
||||
port, err := strconv.Atoi(args[1])
|
||||
if err != nil {
|
||||
errorf("Failed to parse port as integer: %s", args[1])
|
||||
}
|
||||
inputArgs.Mode = args[0]
|
||||
inputArgs.Port = port
|
||||
}
|
||||
case 1:
|
||||
// Possible combinations
|
||||
// 1. revel run [import-path]
|
||||
// 2. revel run [port]
|
||||
// 3. revel run [run-mode]
|
||||
_, err := build.Import(args[0], "", build.FindOnly)
|
||||
if err != nil {
|
||||
revel.RevelLog.Warn("Unable to run using an import path, assuming import path is working directory %s %s", "Argument", args[0], "error", err.Error())
|
||||
}
|
||||
println("Trying to build with", args[0], err)
|
||||
if err == nil {
|
||||
// 1st arg is the import path
|
||||
inputArgs.ImportPath = args[0]
|
||||
} else if port, err := strconv.Atoi(args[0]); err == nil {
|
||||
// 1st arg is the port number
|
||||
inputArgs.Port = port
|
||||
} else {
|
||||
// 1st arg is the run mode
|
||||
inputArgs.Mode = args[0]
|
||||
}
|
||||
}
|
||||
|
||||
revel.INFO.Printf("Running %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, mode)
|
||||
revel.TRACE.Println("Base path:", revel.BasePath)
|
||||
return &inputArgs
|
||||
}
|
||||
|
||||
func runApp(args []string) {
|
||||
runArgs := parseRunArgs(args)
|
||||
|
||||
// Find and parse app.conf
|
||||
revel.Init(runArgs.Mode, runArgs.ImportPath, "")
|
||||
revel.LoadMimeConfig()
|
||||
|
||||
// fallback to default port
|
||||
if runArgs.Port == 0 {
|
||||
runArgs.Port = revel.HTTPPort
|
||||
}
|
||||
|
||||
revel.RevelLog.Infof("Running %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, runArgs.Mode)
|
||||
revel.RevelLog.Debug("Base path:", "path", revel.BasePath)
|
||||
|
||||
// If the app is run in "watched" mode, use the harness to run it.
|
||||
if revel.Config.BoolDefault("watch", true) && revel.Config.BoolDefault("watch.code", true) {
|
||||
revel.TRACE.Println("Running in watched mode.")
|
||||
revel.HttpPort = port
|
||||
revel.RevelLog.Debug("Running in watched mode.")
|
||||
revel.HTTPPort = runArgs.Port
|
||||
harness.NewHarness().Run() // Never returns.
|
||||
}
|
||||
|
||||
// Else, just build and run the app.
|
||||
revel.TRACE.Println("Running in live build mode.")
|
||||
revel.RevelLog.Debug("Running in live build mode.")
|
||||
app, err := harness.Build()
|
||||
if err != nil {
|
||||
errorf("Failed to build app: %s", err)
|
||||
}
|
||||
app.Port = port
|
||||
app.Port = runArgs.Port
|
||||
app.Cmd().Run()
|
||||
}
|
||||
|
||||
3
revel/skeleton/.gitignore
vendored
Normal file
3
revel/skeleton/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
test-results/
|
||||
tmp/
|
||||
routes/
|
||||
43
revel/skeleton/README.md
Normal file
43
revel/skeleton/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Welcome to Revel
|
||||
|
||||
A high-productivity web framework for the [Go language](http://www.golang.org/).
|
||||
|
||||
|
||||
### Start the web server:
|
||||
|
||||
revel run myapp
|
||||
|
||||
### Go to http://localhost:9000/ and you'll see:
|
||||
|
||||
"It works"
|
||||
|
||||
## Code Layout
|
||||
|
||||
The directory structure of a generated Revel application:
|
||||
|
||||
conf/ Configuration directory
|
||||
app.conf Main app configuration file
|
||||
routes Routes definition file
|
||||
|
||||
app/ App sources
|
||||
init.go Interceptor registration
|
||||
controllers/ App controllers go here
|
||||
views/ Templates directory
|
||||
|
||||
messages/ Message files
|
||||
|
||||
public/ Public static assets
|
||||
css/ CSS files
|
||||
js/ Javascript files
|
||||
images/ Image files
|
||||
|
||||
tests/ Test suites
|
||||
|
||||
|
||||
## Help
|
||||
|
||||
* The [Getting Started with Revel](http://revel.github.io/tutorial/gettingstarted.html).
|
||||
* The [Revel guides](http://revel.github.io/manual/index.html).
|
||||
* The [Revel sample apps](http://revel.github.io/examples/index.html).
|
||||
* The [API documentation](https://godoc.org/github.com/revel/revel).
|
||||
|
||||
13
revel/skeleton/app/controllers/app.go
Normal file
13
revel/skeleton/app/controllers/app.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
*revel.Controller
|
||||
}
|
||||
|
||||
func (c App) Index() revel.Result {
|
||||
return c.Render()
|
||||
}
|
||||
57
revel/skeleton/app/init.go
Normal file
57
revel/skeleton/app/init.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var (
|
||||
// AppVersion revel app version (ldflags)
|
||||
AppVersion string
|
||||
|
||||
// BuildTime revel app build-time (ldflags)
|
||||
BuildTime string
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Filters is the default set of global filters.
|
||||
revel.Filters = []revel.Filter{
|
||||
revel.PanicFilter, // Recover from panics and display an error page instead.
|
||||
revel.RouterFilter, // Use the routing table to select the right Action
|
||||
revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
|
||||
revel.ParamsFilter, // Parse parameters into Controller.Params.
|
||||
revel.SessionFilter, // Restore and write the session cookie.
|
||||
revel.FlashFilter, // Restore and write the flash cookie.
|
||||
revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
|
||||
revel.I18nFilter, // Resolve the requested language
|
||||
HeaderFilter, // Add some security based headers
|
||||
revel.InterceptorFilter, // Run interceptors around the action.
|
||||
revel.CompressFilter, // Compress the result.
|
||||
revel.ActionInvoker, // Invoke the action.
|
||||
}
|
||||
|
||||
// Register startup functions with OnAppStart
|
||||
// revel.DevMode and revel.RunMode only work inside of OnAppStart. See Example Startup Script
|
||||
// ( order dependent )
|
||||
// revel.OnAppStart(ExampleStartupScript)
|
||||
// revel.OnAppStart(InitDB)
|
||||
// revel.OnAppStart(FillCache)
|
||||
}
|
||||
|
||||
// HeaderFilter adds common security headers
|
||||
// There is a full implementation of a CSRF filter in
|
||||
// https://github.com/revel/modules/tree/master/csrf
|
||||
var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) {
|
||||
c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN")
|
||||
c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block")
|
||||
c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff")
|
||||
|
||||
fc[0](c, fc[1:]) // Execute the next filter stage.
|
||||
}
|
||||
|
||||
//func ExampleStartupScript() {
|
||||
// // revel.DevMod and revel.RunMode work here
|
||||
// // Use this script to check for dev mode and set dev/prod startup scripts here!
|
||||
// if revel.DevMode == true {
|
||||
// // Dev mode
|
||||
// }
|
||||
//}
|
||||
21
revel/skeleton/app/views/App/Index.html
Normal file
21
revel/skeleton/app/views/App/Index.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{{set . "title" "Home"}}
|
||||
{{template "header.html" .}}
|
||||
|
||||
<header class="jumbotron" style="background-color:#A9F16C">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<h1>It works!</h1>
|
||||
<p></p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
{{template "flash.html" .}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "footer.html" .}}
|
||||
64
revel/skeleton/app/views/debug.html
Normal file
64
revel/skeleton/app/views/debug.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<style type="text/css">
|
||||
#sidebar {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top:69px;
|
||||
max-width: 75%;
|
||||
z-index: 1000;
|
||||
background-color: #fee;
|
||||
border: thin solid grey;
|
||||
padding: 10px;
|
||||
}
|
||||
#toggleSidebar {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 50px;
|
||||
background-color: #fee;
|
||||
}
|
||||
|
||||
</style>
|
||||
<div id="sidebar" style="display:none;">
|
||||
<h4>Available pipelines</h4>
|
||||
<dl>
|
||||
{{ range $index, $value := .}}
|
||||
<dt>{{$index}}</dt>
|
||||
<dd>{{$value}}</dd>
|
||||
{{end}}
|
||||
</dl>
|
||||
<h4>Flash</h4>
|
||||
<dl>
|
||||
{{ range $index, $value := .flash}}
|
||||
<dt>{{$index}}</dt>
|
||||
<dd>{{$value}}</dd>
|
||||
{{end}}
|
||||
</dl>
|
||||
|
||||
<h4>Errors</h4>
|
||||
<dl>
|
||||
{{ range $index, $value := .errors}}
|
||||
<dt>{{$index}}</dt>
|
||||
<dd>{{$value}}</dd>
|
||||
{{end}}
|
||||
</dl>
|
||||
</div>
|
||||
<a id="toggleSidebar" href="#" class="toggles"><i class="glyphicon glyphicon-chevron-left"></i></a>
|
||||
|
||||
<script>
|
||||
$sidebar = 0;
|
||||
$('#toggleSidebar').click(function() {
|
||||
if ($sidebar === 1) {
|
||||
$('#sidebar').hide();
|
||||
$('#toggleSidebar i').addClass('glyphicon-chevron-left');
|
||||
$('#toggleSidebar i').removeClass('glyphicon-chevron-right');
|
||||
$sidebar = 0;
|
||||
}
|
||||
else {
|
||||
$('#sidebar').show();
|
||||
$('#toggleSidebar i').addClass('glyphicon-chevron-right');
|
||||
$('#toggleSidebar i').removeClass('glyphicon-chevron-left');
|
||||
$sidebar = 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
20
revel/skeleton/app/views/errors/404.html
Normal file
20
revel/skeleton/app/views/errors/404.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Not found</title>
|
||||
</head>
|
||||
<body>
|
||||
{{if eq .RunMode "dev"}}
|
||||
{{template "errors/404-dev.html" .}}
|
||||
{{else}}
|
||||
{{with .Error}}
|
||||
<h1>
|
||||
{{.Title}}
|
||||
</h1>
|
||||
<p>
|
||||
{{.Description}}
|
||||
</p>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
16
revel/skeleton/app/views/errors/500.html
Normal file
16
revel/skeleton/app/views/errors/500.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Application error</title>
|
||||
</head>
|
||||
<body>
|
||||
{{if eq .RunMode "dev"}}
|
||||
{{template "errors/500-dev.html" .}}
|
||||
{{else}}
|
||||
<h1>Oops, an error occured</h1>
|
||||
<p>
|
||||
This exception has been logged.
|
||||
</p>
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
18
revel/skeleton/app/views/flash.html
Normal file
18
revel/skeleton/app/views/flash.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{{if .flash.success}}
|
||||
<div class="alert alert-success">
|
||||
{{.flash.success}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if or .errors .flash.error}}
|
||||
<div class="alert alert-danger">
|
||||
{{if .flash.error}}
|
||||
{{.flash.error}}
|
||||
{{end}}
|
||||
<ul style="margin-top:10px;">
|
||||
{{range .errors}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
{{end}}
|
||||
5
revel/skeleton/app/views/footer.html
Normal file
5
revel/skeleton/app/views/footer.html
Normal file
@@ -0,0 +1,5 @@
|
||||
{{if eq .RunMode "dev"}}
|
||||
{{template "debug.html" .}}
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
19
revel/skeleton/app/views/header.html
Normal file
19
revel/skeleton/app/views/header.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.title}}</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="/public/css/bootstrap-3.3.6.min.css">
|
||||
<link rel="shortcut icon" type="image/png" href="/public/img/favicon.png">
|
||||
<script src="/public/js/jquery-2.2.4.min.js"></script>
|
||||
<script src="/public/js/bootstrap-3.3.6.min.js"></script>
|
||||
{{range .moreStyles}}
|
||||
<link rel="stylesheet" type="text/css" href="/public/{{.}}">
|
||||
{{end}}
|
||||
{{range .moreScripts}}
|
||||
<script src="/public/{{.}}" type="text/javascript" charset="utf-8"></script>
|
||||
{{end}}
|
||||
</head>
|
||||
<body>
|
||||
207
revel/skeleton/conf/app.conf.template
Normal file
207
revel/skeleton/conf/app.conf.template
Normal file
@@ -0,0 +1,207 @@
|
||||
################################################################################
|
||||
# Revel configuration file
|
||||
# More info at http://revel.github.io/manual/appconf.html
|
||||
################################################################################
|
||||
|
||||
# Sets `revel.AppName` for use in-app.
|
||||
# Example:
|
||||
# `if revel.AppName {...}`
|
||||
app.name = {{ .AppName }}
|
||||
|
||||
# A secret string which is passed to cryptographically sign the cookie to prevent
|
||||
# (and detect) user modification.
|
||||
# Keep this string secret or users will be able to inject arbitrary cookie values
|
||||
# into your application
|
||||
app.secret = {{ .Secret }}
|
||||
|
||||
# Revel running behind proxy like nginx, haproxy, etc.
|
||||
app.behind.proxy = false
|
||||
|
||||
|
||||
# The IP address on which to listen.
|
||||
http.addr =
|
||||
|
||||
# The port on which to listen.
|
||||
http.port = 9000
|
||||
|
||||
# Whether to use SSL or not.
|
||||
http.ssl = false
|
||||
|
||||
# Path to an X509 certificate file, if using SSL.
|
||||
#http.sslcert =
|
||||
|
||||
# Path to an X509 certificate key, if using SSL.
|
||||
#http.sslkey =
|
||||
|
||||
|
||||
# Timeout specifies a time limit for request (in seconds) made by a single client.
|
||||
# A Timeout of zero means no timeout.
|
||||
http.timeout.read = 90
|
||||
http.timeout.write = 60
|
||||
|
||||
|
||||
# For any cookies set by Revel (Session,Flash,Error) these properties will set
|
||||
# the fields of:
|
||||
# http://golang.org/pkg/net/http/#Cookie
|
||||
#
|
||||
# Each cookie set by Revel is prefixed with this string.
|
||||
cookie.prefix = REVEL
|
||||
|
||||
# A secure cookie has the secure attribute enabled and is only used via HTTPS,
|
||||
# ensuring that the cookie is always encrypted when transmitting from client to
|
||||
# server. This makes the cookie less likely to be exposed to cookie theft via
|
||||
# eavesdropping.
|
||||
#
|
||||
# Defaults to false. If 'http.ssl' is enabled, this will be defaulted to true.
|
||||
# This should only be true when Revel is handling SSL connections. If you are
|
||||
# using a proxy in front of revel (Nginx, Apache, etc), then this should be left
|
||||
# as false.
|
||||
# cookie.secure = false
|
||||
|
||||
# Limit cookie access to a given domain.
|
||||
#cookie.domain =
|
||||
|
||||
# Define when your session cookie expires.
|
||||
# Values:
|
||||
# "720h"
|
||||
# A time duration (http://golang.org/pkg/time/#ParseDuration) after which
|
||||
# the cookie expires and the session is invalid.
|
||||
# "session"
|
||||
# Sets a session cookie which invalidates the session when the user close
|
||||
# the browser.
|
||||
session.expires = 720h
|
||||
|
||||
|
||||
# The date format used by Revel. Possible formats defined by the Go `time`
|
||||
# package (http://golang.org/pkg/time/#Parse)
|
||||
format.date = 2006-01-02
|
||||
format.datetime = 2006-01-02 15:04
|
||||
|
||||
|
||||
# Determines whether the template rendering should use chunked encoding.
|
||||
# Chunked encoding can decrease the time to first byte on the client side by
|
||||
# sending data before the entire template has been fully rendered.
|
||||
results.chunked = false
|
||||
|
||||
|
||||
# The default language of this application.
|
||||
i18n.default_language = en
|
||||
|
||||
# The default format when message is missing.
|
||||
# The original message shows in %s
|
||||
#i18n.unknown_format = "??? %s ???"
|
||||
|
||||
|
||||
# Module to serve static content such as CSS, JavaScript and Media files
|
||||
# Allows Routes like this:
|
||||
# `Static.ServeModule("modulename","public")`
|
||||
module.static = github.com/revel/modules/static
|
||||
|
||||
|
||||
|
||||
################################################################################
|
||||
|
||||
# Section: dev
|
||||
# This section is evaluated when running Revel in dev mode. Like so:
|
||||
# `revel run path/to/myapp`
|
||||
[dev]
|
||||
|
||||
# This sets `revel.DevMode` for use in-app.
|
||||
# Example:
|
||||
# `if revel.DevMode {...}`
|
||||
# or in your templates with
|
||||
# `{{.DevMode}}`
|
||||
# Values:
|
||||
# "true"
|
||||
# Sets `DevMode` to `true`.
|
||||
# "false"
|
||||
# Sets `DevMode` to `false`.
|
||||
mode.dev = true
|
||||
|
||||
|
||||
# Pretty print JSON/XML when calling RenderJSON/RenderXML
|
||||
# Values:
|
||||
# "true"
|
||||
# Enables pretty printing.
|
||||
# "false"
|
||||
# Disables pretty printing.
|
||||
results.pretty = true
|
||||
|
||||
|
||||
# Watch your applicaton files for changes and automatically rebuild
|
||||
# Values:
|
||||
# "true"
|
||||
# Enables auto rebuilding.
|
||||
# "false"
|
||||
# Disables auto rebuilding.
|
||||
watch = true
|
||||
|
||||
|
||||
# Define when to rebuild new changes.
|
||||
# Values:
|
||||
# "normal"
|
||||
# Rebuild when a new request is received and changes have been detected.
|
||||
# "eager"
|
||||
# Rebuild as soon as changes are detected.
|
||||
watch.mode = normal
|
||||
|
||||
# Watch the entire `$GOPATH` for changes.
|
||||
# Values:
|
||||
# "true"
|
||||
# Includes `$GOPATH` in watch path.
|
||||
# "false"
|
||||
# Excludes `$GOPATH` from watch path. Default value.
|
||||
#watch.gopath = true
|
||||
|
||||
|
||||
# Module to run code tests in the browser
|
||||
# See:
|
||||
# http://revel.github.io/manual/testing.html
|
||||
module.testrunner = github.com/revel/modules/testrunner
|
||||
|
||||
|
||||
# Where to log the various Revel logs
|
||||
# Values:
|
||||
# "off"
|
||||
# Disable log output.
|
||||
# "stdout"
|
||||
# Log to OS's standard output.
|
||||
# "stderr"
|
||||
# Log to Os's standard error output. Default value.
|
||||
# "relative/path/to/log"
|
||||
# Log to file.
|
||||
log.all.filter.module.app = stdout # Log all loggers for the application to the stdout
|
||||
log.error.nfilter.module.app = stderr # Everything else that logs an error to stderr
|
||||
log.crit.output = stderr # Everything that logs something as critical goes to this
|
||||
|
||||
# Revel request access log
|
||||
# Access log line format:
|
||||
# INFO 21:53:55 static server-engine.go:169: Request Stats ip=127.0.0.1 path=/public/vendors/datatables.net-buttons/js/buttons.html5.min.js method=GET start=2017/08/31 21:53:55 status=200 duration_seconds=0.0002583 section=requestlog
|
||||
log.request.output = stdout
|
||||
|
||||
|
||||
|
||||
################################################################################
|
||||
# Section: prod
|
||||
# This section is evaluated when running Revel in production mode. Like so:
|
||||
# `revel run path/to/myapp prod`
|
||||
# See:
|
||||
# [dev] section for documentation of the various settings
|
||||
[prod]
|
||||
|
||||
mode.dev = false
|
||||
|
||||
results.pretty = false
|
||||
|
||||
watch = false
|
||||
|
||||
module.testrunner =
|
||||
|
||||
log.warn.output = log/%(app.name)-warn.json # Log all warn messages to file
|
||||
log.error.output = log/%(app.name)-error.json # Log all errors to file
|
||||
log.crit.output = log/%(app.name)-critical.json # Log all critical to file
|
||||
|
||||
# Revel request access log (json format)
|
||||
# Example:
|
||||
# log.request.output = %(app.name)s-request.json
|
||||
log.request.output = log/%(app.name)s-requests.json
|
||||
26
revel/skeleton/conf/routes
Normal file
26
revel/skeleton/conf/routes
Normal file
@@ -0,0 +1,26 @@
|
||||
# Routes Config
|
||||
#
|
||||
# This file defines all application routes (Higher priority routes first)
|
||||
#
|
||||
|
||||
module:testrunner
|
||||
# module:jobs
|
||||
|
||||
|
||||
GET / App.Index
|
||||
|
||||
# Ignore favicon requests
|
||||
GET /favicon.ico 404
|
||||
|
||||
# Map static resources from the /app/public folder to the /public path
|
||||
GET /public/*filepath Static.Serve("public")
|
||||
|
||||
# Catch all, this will route any request into the controller path
|
||||
#
|
||||
# **** WARNING ****
|
||||
# Enabling this exposes any controller and function to the web.
|
||||
# ** This is a serious security issue if used online **
|
||||
#
|
||||
# For rapid development uncomment the following to add new controller.action endpoints
|
||||
# without having to add them to the routes table.
|
||||
# * /:controller/:action :controller.:action
|
||||
7
revel/skeleton/messages/sample.en
Normal file
7
revel/skeleton/messages/sample.en
Normal file
@@ -0,0 +1,7 @@
|
||||
# Sample messages file for the English language (en)
|
||||
# Message file extensions should be ISO 639-1 codes (http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
|
||||
# Sections within each message file can optionally override the defaults using ISO 3166-1 alpha-2 codes (http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
|
||||
# See also:
|
||||
# - http://www.rfc-editor.org/rfc/bcp/bcp47.txt
|
||||
# - http://www.w3.org/International/questions/qa-accept-lang-locales
|
||||
|
||||
5
revel/skeleton/public/css/bootstrap-3.3.6.min.css
vendored
Normal file
5
revel/skeleton/public/css/bootstrap-3.3.6.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
revel/skeleton/public/fonts/glyphicons-halflings-regular.ttf
Normal file
BIN
revel/skeleton/public/fonts/glyphicons-halflings-regular.ttf
Normal file
Binary file not shown.
BIN
revel/skeleton/public/fonts/glyphicons-halflings-regular.woff
Normal file
BIN
revel/skeleton/public/fonts/glyphicons-halflings-regular.woff
Normal file
Binary file not shown.
BIN
revel/skeleton/public/fonts/glyphicons-halflings-regular.woff2
Normal file
BIN
revel/skeleton/public/fonts/glyphicons-halflings-regular.woff2
Normal file
Binary file not shown.
BIN
revel/skeleton/public/img/favicon.png
Normal file
BIN
revel/skeleton/public/img/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
7
revel/skeleton/public/js/bootstrap-3.3.6.min.js
vendored
Normal file
7
revel/skeleton/public/js/bootstrap-3.3.6.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
revel/skeleton/public/js/jquery-2.2.4.min.js
vendored
Normal file
4
revel/skeleton/public/js/jquery-2.2.4.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
23
revel/skeleton/tests/apptest.go
Normal file
23
revel/skeleton/tests/apptest.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"github.com/revel/revel/testing"
|
||||
)
|
||||
|
||||
type AppTest struct {
|
||||
testing.TestSuite
|
||||
}
|
||||
|
||||
func (t *AppTest) Before() {
|
||||
println("Set up")
|
||||
}
|
||||
|
||||
func (t *AppTest) TestThatIndexPageWorks() {
|
||||
t.Get("/")
|
||||
t.AssertOk()
|
||||
t.AssertContentType("text/html; charset=utf-8")
|
||||
}
|
||||
|
||||
func (t *AppTest) After() {
|
||||
println("Tear down")
|
||||
}
|
||||
274
revel/test.go
274
revel/test.go
@@ -1,18 +1,23 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/revel/revel"
|
||||
"github.com/revel/cmd/harness"
|
||||
"github.com/revel/modules/testrunner/app/controllers"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/revel/cmd/harness"
|
||||
"github.com/revel/modules/testrunner/app/controllers"
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var cmdTest = &Command{
|
||||
@@ -23,7 +28,7 @@ Run all tests for the Revel app named by the given import path.
|
||||
|
||||
For example, to run the booking sample application's tests:
|
||||
|
||||
revel test github.com/revel/samples/booking dev
|
||||
revel test github.com/revel/examples/booking dev
|
||||
|
||||
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.
|
||||
@@ -51,7 +56,7 @@ func testApp(args []string) {
|
||||
errorf("No import path given.\nRun 'revel help test' for usage.\n")
|
||||
}
|
||||
|
||||
mode := "dev"
|
||||
mode := DefaultRunMode
|
||||
if len(args) >= 2 {
|
||||
mode = args[1]
|
||||
}
|
||||
@@ -60,25 +65,10 @@ func testApp(args []string) {
|
||||
revel.Init(mode, args[0], "")
|
||||
|
||||
// Ensure that the testrunner is loaded in this mode.
|
||||
testRunnerFound := false
|
||||
for _, module := range revel.Modules {
|
||||
if module.ImportPath == revel.Config.StringDefault("module.testrunner", "github.com/revel/modules/testrunner") {
|
||||
testRunnerFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !testRunnerFound {
|
||||
errorf(`Error: The testrunner module is not running.
|
||||
|
||||
You can add it to a run mode configuration with the following line:
|
||||
|
||||
module.testrunner = github.com/revel/modules/testrunner
|
||||
|
||||
`)
|
||||
}
|
||||
checkTestRunner()
|
||||
|
||||
// Create a directory to hold the test result files.
|
||||
resultPath := path.Join(revel.BasePath, "test-results")
|
||||
resultPath := filepath.Join(revel.BasePath, "test-results")
|
||||
if err = os.RemoveAll(resultPath); err != nil {
|
||||
errorf("Failed to remove test result directory %s: %s", resultPath, err)
|
||||
}
|
||||
@@ -87,9 +77,9 @@ You can add it to a run mode configuration with the following line:
|
||||
}
|
||||
|
||||
// Direct all the output into a file in the test-results directory.
|
||||
file, err := os.OpenFile(path.Join(resultPath, "app.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
file, err := os.OpenFile(filepath.Join(resultPath, "app.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
errorf("Failed to create log file: %s", err)
|
||||
errorf("Failed to create test result log file: %s", err)
|
||||
}
|
||||
|
||||
app, reverr := harness.Build()
|
||||
@@ -107,103 +97,37 @@ You can add it to a run mode configuration with the following line:
|
||||
defer cmd.Kill()
|
||||
revel.INFO.Printf("Testing %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, mode)
|
||||
|
||||
// Get a list of tests.
|
||||
// Since this is the first request to the server, retry/sleep a couple times
|
||||
// in case it hasn't finished starting up yet.
|
||||
var (
|
||||
testSuites []controllers.TestSuiteDesc
|
||||
resp *http.Response
|
||||
baseUrl = fmt.Sprintf("http://127.0.0.1:%d", revel.HttpPort)
|
||||
)
|
||||
for i := 0; ; i++ {
|
||||
if resp, err = http.Get(baseUrl + "/@tests.list"); err == nil {
|
||||
break
|
||||
}
|
||||
if i < 3 {
|
||||
time.Sleep(3 * time.Second)
|
||||
continue
|
||||
}
|
||||
errorf("Failed to request test list: %s", err)
|
||||
var httpAddr = revel.HTTPAddr
|
||||
if httpAddr == "" {
|
||||
httpAddr = "127.0.0.1"
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
json.NewDecoder(resp.Body).Decode(&testSuites)
|
||||
|
||||
var httpProto = "http"
|
||||
if revel.HTTPSsl {
|
||||
httpProto = "https"
|
||||
}
|
||||
|
||||
// Get a list of tests
|
||||
var baseURL = fmt.Sprintf("%s://%s:%d", httpProto, httpAddr, revel.HTTPPort)
|
||||
testSuites, _ := getTestsList(baseURL)
|
||||
|
||||
// If a specific TestSuite[.Method] is specified, only run that suite/test
|
||||
if len(args) == 3 {
|
||||
testSuites = filterTestSuites(testSuites, args[2])
|
||||
}
|
||||
fmt.Printf("\n%d test suite%s to run.\n", len(testSuites), pluralize(len(testSuites), "", "s"))
|
||||
testSuiteCount := len(*testSuites)
|
||||
fmt.Printf("\n%d test suite%s to run.\n", testSuiteCount, pluralize(testSuiteCount, "", "s"))
|
||||
fmt.Println()
|
||||
|
||||
// Load the result template, which we execute for each suite.
|
||||
module, _ := revel.ModuleByName("testrunner")
|
||||
TemplateLoader := revel.NewTemplateLoader([]string{path.Join(module.Path, "app", "views")})
|
||||
if err := TemplateLoader.Refresh(); err != nil {
|
||||
errorf("Failed to compile templates: %s", err)
|
||||
}
|
||||
resultTemplate, err := TemplateLoader.Template("TestRunner/SuiteResult.html")
|
||||
if err != nil {
|
||||
errorf("Failed to load suite result template: %s", err)
|
||||
}
|
||||
|
||||
// Run each suite.
|
||||
var (
|
||||
overallSuccess = true
|
||||
failedResults []controllers.TestSuiteResult
|
||||
)
|
||||
for _, suite := range testSuites {
|
||||
// Print the name of the suite we're running.
|
||||
name := suite.Name
|
||||
if len(name) > 22 {
|
||||
name = name[:19] + "..."
|
||||
}
|
||||
fmt.Printf("%-22s", name)
|
||||
|
||||
// Run every test.
|
||||
startTime := time.Now()
|
||||
suiteResult := controllers.TestSuiteResult{Name: suite.Name, Passed: true}
|
||||
for _, test := range suite.Tests {
|
||||
testUrl := baseUrl + "/@tests/" + suite.Name + "/" + test.Name
|
||||
resp, err := http.Get(testUrl)
|
||||
if err != nil {
|
||||
errorf("Failed to fetch test result at url %s: %s", testUrl, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var testResult controllers.TestResult
|
||||
json.NewDecoder(resp.Body).Decode(&testResult)
|
||||
if !testResult.Passed {
|
||||
suiteResult.Passed = false
|
||||
}
|
||||
suiteResult.Results = append(suiteResult.Results, testResult)
|
||||
}
|
||||
overallSuccess = overallSuccess && suiteResult.Passed
|
||||
|
||||
// Print result. (Just PASSED or FAILED, and the time taken)
|
||||
suiteResultStr, suiteAlert := "PASSED", ""
|
||||
if !suiteResult.Passed {
|
||||
suiteResultStr, suiteAlert = "FAILED", "!"
|
||||
failedResults = append(failedResults, suiteResult)
|
||||
}
|
||||
fmt.Printf("%8s%3s%6ds\n", suiteResultStr, suiteAlert, int(time.Since(startTime).Seconds()))
|
||||
// Create the result HTML file.
|
||||
suiteResultFilename := path.Join(resultPath,
|
||||
fmt.Sprintf("%s.%s.html", suite.Name, strings.ToLower(suiteResultStr)))
|
||||
suiteResultFile, err := os.Create(suiteResultFilename)
|
||||
if err != nil {
|
||||
errorf("Failed to create result file %s: %s", suiteResultFilename, err)
|
||||
}
|
||||
if err = resultTemplate.Render(suiteResultFile, suiteResult); err != nil {
|
||||
errorf("Failed to render result template: %s", err)
|
||||
}
|
||||
}
|
||||
failedResults, overallSuccess := runTestSuites(baseURL, resultPath, testSuites)
|
||||
|
||||
fmt.Println()
|
||||
if overallSuccess {
|
||||
writeResultFile(resultPath, "result.passed", "passed")
|
||||
fmt.Println("All Tests Passed.")
|
||||
} else {
|
||||
for _, failedResult := range failedResults {
|
||||
for _, failedResult := range *failedResults {
|
||||
fmt.Printf("Failures:\n")
|
||||
for _, result := range failedResult.Results {
|
||||
if !result.Passed {
|
||||
@@ -218,8 +142,8 @@ You can add it to a run mode configuration with the following line:
|
||||
}
|
||||
|
||||
func writeResultFile(resultPath, name, content string) {
|
||||
if err := ioutil.WriteFile(path.Join(resultPath, name), []byte(content), 0666); err != nil {
|
||||
errorf("Failed to write result file %s: %s", path.Join(resultPath, name), err)
|
||||
if err := ioutil.WriteFile(filepath.Join(resultPath, name), []byte(content), 0666); err != nil {
|
||||
errorf("Failed to write result file %s: %s", filepath.Join(resultPath, name), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +156,7 @@ func pluralize(num int, singular, plural string) string {
|
||||
|
||||
// Filters test suites and individual tests to match
|
||||
// the parsed command line parameter
|
||||
func filterTestSuites(suites []controllers.TestSuiteDesc, suiteArgument string) []controllers.TestSuiteDesc {
|
||||
func filterTestSuites(suites *[]controllers.TestSuiteDesc, suiteArgument string) *[]controllers.TestSuiteDesc {
|
||||
var suiteName, testName string
|
||||
argArray := strings.Split(suiteArgument, ".")
|
||||
suiteName = argArray[0]
|
||||
@@ -242,20 +166,20 @@ func filterTestSuites(suites []controllers.TestSuiteDesc, suiteArgument string)
|
||||
if len(argArray) == 2 {
|
||||
testName = argArray[1]
|
||||
}
|
||||
for _, suite := range suites {
|
||||
for _, suite := range *suites {
|
||||
if suite.Name != suiteName {
|
||||
continue
|
||||
}
|
||||
if testName == "" {
|
||||
return []controllers.TestSuiteDesc{suite}
|
||||
return &[]controllers.TestSuiteDesc{suite}
|
||||
}
|
||||
// Only run a particular test in a suite
|
||||
for _, test := range suite.Tests {
|
||||
if test.Name != testName {
|
||||
continue
|
||||
}
|
||||
return []controllers.TestSuiteDesc{
|
||||
controllers.TestSuiteDesc{
|
||||
return &[]controllers.TestSuiteDesc{
|
||||
{
|
||||
Name: suite.Name,
|
||||
Tests: []controllers.TestDesc{test},
|
||||
},
|
||||
@@ -266,3 +190,125 @@ func filterTestSuites(suites []controllers.TestSuiteDesc, suiteArgument string)
|
||||
errorf("Couldn't find test suite %s", suiteName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkTestRunner() {
|
||||
testRunnerFound := false
|
||||
for _, module := range revel.Modules {
|
||||
if module.ImportPath == revel.Config.StringDefault("module.testrunner", "github.com/revel/modules/testrunner") {
|
||||
testRunnerFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !testRunnerFound {
|
||||
errorf(`Error: The testrunner module is not running.
|
||||
|
||||
You can add it to a run mode configuration with the following line:
|
||||
|
||||
module.testrunner = github.com/revel/modules/testrunner
|
||||
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
// Get a list of tests from server.
|
||||
// Since this is the first request to the server, retry/sleep a couple times
|
||||
// in case it hasn't finished starting up yet.
|
||||
func getTestsList(baseURL string) (*[]controllers.TestSuiteDesc, error) {
|
||||
var (
|
||||
err error
|
||||
resp *http.Response
|
||||
testSuites []controllers.TestSuiteDesc
|
||||
)
|
||||
for i := 0; ; i++ {
|
||||
if resp, err = http.Get(baseURL + "/@tests.list"); err == nil {
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i < 3 {
|
||||
time.Sleep(3 * time.Second)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
errorf("Failed to request test list: %s", err)
|
||||
} else {
|
||||
errorf("Failed to request test list: non-200 response")
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&testSuites)
|
||||
|
||||
return &testSuites, err
|
||||
}
|
||||
|
||||
func runTestSuites(baseURL, resultPath string, testSuites *[]controllers.TestSuiteDesc) (*[]controllers.TestSuiteResult, bool) {
|
||||
// Load the result template, which we execute for each suite.
|
||||
module, _ := revel.ModuleByName("testrunner")
|
||||
TemplateLoader := revel.NewTemplateLoader([]string{filepath.Join(module.Path, "app", "views")})
|
||||
if err := TemplateLoader.Refresh(); err != nil {
|
||||
errorf("Failed to compile templates: %s", err)
|
||||
}
|
||||
resultTemplate, err := TemplateLoader.Template("TestRunner/SuiteResult.html")
|
||||
if err != nil {
|
||||
errorf("Failed to load suite result template: %s", err)
|
||||
}
|
||||
|
||||
var (
|
||||
overallSuccess = true
|
||||
failedResults []controllers.TestSuiteResult
|
||||
)
|
||||
for _, suite := range *testSuites {
|
||||
// Print the name of the suite we're running.
|
||||
name := suite.Name
|
||||
if len(name) > 22 {
|
||||
name = name[:19] + "..."
|
||||
}
|
||||
fmt.Printf("%-22s", name)
|
||||
|
||||
// Run every test.
|
||||
startTime := time.Now()
|
||||
suiteResult := controllers.TestSuiteResult{Name: suite.Name, Passed: true}
|
||||
for _, test := range suite.Tests {
|
||||
testURL := baseURL + "/@tests/" + suite.Name + "/" + test.Name
|
||||
resp, err := http.Get(testURL)
|
||||
if err != nil {
|
||||
errorf("Failed to fetch test result at url %s: %s", testURL, err)
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
var testResult controllers.TestResult
|
||||
err = json.NewDecoder(resp.Body).Decode(&testResult)
|
||||
if err == nil && !testResult.Passed {
|
||||
suiteResult.Passed = false
|
||||
}
|
||||
suiteResult.Results = append(suiteResult.Results, testResult)
|
||||
}
|
||||
overallSuccess = overallSuccess && suiteResult.Passed
|
||||
|
||||
// Print result. (Just PASSED or FAILED, and the time taken)
|
||||
suiteResultStr, suiteAlert := "PASSED", ""
|
||||
if !suiteResult.Passed {
|
||||
suiteResultStr, suiteAlert = "FAILED", "!"
|
||||
failedResults = append(failedResults, suiteResult)
|
||||
}
|
||||
fmt.Printf("%8s%3s%6ds\n", suiteResultStr, suiteAlert, int(time.Since(startTime).Seconds()))
|
||||
// Create the result HTML file.
|
||||
suiteResultFilename := filepath.Join(resultPath,
|
||||
fmt.Sprintf("%s.%s.html", suite.Name, strings.ToLower(suiteResultStr)))
|
||||
suiteResultFile, err := os.Create(suiteResultFilename)
|
||||
if err != nil {
|
||||
errorf("Failed to create result file %s: %s", suiteResultFilename, err)
|
||||
}
|
||||
if err = resultTemplate.Render(suiteResultFile, suiteResult); err != nil {
|
||||
errorf("Failed to render result template: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &failedResults, overallSuccess
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
@@ -14,7 +18,7 @@ import (
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
// Use a wrapper to differentiate logged panics from unexpected ones.
|
||||
// LoggedError is wrapper to differentiate logged panics from unexpected ones.
|
||||
type LoggedError struct{ error }
|
||||
|
||||
func panicOnError(err error, msg string) {
|
||||
@@ -66,23 +70,11 @@ func mustChmod(filename string, mode os.FileMode) {
|
||||
// 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 {
|
||||
var fullSrcDir string
|
||||
// Handle symlinked directories.
|
||||
f, err := os.Lstat(srcDir)
|
||||
if err == nil && f.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
fullSrcDir, err = os.Readlink(srcDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
fullSrcDir = srcDir
|
||||
}
|
||||
|
||||
return filepath.Walk(fullSrcDir, func(srcPath string, info os.FileInfo, err error) error {
|
||||
return revel.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
|
||||
// Get the relative path from the source base, and the corresponding path in
|
||||
// the dest directory.
|
||||
relSrcPath := strings.TrimLeft(srcPath[len(fullSrcDir):], string(os.PathSeparator))
|
||||
destPath := path.Join(destDir, relSrcPath)
|
||||
relSrcPath := strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator))
|
||||
destPath := filepath.Join(destDir, relSrcPath)
|
||||
|
||||
// Skip dot files and dot directories.
|
||||
if strings.HasPrefix(relSrcPath, ".") {
|
||||
@@ -94,7 +86,7 @@ func mustCopyDir(destDir, srcDir string, data map[string]interface{}) error {
|
||||
|
||||
// Create a subdirectory if necessary.
|
||||
if info.IsDir() {
|
||||
err := os.MkdirAll(path.Join(destDir, relSrcPath), 0777)
|
||||
err := os.MkdirAll(filepath.Join(destDir, relSrcPath), 0777)
|
||||
if !os.IsExist(err) {
|
||||
panicOnError(err, "Failed to create directory")
|
||||
}
|
||||
@@ -116,22 +108,30 @@ func mustCopyDir(destDir, srcDir string, data map[string]interface{}) error {
|
||||
func mustTarGzDir(destFilename, srcDir string) string {
|
||||
zipFile, err := os.Create(destFilename)
|
||||
panicOnError(err, "Failed to create archive")
|
||||
defer zipFile.Close()
|
||||
defer func() {
|
||||
_ = zipFile.Close()
|
||||
}()
|
||||
|
||||
gzipWriter := gzip.NewWriter(zipFile)
|
||||
defer gzipWriter.Close()
|
||||
defer func() {
|
||||
_ = gzipWriter.Close()
|
||||
}()
|
||||
|
||||
tarWriter := tar.NewWriter(gzipWriter)
|
||||
defer tarWriter.Close()
|
||||
defer func() {
|
||||
_ = tarWriter.Close()
|
||||
}()
|
||||
|
||||
filepath.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
|
||||
_ = revel.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
srcFile, err := os.Open(srcPath)
|
||||
panicOnError(err, "Failed to read source file")
|
||||
defer srcFile.Close()
|
||||
defer func() {
|
||||
_ = srcFile.Close()
|
||||
}()
|
||||
|
||||
err = tarWriter.WriteHeader(&tar.Header{
|
||||
Name: strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator)),
|
||||
@@ -162,7 +162,15 @@ func empty(dirname string) bool {
|
||||
if err != nil {
|
||||
errorf("error opening directory: %s", err)
|
||||
}
|
||||
defer dir.Close()
|
||||
defer func() {
|
||||
_ = dir.Close()
|
||||
}()
|
||||
results, _ := dir.Readdir(1)
|
||||
return len(results) == 0
|
||||
}
|
||||
|
||||
func importPathFromCurrentDir() string {
|
||||
pwd, _ := os.Getwd()
|
||||
importPath, _ := filepath.Rel(filepath.Join(build.Default.GOPATH, "src"), pwd)
|
||||
return filepath.ToSlash(importPath)
|
||||
}
|
||||
|
||||
38
revel/version.go
Normal file
38
revel/version.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var cmdVersion = &Command{
|
||||
UsageLine: "version",
|
||||
Short: "displays the Revel Framework and Go version",
|
||||
Long: `
|
||||
Displays the Revel Framework and Go version.
|
||||
|
||||
For example:
|
||||
|
||||
revel version
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdVersion.Run = versionApp
|
||||
}
|
||||
|
||||
func versionApp(args []string) {
|
||||
fmt.Printf("Version(s):")
|
||||
fmt.Printf("\n Revel v%v (%v)", revel.Version, revel.BuildDate)
|
||||
fmt.Printf("\n %s %s/%s\n\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
Reference in New Issue
Block a user