cmd
This commit is contained in:
23
Vendor.md
Executable file
23
Vendor.md
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
# Vendor
|
||||||
|
|
||||||
|
## revel 0.18
|
||||||
|
|
||||||
|
```
|
||||||
|
revel run github.com/leanote/leanote
|
||||||
|
````
|
||||||
|
|
||||||
|
## build leanote
|
||||||
|
|
||||||
|
在当前目录生成了leanote二进制文件
|
||||||
|
|
||||||
|
```
|
||||||
|
go build -o ./leanote github.com/leanote/leanote/app/tmp
|
||||||
|
```
|
||||||
|
|
||||||
|
## 运行leanote
|
||||||
|
|
||||||
|
其中-importPath是必须的
|
||||||
|
|
||||||
|
```
|
||||||
|
./leanote -importPath=github.com/leanote/leanote -runMode=prod -port=9000
|
||||||
|
```
|
11
cmd/README.md
Executable file
11
cmd/README.md
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
# Revel command line tools
|
||||||
|
|
||||||
|
Provides the `revel` command, used to create and run Revel apps.
|
||||||
|
|
||||||
|
- More info at http://revel.github.io/manual/tool.html
|
||||||
|
|
||||||
|
Install
|
||||||
|
------------
|
||||||
|
```bash
|
||||||
|
go get github.com/revel/cmd/revel
|
||||||
|
```
|
128
cmd/harness/app.go
Normal file
128
cmd/harness/app.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// 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"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/revel/revel"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App contains the configuration for running a Revel app. (Not for the app itself)
|
||||||
|
// Its only purpose is constructing the command to execute.
|
||||||
|
type App struct {
|
||||||
|
BinaryPath string // Path to the app executable
|
||||||
|
Port int // Port to pass as a command line argument.
|
||||||
|
cmd AppCmd // The last cmd returned.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApp returns app instance with binary path in it
|
||||||
|
func NewApp(binPath string) *App {
|
||||||
|
return &App{BinaryPath: binPath}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill the last app command returned.
|
||||||
|
func (a *App) Kill() {
|
||||||
|
a.cmd.Kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppCmd manages the running of a Revel app server.
|
||||||
|
// It requires revel.Init to have been called previously.
|
||||||
|
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),
|
||||||
|
fmt.Sprintf("-importPath=%s", revel.ImportPath),
|
||||||
|
fmt.Sprintf("-runMode=%s", revel.RunMode))
|
||||||
|
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||||
|
return AppCmd{cmd}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)}
|
||||||
|
cmd.Stdout = listeningWriter
|
||||||
|
revel.RevelLog.Debug("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
||||||
|
if err := cmd.Cmd.Start(); err != nil {
|
||||||
|
revel.RevelLog.Fatal("Error running:", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-cmd.waitChan():
|
||||||
|
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.RevelLog.Debug("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
||||||
|
if err := cmd.Cmd.Run(); err != nil {
|
||||||
|
revel.RevelLog.Fatal("Error running:", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill terminates the app server if it's running.
|
||||||
|
func (cmd AppCmd) Kill() {
|
||||||
|
if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) {
|
||||||
|
revel.RevelLog.Debug("Killing revel server pid", "pid", cmd.Process.Pid)
|
||||||
|
err := cmd.Process.Kill()
|
||||||
|
if err != nil {
|
||||||
|
revel.RevelLog.Fatal("Failed to kill revel server:", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a channel that is notified when Wait() returns.
|
||||||
|
func (cmd AppCmd) waitChan() <-chan struct{} {
|
||||||
|
ch := make(chan struct{}, 1)
|
||||||
|
go func() {
|
||||||
|
_ = cmd.Wait()
|
||||||
|
ch <- struct{}{}
|
||||||
|
}()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// A io.Writer that copies to the destination, and listens for "Listening on.."
|
||||||
|
// in the stream. (Which tells us when the revel server has finished starting up)
|
||||||
|
// This is super ghetto, but by far the simplest thing that should work.
|
||||||
|
type startupListeningWriter struct {
|
||||||
|
dest io.Writer
|
||||||
|
notifyReady chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return w.dest.Write(p)
|
||||||
|
}
|
491
cmd/harness/build.go
Executable file
491
cmd/harness/build.go
Executable file
@ -0,0 +1,491 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/revel/revel"
|
||||||
|
)
|
||||||
|
|
||||||
|
var importErrorPattern = regexp.MustCompile("cannot find package \"([^\"]+)\"")
|
||||||
|
|
||||||
|
// Build the app:
|
||||||
|
// 1. Generate the the main.go file.
|
||||||
|
// 2. Run the appropriate "go build" command.
|
||||||
|
// Requires that revel.Init has been called previously.
|
||||||
|
// Returns the path to the built binary, and an error if there was a problem building it.
|
||||||
|
func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
||||||
|
// First, clear the generated files (to avoid them messing with ProcessSource).
|
||||||
|
cleanSource("tmp", "routes")
|
||||||
|
|
||||||
|
sourceInfo, compileError := ProcessSource(revel.CodePaths)
|
||||||
|
if compileError != nil {
|
||||||
|
return nil, compileError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the db.import to the import paths.
|
||||||
|
if dbImportPath, found := revel.Config.String("db.import"); found {
|
||||||
|
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, strings.Split(dbImportPath,",")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate two source files.
|
||||||
|
templateArgs := map[string]interface{}{
|
||||||
|
"Controllers": sourceInfo.ControllerSpecs(),
|
||||||
|
"ValidationKeys": sourceInfo.ValidationKeys,
|
||||||
|
"ImportPaths": calcImportAliases(sourceInfo),
|
||||||
|
"TestSuites": sourceInfo.TestSuites(),
|
||||||
|
}
|
||||||
|
genSource("tmp", "main.go", RevelMainTemplate, templateArgs)
|
||||||
|
genSource("routes", "routes.go", RevelRoutesTemplate, templateArgs)
|
||||||
|
|
||||||
|
// Read build config.
|
||||||
|
buildTags := revel.Config.StringDefault("build.tags", "")
|
||||||
|
|
||||||
|
// Build the user program (all code under app).
|
||||||
|
// It relies on the user having "go" installed.
|
||||||
|
goPath, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
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.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 := filepath.Join(pkg.BinDir, "revel.d", revel.ImportPath, filepath.Base(revel.BasePath))
|
||||||
|
|
||||||
|
// Change binary path for Windows build
|
||||||
|
goos := runtime.GOOS
|
||||||
|
if goosEnv := os.Getenv("GOOS"); goosEnv != "" {
|
||||||
|
goos = goosEnv
|
||||||
|
}
|
||||||
|
if goos == "windows" {
|
||||||
|
binName += ".exe"
|
||||||
|
}
|
||||||
|
|
||||||
|
gotten := make(map[string]struct{})
|
||||||
|
for {
|
||||||
|
appVersion := getAppVersion()
|
||||||
|
|
||||||
|
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}
|
||||||
|
|
||||||
|
// Add in build flags
|
||||||
|
flags = append(flags, buildFlags...)
|
||||||
|
|
||||||
|
// 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.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.RevelLog.Error(string(output))
|
||||||
|
|
||||||
|
// See if it was an import error that we can go get.
|
||||||
|
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{}{}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO remove this unreachable code and document it
|
||||||
|
revel.RevelLog.Fatalf("Not reachable")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to define a version string for the compiled app
|
||||||
|
// The following is tried (first match returns):
|
||||||
|
// - Read a version explicitly specified in the APP_VERSION environment
|
||||||
|
// variable
|
||||||
|
// - Read the output of "git describe" if the source is in a git repository
|
||||||
|
// If no version can be determined, an empty string is returned.
|
||||||
|
func getAppVersion() string {
|
||||||
|
if version := os.Getenv("APP_VERSION"); version != "" {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the git binary
|
||||||
|
if gitPath, err := exec.LookPath("git"); err == nil {
|
||||||
|
// Check for the .git directory
|
||||||
|
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.RevelLog.Debug("Exec:", "args", gitCmd.Args)
|
||||||
|
output, err := gitCmd.Output()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
revel.RevelLog.Warn("Cannot determine git repository version:", "error", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return "git-" + strings.TrimSpace(string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanSource(dirs ...string) {
|
||||||
|
for _, dir := range dirs {
|
||||||
|
cleanDir(dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanDir(dir string) {
|
||||||
|
revel.RevelLog.Info("Cleaning dir " + dir)
|
||||||
|
tmpPath := filepath.Join(revel.AppPath, dir)
|
||||||
|
f, err := os.Open(tmpPath)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
revel.RevelLog.Error("Failed to clean dir:", "error", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defer func() {
|
||||||
|
_ = f.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
infos, err := f.Readdir(0)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
revel.RevelLog.Error("Failed to clean dir:", "error", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, info := range infos {
|
||||||
|
pathName := filepath.Join(tmpPath, info.Name())
|
||||||
|
if info.IsDir() {
|
||||||
|
err := os.RemoveAll(pathName)
|
||||||
|
if err != nil {
|
||||||
|
revel.RevelLog.Error("Failed to remove dir:", "error", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := os.Remove(pathName)
|
||||||
|
if err != nil {
|
||||||
|
revel.RevelLog.Error("Failed to remove file:", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{}) {
|
||||||
|
sourceCode := revel.ExecuteTemplate(
|
||||||
|
template.Must(template.New("").Parse(templateSource)),
|
||||||
|
args)
|
||||||
|
|
||||||
|
// Create a fresh dir.
|
||||||
|
cleanSource(dir)
|
||||||
|
tmpPath := filepath.Join(revel.AppPath, dir)
|
||||||
|
err := os.Mkdir(tmpPath, 0777)
|
||||||
|
if err != nil && !os.IsExist(err) {
|
||||||
|
revel.RevelLog.Fatalf("Failed to make '%v' directory: %v", dir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the file
|
||||||
|
file, err := os.Create(filepath.Join(tmpPath, filename))
|
||||||
|
if err != nil {
|
||||||
|
revel.RevelLog.Fatalf("Failed to create file: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = file.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err = file.WriteString(sourceCode); err != nil {
|
||||||
|
revel.RevelLog.Fatalf("Failed to write to file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks through all the method args and returns a set of unique import paths
|
||||||
|
// that cover all the method arg types.
|
||||||
|
// Additionally, assign package aliases when necessary to resolve ambiguity.
|
||||||
|
func calcImportAliases(src *SourceInfo) map[string]string {
|
||||||
|
aliases := make(map[string]string)
|
||||||
|
typeArrays := [][]*TypeInfo{src.ControllerSpecs(), src.TestSuites()}
|
||||||
|
for _, specs := range typeArrays {
|
||||||
|
for _, spec := range specs {
|
||||||
|
addAlias(aliases, spec.ImportPath, spec.PackageName)
|
||||||
|
|
||||||
|
for _, methSpec := range spec.MethodSpecs {
|
||||||
|
for _, methArg := range methSpec.Args {
|
||||||
|
if methArg.ImportPath == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addAlias(aliases, methArg.ImportPath, methArg.TypeExpr.PkgName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the "InitImportPaths", with alias "_"
|
||||||
|
for _, importPath := range src.InitImportPaths {
|
||||||
|
if _, ok := aliases[importPath]; !ok {
|
||||||
|
aliases[importPath] = "_"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
func addAlias(aliases map[string]string, importPath, pkgName string) {
|
||||||
|
alias, ok := aliases[importPath]
|
||||||
|
if ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
alias = makePackageAlias(aliases, pkgName)
|
||||||
|
aliases[importPath] = alias
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePackageAlias(aliases map[string]string, pkgName string) string {
|
||||||
|
i := 0
|
||||||
|
alias := pkgName
|
||||||
|
for containsValue(aliases, alias) || alias == "revel" {
|
||||||
|
alias = fmt.Sprintf("%s%d", pkgName, i)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return alias
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsValue(m map[string]string, val string) bool {
|
||||||
|
for _, v := range m {
|
||||||
|
if v == val {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the output of the "go build" command.
|
||||||
|
// Return a detailed Error.
|
||||||
|
func newCompileError(output []byte) *revel.Error {
|
||||||
|
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
|
||||||
|
FindSubmatch(output)
|
||||||
|
if errorMatch == nil {
|
||||||
|
errorMatch = regexp.MustCompile(`(?m)^(.*?)\:(\d+)\:\s(.*?)$`).FindSubmatch(output)
|
||||||
|
|
||||||
|
if errorMatch == nil {
|
||||||
|
revel.RevelLog.Error("Failed to parse build errors", "error", string(output))
|
||||||
|
return &revel.Error{
|
||||||
|
SourceType: "Go code",
|
||||||
|
Title: "Go Compilation Error",
|
||||||
|
Description: "See console for build error.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMatch = append(errorMatch, errorMatch[3])
|
||||||
|
|
||||||
|
revel.RevelLog.Error("Build errors", "errors", string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the source for the offending file.
|
||||||
|
var (
|
||||||
|
relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go"
|
||||||
|
absFilename, _ = filepath.Abs(relFilename)
|
||||||
|
line, _ = strconv.Atoi(string(errorMatch[2]))
|
||||||
|
description = string(errorMatch[4])
|
||||||
|
compileError = &revel.Error{
|
||||||
|
SourceType: "Go code",
|
||||||
|
Title: "Go Compilation Error",
|
||||||
|
Path: relFilename,
|
||||||
|
Description: description,
|
||||||
|
Line: line,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
errorLink := revel.Config.StringDefault("error.link", "")
|
||||||
|
|
||||||
|
if errorLink != "" {
|
||||||
|
compileError.SetLink(errorLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileStr, err := revel.ReadLines(absFilename)
|
||||||
|
if err != nil {
|
||||||
|
compileError.MetaError = absFilename + ": " + err.Error()
|
||||||
|
revel.RevelLog.Error(compileError.MetaError)
|
||||||
|
return compileError
|
||||||
|
}
|
||||||
|
|
||||||
|
compileError.SourceLines = fileStr
|
||||||
|
return compileError
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevelMainTemplate template for app/tmp/main.go
|
||||||
|
const RevelMainTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"reflect"
|
||||||
|
"github.com/revel/revel"{{range $k, $v := $.ImportPaths}}
|
||||||
|
{{$v}} "{{$k}}"{{end}}
|
||||||
|
"github.com/revel/revel/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
runMode *string = flag.String("runMode", "", "Run mode.")
|
||||||
|
port *int = flag.Int("port", 0, "By default, read from app.conf")
|
||||||
|
importPath *string = flag.String("importPath", "", "Go Import Path for the app.")
|
||||||
|
srcPath *string = flag.String("srcPath", "", "Path to the source root.")
|
||||||
|
|
||||||
|
// So compiler won't complain if the generated code doesn't reference reflect package...
|
||||||
|
_ = reflect.Invalid
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
revel.Init(*runMode, *importPath, *srcPath)
|
||||||
|
revel.AppLog.Info("Running revel server")
|
||||||
|
{{range $i, $c := .Controllers}}
|
||||||
|
revel.RegisterController((*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),
|
||||||
|
[]*revel.MethodType{
|
||||||
|
{{range .MethodSpecs}}&revel.MethodType{
|
||||||
|
Name: "{{.Name}}",
|
||||||
|
Args: []*revel.MethodArg{ {{range .Args}}
|
||||||
|
&revel.MethodArg{Name: "{{.Name}}", Type: reflect.TypeOf((*{{index $.ImportPaths .ImportPath | .TypeExpr.TypeName}})(nil)) },{{end}}
|
||||||
|
},
|
||||||
|
RenderArgNames: map[int][]string{ {{range .RenderCalls}}
|
||||||
|
{{.Line}}: []string{ {{range .Names}}
|
||||||
|
"{{.}}",{{end}}
|
||||||
|
},{{end}}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{{end}}
|
||||||
|
})
|
||||||
|
{{end}}
|
||||||
|
revel.DefaultValidationKeys = map[string]map[int]string{ {{range $path, $lines := .ValidationKeys}}
|
||||||
|
"{{$path}}": { {{range $line, $key := $lines}}
|
||||||
|
{{$line}}: "{{$key}}",{{end}}
|
||||||
|
},{{end}}
|
||||||
|
}
|
||||||
|
testing.TestSuites = []interface{}{ {{range .TestSuites}}
|
||||||
|
(*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),{{end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
revel.Run(*port)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// RevelRoutesTemplate template for app/conf/routes
|
||||||
|
const RevelRoutesTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||||
|
package routes
|
||||||
|
|
||||||
|
import "github.com/revel/revel"
|
||||||
|
|
||||||
|
{{range $i, $c := .Controllers}}
|
||||||
|
type t{{.StructName}} struct {}
|
||||||
|
var {{.StructName}} t{{.StructName}}
|
||||||
|
|
||||||
|
{{range .MethodSpecs}}
|
||||||
|
func (_ t{{$c.StructName}}) {{.Name}}({{range .Args}}
|
||||||
|
{{.Name}} {{if .ImportPath}}interface{}{{else}}{{.TypeExpr.TypeName ""}}{{end}},{{end}}
|
||||||
|
) string {
|
||||||
|
args := make(map[string]string)
|
||||||
|
{{range .Args}}
|
||||||
|
revel.Unbind(args, "{{.Name}}", {{.Name}}){{end}}
|
||||||
|
return revel.MainRouter.Reverse("{{$c.StructName}}.{{.Name}}", args).URL
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
`
|
285
cmd/harness/harness.go
Normal file
285
cmd/harness/harness.go
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
// 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 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"
|
||||||
|
"go/build"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/revel/revel"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
doNotWatch = []string{"tmp", "views", "routes"}
|
||||||
|
|
||||||
|
lastRequestHadError int32
|
||||||
|
)
|
||||||
|
|
||||||
|
// Harness reverse proxies requests to the application server.
|
||||||
|
// It builds / runs / rebuilds / restarts the server when code is changed.
|
||||||
|
type Harness struct {
|
||||||
|
app *App
|
||||||
|
serverHost string
|
||||||
|
port int
|
||||||
|
proxy *httputil.ReverseProxy
|
||||||
|
watcher *revel.Watcher
|
||||||
|
mutex *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush any change events and rebuild app if necessary.
|
||||||
|
// Render an error page if the rebuild / restart failed.
|
||||||
|
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, h.serverHost)
|
||||||
|
} else {
|
||||||
|
h.proxy.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{filepath.Join(revel.RevelPath, "templates")})
|
||||||
|
if err := revel.MainTemplateLoader.Refresh(); err != nil {
|
||||||
|
revel.RevelLog.Error("Template loader error", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := revel.HTTPAddr
|
||||||
|
port := revel.Config.IntDefault("harness.port", 0)
|
||||||
|
scheme := "http"
|
||||||
|
if revel.HTTPSsl {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the server is running on the wildcard address, use "localhost"
|
||||||
|
if addr == "" {
|
||||||
|
addr = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
if port == 0 {
|
||||||
|
port = getFreePort()
|
||||||
|
}
|
||||||
|
|
||||||
|
serverURL, _ := url.ParseRequestURI(fmt.Sprintf(scheme+"://%s:%d", addr, port))
|
||||||
|
|
||||||
|
serverHarness := &Harness{
|
||||||
|
port: port,
|
||||||
|
serverHost: serverURL.String()[len(scheme+"://"):],
|
||||||
|
proxy: httputil.NewSingleHostReverseProxy(serverURL),
|
||||||
|
mutex: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if revel.HTTPSsl {
|
||||||
|
serverHarness.proxy.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return serverHarness
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.RevelLog.Debug("Rebuild Called")
|
||||||
|
h.app, err = Build()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.app.Port = h.port
|
||||||
|
if err2 := h.app.Cmd().Start(); err2 != nil {
|
||||||
|
return &revel.Error{
|
||||||
|
Title: "App failed to start up",
|
||||||
|
Description: err2.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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the harness, which listens for requests and proxies them to the app
|
||||||
|
// server, which it runs and rebuilds as necessary.
|
||||||
|
func (h *Harness) Run() {
|
||||||
|
var paths []string
|
||||||
|
if revel.Config.BoolDefault("watch.gopath", false) {
|
||||||
|
gopaths := filepath.SplitList(build.Default.GOPATH)
|
||||||
|
paths = append(paths, gopaths...)
|
||||||
|
}
|
||||||
|
paths = append(paths, revel.CodePaths...)
|
||||||
|
h.watcher = revel.NewWatcher()
|
||||||
|
h.watcher.Listen(h, paths...)
|
||||||
|
h.watcher.Notify()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
err = http.ListenAndServe(addr, h)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
revel.RevelLog.Error("Failed to start reverse proxy:", "error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Kill the app on signal.
|
||||||
|
ch := make(chan os.Signal)
|
||||||
|
signal.Notify(ch, os.Interrupt, os.Kill)
|
||||||
|
<-ch
|
||||||
|
if h.app != nil {
|
||||||
|
h.app.Kill()
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find an unused port
|
||||||
|
func getFreePort() (port int) {
|
||||||
|
conn, err := net.Listen("tcp", ":0")
|
||||||
|
if err != nil {
|
||||||
|
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.RevelLog.Fatal("Unable to close port", "error", err)
|
||||||
|
}
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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.RevelLog.Error("Error dialing websocket backend ", "host", host, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hj, ok := w.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "Not a hijacker?", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nc, _, err := hj.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
revel.RevelLog.Error("Hijack error", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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.RevelLog.Error("Error copying request to target", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errc := make(chan error, 2)
|
||||||
|
cp := func(dst io.Writer, src io.Reader) {
|
||||||
|
_, err := io.Copy(dst, src)
|
||||||
|
errc <- err
|
||||||
|
}
|
||||||
|
go cp(d, nc)
|
||||||
|
go cp(nc, d)
|
||||||
|
<-errc
|
||||||
|
}
|
843
cmd/harness/reflect.go
Normal file
843
cmd/harness/reflect.go
Normal file
@ -0,0 +1,843 @@
|
|||||||
|
// 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.
|
||||||
|
// It catalogs the controllers, their methods, and their arguments.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/build"
|
||||||
|
"go/parser"
|
||||||
|
"go/scanner"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/revel/revel"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SourceInfo is the top-level struct containing all extracted information
|
||||||
|
// about the app source code, used to generate main.go.
|
||||||
|
type SourceInfo struct {
|
||||||
|
// StructSpecs lists type info for all structs found under the code paths.
|
||||||
|
// They may be queried to determine which ones (transitively) embed certain types.
|
||||||
|
StructSpecs []*TypeInfo
|
||||||
|
// ValidationKeys provides a two-level lookup. The keys are:
|
||||||
|
// 1. The fully-qualified function name,
|
||||||
|
// e.g. "github.com/revel/examples/chat/app/controllers.(*Application).Action"
|
||||||
|
// 2. Within that func's file, the line number of the (overall) expression statement.
|
||||||
|
// e.g. the line returned from runtime.Caller()
|
||||||
|
// The result of the lookup the name of variable being validated.
|
||||||
|
ValidationKeys map[string]map[int]string
|
||||||
|
// A list of import paths.
|
||||||
|
// Revel notices files with an init() function and imports that package.
|
||||||
|
InitImportPaths []string
|
||||||
|
|
||||||
|
// controllerSpecs lists type info for all structs found under
|
||||||
|
// app/controllers/... that embed (directly or indirectly) revel.Controller
|
||||||
|
controllerSpecs []*TypeInfo
|
||||||
|
// testSuites list the types that constitute the set of application tests.
|
||||||
|
testSuites []*TypeInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeInfo summarizes information about a struct type in the app source code.
|
||||||
|
type TypeInfo struct {
|
||||||
|
StructName string // e.g. "Application"
|
||||||
|
ImportPath string // e.g. "github.com/revel/examples/chat/app/controllers"
|
||||||
|
PackageName string // e.g. "controllers"
|
||||||
|
MethodSpecs []*MethodSpec
|
||||||
|
|
||||||
|
// Used internally to identify controllers that indirectly embed *revel.Controller.
|
||||||
|
embeddedTypes []*embeddedTypeName
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodCall describes a call to c.Render(..)
|
||||||
|
// It documents the argument names used, in order to propagate them to RenderArgs.
|
||||||
|
type methodCall struct {
|
||||||
|
Path string // e.g. "myapp/app/controllers.(*Application).Action"
|
||||||
|
Line int
|
||||||
|
Names []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MethodSpec holds the information of one Method
|
||||||
|
type MethodSpec struct {
|
||||||
|
Name string // Name of the method, e.g. "Index"
|
||||||
|
Args []*MethodArg // Argument descriptors
|
||||||
|
RenderCalls []*methodCall // Descriptions of Render() invocations from this Method.
|
||||||
|
}
|
||||||
|
|
||||||
|
// MethodArg holds the information of one argument
|
||||||
|
type MethodArg struct {
|
||||||
|
Name string // Name of the argument.
|
||||||
|
TypeExpr TypeExpr // The name of the type, e.g. "int", "*pkg.UserType"
|
||||||
|
ImportPath string // If the arg is of an imported type, this is the import path.
|
||||||
|
}
|
||||||
|
|
||||||
|
type embeddedTypeName struct {
|
||||||
|
ImportPath, StructName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps a controller simple name (e.g. "Login") to the methods for which it is a
|
||||||
|
// receiver.
|
||||||
|
type methodMap map[string][]*MethodSpec
|
||||||
|
|
||||||
|
// 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
|
||||||
|
compileError *revel.Error
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, root := range roots {
|
||||||
|
rootImportPath := importPathFromPath(root)
|
||||||
|
if rootImportPath == "" {
|
||||||
|
revel.RevelLog.Warn("Skipping empty code path", "path", root)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start walking the directory tree.
|
||||||
|
_ = revel.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
revel.RevelLog.Error("Error scanning app source:", "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() || info.Name() == "tmp" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the import path of the package.
|
||||||
|
pkgImportPath := rootImportPath
|
||||||
|
if root != path {
|
||||||
|
pkgImportPath = rootImportPath + "/" + filepath.ToSlash(path[len(root)+1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse files within the path.
|
||||||
|
var pkgs map[string]*ast.Package
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
pkgs, err = parser.ParseDir(fset, path, func(f os.FileInfo) bool {
|
||||||
|
return !f.IsDir() && !strings.HasPrefix(f.Name(), ".") && strings.HasSuffix(f.Name(), ".go")
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
if errList, ok := err.(scanner.ErrorList); ok {
|
||||||
|
var pos = errList[0].Pos
|
||||||
|
compileError = &revel.Error{
|
||||||
|
SourceType: ".go source",
|
||||||
|
Title: "Go Compilation Error",
|
||||||
|
Path: pos.Filename,
|
||||||
|
Description: errList[0].Msg,
|
||||||
|
Line: pos.Line,
|
||||||
|
Column: pos.Column,
|
||||||
|
SourceLines: revel.MustReadLines(pos.Filename),
|
||||||
|
}
|
||||||
|
|
||||||
|
errorLink := revel.Config.StringDefault("error.link", "")
|
||||||
|
|
||||||
|
if errorLink != "" {
|
||||||
|
compileError.SetLink(errorLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
return compileError
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is exception, err alredy checked above. Here just a print
|
||||||
|
ast.Print(nil, err)
|
||||||
|
revel.RevelLog.Fatal("Failed to parse dir", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip "main" packages.
|
||||||
|
delete(pkgs, "main")
|
||||||
|
|
||||||
|
// If there is no code in this directory, skip it.
|
||||||
|
if len(pkgs) == 0 {
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
for _, v := range pkgs {
|
||||||
|
pkg = v
|
||||||
|
}
|
||||||
|
|
||||||
|
srcInfo = appendSourceInfo(srcInfo, processPackage(fset, pkgImportPath, path, pkg))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return srcInfo, compileError
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendSourceInfo(srcInfo1, srcInfo2 *SourceInfo) *SourceInfo {
|
||||||
|
if srcInfo1 == nil {
|
||||||
|
return srcInfo2
|
||||||
|
}
|
||||||
|
|
||||||
|
srcInfo1.StructSpecs = append(srcInfo1.StructSpecs, srcInfo2.StructSpecs...)
|
||||||
|
srcInfo1.InitImportPaths = append(srcInfo1.InitImportPaths, srcInfo2.InitImportPaths...)
|
||||||
|
for k, v := range srcInfo2.ValidationKeys {
|
||||||
|
if _, ok := srcInfo1.ValidationKeys[k]; ok {
|
||||||
|
revel.RevelLog.Warn("Key conflict when scanning validation calls:", "key", k)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
srcInfo1.ValidationKeys[k] = v
|
||||||
|
}
|
||||||
|
return srcInfo1
|
||||||
|
}
|
||||||
|
|
||||||
|
func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast.Package) *SourceInfo {
|
||||||
|
var (
|
||||||
|
structSpecs []*TypeInfo
|
||||||
|
initImportPaths []string
|
||||||
|
|
||||||
|
methodSpecs = make(methodMap)
|
||||||
|
validationKeys = make(map[string]map[int]string)
|
||||||
|
scanControllers = strings.HasSuffix(pkgImportPath, "/controllers") ||
|
||||||
|
strings.Contains(pkgImportPath, "/controllers/")
|
||||||
|
scanTests = strings.HasSuffix(pkgImportPath, "/tests") ||
|
||||||
|
strings.Contains(pkgImportPath, "/tests/")
|
||||||
|
)
|
||||||
|
|
||||||
|
// For each source file in the package...
|
||||||
|
for _, file := range pkg.Files {
|
||||||
|
|
||||||
|
// Imports maps the package key to the full import path.
|
||||||
|
// e.g. import "sample/app/models" => "models": "sample/app/models"
|
||||||
|
imports := map[string]string{}
|
||||||
|
|
||||||
|
// For each declaration in the source file...
|
||||||
|
for _, decl := range file.Decls {
|
||||||
|
addImports(imports, decl, pkgPath)
|
||||||
|
|
||||||
|
if scanControllers {
|
||||||
|
// Match and add both structs and methods
|
||||||
|
structSpecs = appendStruct(structSpecs, pkgImportPath, pkg, decl, imports, fset)
|
||||||
|
appendAction(fset, methodSpecs, decl, pkgImportPath, pkg.Name, imports)
|
||||||
|
} else if scanTests {
|
||||||
|
structSpecs = appendStruct(structSpecs, pkgImportPath, pkg, decl, imports, fset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a func...
|
||||||
|
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
||||||
|
// Scan it for validation calls
|
||||||
|
lineKeys := getValidationKeys(fset, funcDecl, imports)
|
||||||
|
if len(lineKeys) > 0 {
|
||||||
|
validationKeys[pkgImportPath+"."+getFuncName(funcDecl)] = lineKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's an init function.
|
||||||
|
if funcDecl.Name.Name == "init" {
|
||||||
|
initImportPaths = []string{pkgImportPath}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the method specs to the struct specs.
|
||||||
|
for _, spec := range structSpecs {
|
||||||
|
spec.MethodSpecs = methodSpecs[spec.StructName]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SourceInfo{
|
||||||
|
StructSpecs: structSpecs,
|
||||||
|
ValidationKeys: validationKeys,
|
||||||
|
InitImportPaths: initImportPaths,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFuncName returns a name for this func or method declaration.
|
||||||
|
// e.g. "(*Application).SayHello" for a method, "SayHello" for a func.
|
||||||
|
func getFuncName(funcDecl *ast.FuncDecl) string {
|
||||||
|
prefix := ""
|
||||||
|
if funcDecl.Recv != nil {
|
||||||
|
recvType := funcDecl.Recv.List[0].Type
|
||||||
|
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||||
|
prefix = "(*" + recvStarType.X.(*ast.Ident).Name + ")"
|
||||||
|
} else {
|
||||||
|
prefix = recvType.(*ast.Ident).Name
|
||||||
|
}
|
||||||
|
prefix += "."
|
||||||
|
}
|
||||||
|
return prefix + funcDecl.Name.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if genDecl.Tok != token.IMPORT {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, spec := range genDecl.Specs {
|
||||||
|
importSpec := spec.(*ast.ImportSpec)
|
||||||
|
var pkgAlias string
|
||||||
|
if importSpec.Name != nil {
|
||||||
|
pkgAlias = importSpec.Name.Name
|
||||||
|
if pkgAlias == "_" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\""
|
||||||
|
fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes
|
||||||
|
|
||||||
|
// If the package was not aliased (common case), we have to import it
|
||||||
|
// to see what the package name is.
|
||||||
|
// TODO: Can improve performance here a lot:
|
||||||
|
// 1. Do not import everything over and over again. Keep a cache.
|
||||||
|
// 2. Exempt the standard library; their directories always match the package name.
|
||||||
|
// 3. Can use build.FindOnly and then use parser.ParseDir with mode PackageClauseOnly
|
||||||
|
if pkgAlias == "" {
|
||||||
|
pkg, err := build.Import(fullPath, srcDir, 0)
|
||||||
|
if err != nil {
|
||||||
|
// We expect this to happen for apps using reverse routing (since we
|
||||||
|
// have not yet generated the routes). Don't log that.
|
||||||
|
if !strings.HasSuffix(fullPath, "/app/routes") {
|
||||||
|
revel.RevelLog.Debug("Could not find import:", "path", fullPath)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pkgAlias = pkg.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
imports[pkgAlias] = fullPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this Decl is a struct type definition, it is summarized and added to specs.
|
||||||
|
// Else, specs is returned unchanged.
|
||||||
|
func appendStruct(specs []*TypeInfo, pkgImportPath string, pkg *ast.Package, decl ast.Decl, imports map[string]string, fset *token.FileSet) []*TypeInfo {
|
||||||
|
// Filter out non-Struct type declarations.
|
||||||
|
spec, found := getStructTypeDecl(decl, fset)
|
||||||
|
if !found {
|
||||||
|
return specs
|
||||||
|
}
|
||||||
|
|
||||||
|
structType := spec.Type.(*ast.StructType)
|
||||||
|
|
||||||
|
// At this point we know it's a type declaration for a struct.
|
||||||
|
// Fill in the rest of the info by diving into the fields.
|
||||||
|
// Add it provisionally to the Controller list -- it's later filtered using field info.
|
||||||
|
controllerSpec := &TypeInfo{
|
||||||
|
StructName: spec.Name.Name,
|
||||||
|
ImportPath: pkgImportPath,
|
||||||
|
PackageName: pkg.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, field := range structType.Fields.List {
|
||||||
|
// If field.Names is set, it's not an embedded type.
|
||||||
|
if field.Names != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// A direct "sub-type" has an ast.Field as either:
|
||||||
|
// Ident { "AppController" }
|
||||||
|
// SelectorExpr { "rev", "Controller" }
|
||||||
|
// Additionally, that can be wrapped by StarExprs.
|
||||||
|
fieldType := field.Type
|
||||||
|
pkgName, typeName := func() (string, string) {
|
||||||
|
// Drill through any StarExprs.
|
||||||
|
for {
|
||||||
|
if starExpr, ok := fieldType.(*ast.StarExpr); ok {
|
||||||
|
fieldType = starExpr.X
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the embedded type is in the same package, it's an Ident.
|
||||||
|
if ident, ok := fieldType.(*ast.Ident); ok {
|
||||||
|
return "", ident.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
|
||||||
|
if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
|
||||||
|
return pkgIdent.Name, selectorExpr.Sel.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
|
}()
|
||||||
|
|
||||||
|
// If a typename wasn't found, skip it.
|
||||||
|
if typeName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the import path for this type.
|
||||||
|
// If it was referenced without a package name, use the current package import path.
|
||||||
|
// Else, look up the package's import path by name.
|
||||||
|
var importPath string
|
||||||
|
if pkgName == "" {
|
||||||
|
importPath = pkgImportPath
|
||||||
|
} else {
|
||||||
|
var ok bool
|
||||||
|
if importPath, ok = imports[pkgName]; !ok {
|
||||||
|
revel.RevelLog.Error("Failed to find import path for ", "package", pkgName, "type", typeName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controllerSpec.embeddedTypes = append(controllerSpec.embeddedTypes, &embeddedTypeName{
|
||||||
|
ImportPath: importPath,
|
||||||
|
StructName: typeName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(specs, controllerSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If decl is a Method declaration, it is summarized and added to the array
|
||||||
|
// underneath its receiver type.
|
||||||
|
// e.g. "Login" => {MethodSpec, MethodSpec, ..}
|
||||||
|
func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPath, pkgName string, imports map[string]string) {
|
||||||
|
// Func declaration?
|
||||||
|
funcDecl, ok := decl.(*ast.FuncDecl)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have a receiver?
|
||||||
|
if funcDecl.Recv == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it public?
|
||||||
|
if !funcDecl.Name.IsExported() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does it return a Result?
|
||||||
|
if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if selExpr.Sel.Name != "Result" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != revel.RevelImportPath {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
method := &MethodSpec{
|
||||||
|
Name: funcDecl.Name.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a description of the arguments to the method.
|
||||||
|
for _, field := range funcDecl.Type.Params.List {
|
||||||
|
for _, name := range field.Names {
|
||||||
|
var importPath string
|
||||||
|
typeExpr := NewTypeExpr(pkgName, field.Type)
|
||||||
|
if !typeExpr.Valid {
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
// Local object
|
||||||
|
if typeExpr.PkgName == pkgName {
|
||||||
|
importPath = pkgImportPath
|
||||||
|
} else if typeExpr.PkgName != "" {
|
||||||
|
var ok bool
|
||||||
|
if importPath, ok = imports[typeExpr.PkgName]; !ok {
|
||||||
|
revel.RevelLog.Errorf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
method.Args = append(method.Args, &MethodArg{
|
||||||
|
Name: name.Name,
|
||||||
|
TypeExpr: typeExpr,
|
||||||
|
ImportPath: importPath,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a description of the calls to Render from the method.
|
||||||
|
// Inspect every node (e.g. always return true).
|
||||||
|
method.RenderCalls = []*methodCall{}
|
||||||
|
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||||
|
// Is it a function call?
|
||||||
|
callExpr, ok := node.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it calling (*Controller).Render?
|
||||||
|
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type of the receiver is not easily available, so just store every
|
||||||
|
// call to any method called Render.
|
||||||
|
if selExpr.Sel.Name != "Render" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this call's args to the renderArgs.
|
||||||
|
pos := fset.Position(callExpr.Lparen)
|
||||||
|
methodCall := &methodCall{
|
||||||
|
Line: pos.Line,
|
||||||
|
Names: []string{},
|
||||||
|
}
|
||||||
|
for _, arg := range callExpr.Args {
|
||||||
|
argIdent, ok := arg.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
methodCall.Names = append(methodCall.Names, argIdent.Name)
|
||||||
|
}
|
||||||
|
method.RenderCalls = append(method.RenderCalls, methodCall)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
var recvTypeName string
|
||||||
|
var recvType = funcDecl.Recv.List[0].Type
|
||||||
|
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||||
|
recvTypeName = recvStarType.X.(*ast.Ident).Name
|
||||||
|
} else {
|
||||||
|
recvTypeName = recvType.(*ast.Ident).Name
|
||||||
|
}
|
||||||
|
|
||||||
|
mm[recvTypeName] = append(mm[recvTypeName], method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan app source code for calls to X.Y(), where X is of type *Validation.
|
||||||
|
//
|
||||||
|
// Recognize these scenarios:
|
||||||
|
// - "Y" = "Validation" and is a member of the receiver.
|
||||||
|
// (The common case for inline validation)
|
||||||
|
// - "X" is passed in to the func as a parameter.
|
||||||
|
// (For structs implementing Validated)
|
||||||
|
//
|
||||||
|
// The line number to which a validation call is attributed is that of the
|
||||||
|
// surrounding ExprStmt. This is so that it matches what runtime.Callers()
|
||||||
|
// reports.
|
||||||
|
//
|
||||||
|
// The end result is that we can set the default validation key for each call to
|
||||||
|
// be the same as the local variable.
|
||||||
|
func getValidationKeys(fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[string]string) map[int]string {
|
||||||
|
var (
|
||||||
|
lineKeys = make(map[int]string)
|
||||||
|
|
||||||
|
// Check the func parameters and the receiver's members for the *revel.Validation type.
|
||||||
|
validationParam = getValidationParameter(funcDecl, imports)
|
||||||
|
)
|
||||||
|
|
||||||
|
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||||
|
// e.g. c.Validation.Required(arg) or v.Required(arg)
|
||||||
|
callExpr, ok := node.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// e.g. c.Validation.Required or v.Required
|
||||||
|
funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch x := funcSelector.X.(type) {
|
||||||
|
case *ast.SelectorExpr: // e.g. c.Validation
|
||||||
|
if x.Sel.Name != "Validation" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.Ident: // e.g. v
|
||||||
|
if validationParam == nil || x.Obj != validationParam {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(callExpr.Args) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the validation expression, extract the key.
|
||||||
|
key := callExpr.Args[0]
|
||||||
|
switch expr := key.(type) {
|
||||||
|
case *ast.BinaryExpr:
|
||||||
|
// If the argument is a binary expression, take the first expression.
|
||||||
|
// (e.g. c.Validation.Required(myName != ""))
|
||||||
|
key = expr.X
|
||||||
|
case *ast.UnaryExpr:
|
||||||
|
// If the argument is a unary expression, drill in.
|
||||||
|
// (e.g. c.Validation.Required(!myBool)
|
||||||
|
key = expr.X
|
||||||
|
case *ast.BasicLit:
|
||||||
|
// If it's a literal, skip it.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if typeExpr := NewTypeExpr("", key); typeExpr.Valid {
|
||||||
|
lineKeys[fset.Position(callExpr.End()).Line] = typeExpr.TypeName("")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return lineKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if there is a *revel.Validation as an argument.
|
||||||
|
func getValidationParameter(funcDecl *ast.FuncDecl, imports map[string]string) *ast.Object {
|
||||||
|
for _, field := range funcDecl.Type.Params.List {
|
||||||
|
starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == revel.RevelImportPath {
|
||||||
|
return field.Names[0].Obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TypeInfo) String() string {
|
||||||
|
return s.ImportPath + "." + s.StructName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *embeddedTypeName) String() string {
|
||||||
|
return s.ImportPath + "." + s.StructName
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStructTypeDecl checks if the given decl is a type declaration for a
|
||||||
|
// struct. If so, the TypeSpec is returned.
|
||||||
|
func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) {
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if genDecl.Tok != token.TYPE {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(genDecl.Specs) == 0 {
|
||||||
|
revel.RevelLog.Warnf("Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
spec = genDecl.Specs[0].(*ast.TypeSpec)
|
||||||
|
_, found = spec.Type.(*ast.StructType)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypesThatEmbed returns all types that (directly or indirectly) embed the
|
||||||
|
// target type, which must be a fully qualified type name,
|
||||||
|
// e.g. "github.com/revel/revel.Controller"
|
||||||
|
func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered []*TypeInfo) {
|
||||||
|
// Do a search in the "embedded type graph", starting with the target type.
|
||||||
|
var (
|
||||||
|
nodeQueue = []string{targetType}
|
||||||
|
processed []string
|
||||||
|
)
|
||||||
|
for len(nodeQueue) > 0 {
|
||||||
|
controllerSimpleName := nodeQueue[0]
|
||||||
|
nodeQueue = nodeQueue[1:]
|
||||||
|
processed = append(processed, controllerSimpleName)
|
||||||
|
|
||||||
|
// Look through all known structs.
|
||||||
|
for _, spec := range s.StructSpecs {
|
||||||
|
// If this one has been processed or is already in nodeQueue, then skip it.
|
||||||
|
if revel.ContainsString(processed, spec.String()) ||
|
||||||
|
revel.ContainsString(nodeQueue, spec.String()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look through the embedded types to see if the current type is among them.
|
||||||
|
for _, embeddedType := range spec.embeddedTypes {
|
||||||
|
|
||||||
|
// If so, add this type's simple name to the nodeQueue, and its spec to
|
||||||
|
// the filtered list.
|
||||||
|
if controllerSimpleName == embeddedType.String() {
|
||||||
|
nodeQueue = append(nodeQueue, spec.String())
|
||||||
|
filtered = append(filtered, spec)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Strip out any specifications that contain a lower case
|
||||||
|
for exit := false; !exit; exit = true {
|
||||||
|
for i, filteredItem := range filtered {
|
||||||
|
if unicode.IsLower([]rune(filteredItem.StructName)[0]) {
|
||||||
|
revel.RevelLog.Debug("Skipping adding spec for unexported type",
|
||||||
|
"type", filteredItem.StructName,
|
||||||
|
"package", filteredItem.ImportPath)
|
||||||
|
filtered = append(filtered[:i], filtered[i+1:]...)
|
||||||
|
exit = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for any missed types that where from expected packages
|
||||||
|
for _, spec := range s.StructSpecs {
|
||||||
|
if spec.PackageName == packageFilter {
|
||||||
|
found := false
|
||||||
|
for _, filteredItem := range filtered {
|
||||||
|
if filteredItem.StructName == spec.StructName {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
revel.RevelLog.Warn("Type found in package: "+packageFilter+
|
||||||
|
", but did not embed from: "+filepath.Base(targetType),
|
||||||
|
"name", spec.StructName, "path", spec.ImportPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ControllerSpecs returns the all the contollers that embeds
|
||||||
|
// `revel.Controller`
|
||||||
|
func (s *SourceInfo) ControllerSpecs() []*TypeInfo {
|
||||||
|
if s.controllerSpecs == nil {
|
||||||
|
s.controllerSpecs = s.TypesThatEmbed(revel.RevelImportPath+".Controller", "controllers")
|
||||||
|
}
|
||||||
|
return s.controllerSpecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSuites returns the all the Application tests that embeds
|
||||||
|
// `testing.TestSuite`
|
||||||
|
func (s *SourceInfo) TestSuites() []*TypeInfo {
|
||||||
|
if s.testSuites == nil {
|
||||||
|
s.testSuites = s.TypesThatEmbed(revel.RevelImportPath+"/testing.TestSuite", "testsuite")
|
||||||
|
}
|
||||||
|
return s.testSuites
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeExpr provides a type name that may be rewritten to use a package name.
|
||||||
|
type TypeExpr struct {
|
||||||
|
Expr string // The unqualified type expression, e.g. "[]*MyType"
|
||||||
|
PkgName string // The default package idenifier
|
||||||
|
pkgIndex int // The index where the package identifier should be inserted.
|
||||||
|
Valid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeName returns the fully-qualified type name for this expression.
|
||||||
|
// The caller may optionally specify a package name to override the default.
|
||||||
|
func (e TypeExpr) TypeName(pkgOverride string) string {
|
||||||
|
pkgName := revel.FirstNonEmpty(pkgOverride, e.PkgName)
|
||||||
|
if pkgName == "" {
|
||||||
|
return e.Expr
|
||||||
|
}
|
||||||
|
return e.Expr[:e.pkgIndex] + pkgName + "." + e.Expr[e.pkgIndex:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTypeExpr returns the syntactic expression for referencing this type in Go.
|
||||||
|
func NewTypeExpr(pkgName string, expr ast.Expr) TypeExpr {
|
||||||
|
switch t := expr.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
if IsBuiltinType(t.Name) {
|
||||||
|
pkgName = ""
|
||||||
|
}
|
||||||
|
return TypeExpr{t.Name, pkgName, 0, true}
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
e := NewTypeExpr(pkgName, t.X)
|
||||||
|
return TypeExpr{t.Sel.Name, e.Expr, 0, e.Valid}
|
||||||
|
case *ast.StarExpr:
|
||||||
|
e := NewTypeExpr(pkgName, t.X)
|
||||||
|
return TypeExpr{"*" + e.Expr, e.PkgName, e.pkgIndex + 1, e.Valid}
|
||||||
|
case *ast.ArrayType:
|
||||||
|
e := NewTypeExpr(pkgName, t.Elt)
|
||||||
|
return TypeExpr{"[]" + e.Expr, e.PkgName, e.pkgIndex + 2, e.Valid}
|
||||||
|
case *ast.Ellipsis:
|
||||||
|
e := NewTypeExpr(pkgName, t.Elt)
|
||||||
|
return TypeExpr{"[]" + e.Expr, e.PkgName, e.pkgIndex + 2, e.Valid}
|
||||||
|
default:
|
||||||
|
revel.RevelLog.Error("Failed to generate name for field. Make sure the field name is valid.")
|
||||||
|
}
|
||||||
|
return TypeExpr{Valid: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
var builtInTypes = map[string]struct{}{
|
||||||
|
"bool": {},
|
||||||
|
"byte": {},
|
||||||
|
"complex128": {},
|
||||||
|
"complex64": {},
|
||||||
|
"error": {},
|
||||||
|
"float32": {},
|
||||||
|
"float64": {},
|
||||||
|
"int": {},
|
||||||
|
"int16": {},
|
||||||
|
"int32": {},
|
||||||
|
"int64": {},
|
||||||
|
"int8": {},
|
||||||
|
"rune": {},
|
||||||
|
"string": {},
|
||||||
|
"uint": {},
|
||||||
|
"uint16": {},
|
||||||
|
"uint32": {},
|
||||||
|
"uint64": {},
|
||||||
|
"uint8": {},
|
||||||
|
"uintptr": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBuiltinType checks the given type is built-in types of Go
|
||||||
|
func IsBuiltinType(name string) bool {
|
||||||
|
_, ok := builtInTypes[name]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func importPathFromPath(root string) string {
|
||||||
|
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) {
|
||||||
|
return filepath.ToSlash(root[len(srcPath)+1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
srcPath := filepath.Join(build.Default.GOROOT, "src", "pkg")
|
||||||
|
if strings.HasPrefix(root, srcPath) {
|
||||||
|
revel.RevelLog.Warn("Code path should be in GOPATH, but is in GOROOT:", "path", root)
|
||||||
|
return filepath.ToSlash(root[len(srcPath)+1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
revel.RevelLog.Error("Unexpected! Code path is not in GOPATH:", "path", root)
|
||||||
|
return ""
|
||||||
|
}
|
193
cmd/harness/reflect_test.go
Normal file
193
cmd/harness/reflect_test.go
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// 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 (
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/revel/revel"
|
||||||
|
)
|
||||||
|
|
||||||
|
const validationKeysSource = `
|
||||||
|
package test
|
||||||
|
|
||||||
|
func (c *Application) testFunc(a, b int, user models.User) revel.Result {
|
||||||
|
// Line 5
|
||||||
|
c.Validation.Required(a)
|
||||||
|
c.Validation.Required(a).Message("Error message")
|
||||||
|
c.Validation.Required(a).
|
||||||
|
Message("Error message")
|
||||||
|
|
||||||
|
// Line 11
|
||||||
|
c.Validation.Required(user.Name)
|
||||||
|
c.Validation.Required(user.Name).Message("Error message")
|
||||||
|
|
||||||
|
// Line 15
|
||||||
|
c.Validation.MinSize(b, 12)
|
||||||
|
c.Validation.MinSize(b, 12).Message("Error message")
|
||||||
|
c.Validation.MinSize(b,
|
||||||
|
12)
|
||||||
|
|
||||||
|
// Line 21
|
||||||
|
c.Validation.Required(b == 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Model) Validate(v *revel.Validation) {
|
||||||
|
// Line 26
|
||||||
|
v.Required(m.name)
|
||||||
|
v.Required(m.name == "something").
|
||||||
|
Message("Error Message")
|
||||||
|
v.Required(!m.bool)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
var expectedValidationKeys = []map[int]string{
|
||||||
|
{
|
||||||
|
6: "a",
|
||||||
|
7: "a",
|
||||||
|
8: "a",
|
||||||
|
12: "user.Name",
|
||||||
|
13: "user.Name",
|
||||||
|
16: "b",
|
||||||
|
17: "b",
|
||||||
|
19: "b",
|
||||||
|
22: "b",
|
||||||
|
}, {
|
||||||
|
27: "m.name",
|
||||||
|
28: "m.name",
|
||||||
|
30: "m.bool",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// This tests the recording of line number to validation key of the preceeding
|
||||||
|
// example source.
|
||||||
|
func TestGetValidationKeys(t *testing.T) {
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
|
||||||
|
file, err := parser.ParseFile(fset, "validationKeysSource", validationKeysSource, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(file.Decls) != 2 {
|
||||||
|
t.Fatal("Expected 2 decl in the source, found", len(file.Decls))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, decl := range file.Decls {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(lineKeys) != len(expectedValidationKeys[i]) {
|
||||||
|
t.Error("Validation key map not the same size as expected:", lineKeys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var TypeExprs = map[string]TypeExpr{
|
||||||
|
"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) {
|
||||||
|
for typeStr, expected := range TypeExprs {
|
||||||
|
// Handle arrays and ... myself, since ParseExpr() does not.
|
||||||
|
array := strings.HasPrefix(typeStr, "[]")
|
||||||
|
if array {
|
||||||
|
typeStr = typeStr[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
ellipsis := strings.HasPrefix(typeStr, "...")
|
||||||
|
if ellipsis {
|
||||||
|
typeStr = typeStr[3:]
|
||||||
|
}
|
||||||
|
|
||||||
|
expr, err := parser.ParseExpr(typeStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Failed to parse test expr:", typeStr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if array {
|
||||||
|
expr = &ast.ArrayType{Lbrack: expr.Pos(), Len: nil, Elt: expr}
|
||||||
|
}
|
||||||
|
if ellipsis {
|
||||||
|
expr = &ast.Ellipsis{Ellipsis: expr.Pos(), Elt: expr}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := NewTypeExpr("pkg", expr)
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Error("Fail, expected", expected, ", was", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessBookingSource(t *testing.T) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
controllerPackage := "github.com/revel/examples/booking/app/controllers"
|
||||||
|
expectedControllerSpecs := []*TypeInfo{
|
||||||
|
{"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",
|
||||||
|
len(expectedControllerSpecs), len(sourceInfo.ControllerSpecs()))
|
||||||
|
}
|
||||||
|
|
||||||
|
NEXT_TEST:
|
||||||
|
for _, expected := range expectedControllerSpecs {
|
||||||
|
for _, actual := range sourceInfo.ControllerSpecs() {
|
||||||
|
if actual.StructName == expected.StructName {
|
||||||
|
if actual.ImportPath != expected.ImportPath {
|
||||||
|
t.Errorf("%s expected to have import path %s, actual %s",
|
||||||
|
actual.StructName, expected.ImportPath, actual.ImportPath)
|
||||||
|
}
|
||||||
|
if actual.PackageName != expected.PackageName {
|
||||||
|
t.Errorf("%s expected to have package name %s, actual %s",
|
||||||
|
actual.StructName, expected.PackageName, actual.PackageName)
|
||||||
|
}
|
||||||
|
continue NEXT_TEST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Errorf("Expected to find controller %s, but did not. Actuals: %s",
|
||||||
|
expected.StructName, sourceInfo.ControllerSpecs())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkProcessBookingSource(b *testing.B) {
|
||||||
|
revel.Init("", "github.com/revel/examples/booking", "")
|
||||||
|
revel.GetRootLogHandler().Disable()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := ProcessSource(revel.CodePaths)
|
||||||
|
if err != nil {
|
||||||
|
b.Error("Unexpected error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
cmd/leanote_revel/build.go
Executable file
130
cmd/leanote_revel/build.go
Executable file
@ -0,0 +1,130 @@
|
|||||||
|
// 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/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/leanote/leanote/cmd/harness"
|
||||||
|
"github.com/revel/revel"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdBuild = &Command{
|
||||||
|
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/examples/chat /tmp/chat
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmdBuild.Run = buildApp
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildApp(args []string) {
|
||||||
|
if len(args) < 2 {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appImportPath, destPath, mode := args[0], args[1], DefaultRunMode
|
||||||
|
if len(args) >= 3 {
|
||||||
|
mode = args[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
if !revel.Initialized {
|
||||||
|
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(filepath.Join(destPath, "run.sh")) {
|
||||||
|
errorf("Abort: %s exists and does not look like a build directory.", destPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
|
||||||
|
revel.ERROR.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(destPath, 0777); err != nil {
|
||||||
|
revel.ERROR.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app, reverr := harness.Build()
|
||||||
|
panicOnError(reverr, "Failed to build")
|
||||||
|
|
||||||
|
// Included are:
|
||||||
|
// - run scripts
|
||||||
|
// - binary
|
||||||
|
// - revel
|
||||||
|
// - app
|
||||||
|
|
||||||
|
// Revel and the app are in a directory structure mirroring import path
|
||||||
|
srcPath := filepath.Join(destPath, "src")
|
||||||
|
destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath))
|
||||||
|
tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(revel.RevelImportPath))
|
||||||
|
mustCopyFile(destBinaryPath, app.BinaryPath)
|
||||||
|
mustChmod(destBinaryPath, 0755)
|
||||||
|
_ = 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()
|
||||||
|
modulePaths := make(map[string]string) // import path => filesystem path
|
||||||
|
for _, section := range config.Sections() {
|
||||||
|
options, _ := config.SectionOptions(section)
|
||||||
|
for _, key := range options {
|
||||||
|
if !strings.HasPrefix(key, "module.") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
moduleImportPath, _ := config.String(section, key)
|
||||||
|
if moduleImportPath == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
modulePath, err := revel.ResolveImportPath(moduleImportPath)
|
||||||
|
if err != nil {
|
||||||
|
revel.ERROR.Fatalln("Failed to load module %s: %s", key[len("module."):], err)
|
||||||
|
}
|
||||||
|
modulePaths[moduleImportPath] = modulePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for importPath, fsPath := range modulePaths {
|
||||||
|
_ = mustCopyDir(filepath.Join(srcPath, importPath), fsPath, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmplData, runShPath := map[string]interface{}{
|
||||||
|
"BinName": filepath.Base(app.BinaryPath),
|
||||||
|
"ImportPath": appImportPath,
|
||||||
|
"Mode": mode,
|
||||||
|
}, filepath.Join(destPath, "run.sh")
|
||||||
|
|
||||||
|
mustRenderTemplate(
|
||||||
|
runShPath,
|
||||||
|
filepath.Join(revel.RevelPath, "..", "cmd", "revel", "package_run.sh.template"),
|
||||||
|
tmplData)
|
||||||
|
|
||||||
|
mustChmod(runShPath, 0755)
|
||||||
|
|
||||||
|
mustRenderTemplate(
|
||||||
|
filepath.Join(destPath, "run.bat"),
|
||||||
|
filepath.Join(revel.RevelPath, "..", "cmd", "revel", "package_run.bat.template"),
|
||||||
|
tmplData)
|
||||||
|
}
|
57
cmd/leanote_revel/clean.go
Executable file
57
cmd/leanote_revel/clean.go
Executable file
@ -0,0 +1,57 @@
|
|||||||
|
// 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/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdClean = &Command{
|
||||||
|
UsageLine: "clean [import path]",
|
||||||
|
Short: "clean a Revel application's temp files",
|
||||||
|
Long: `
|
||||||
|
Clean the Revel web application named by the given import path.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
revel clean github.com/revel/examples/chat
|
||||||
|
|
||||||
|
It removes the app/tmp and app/routes directory.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmdClean.Run = cleanApp
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanApp(args []string) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, cmdClean.Long)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appPkg, err := build.Import(args[0], "", build.FindOnly)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Abort: Failed to find import path:", 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
223
cmd/leanote_revel/new.go
Executable file
223
cmd/leanote_revel/new.go
Executable file
@ -0,0 +1,223 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/revel/revel"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdNew = &Command{
|
||||||
|
UsageLine: "new [path] [skeleton]",
|
||||||
|
Short: "create a skeleton Revel application",
|
||||||
|
Long: `
|
||||||
|
New creates a few files to get a new Revel application running quickly.
|
||||||
|
|
||||||
|
It puts all of the files in the given import path, taking the final element in
|
||||||
|
the path to be the app name.
|
||||||
|
|
||||||
|
Skeleton is an optional argument, provided as an import path
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
revel new import/path/helloworld
|
||||||
|
|
||||||
|
revel new import/path/helloworld import/path/skeleton
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmdNew.Run = newApp
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
|
||||||
|
// go related paths
|
||||||
|
gopath string
|
||||||
|
gocmd string
|
||||||
|
srcRoot string
|
||||||
|
|
||||||
|
// revel related paths
|
||||||
|
revelPkg *build.Package
|
||||||
|
revelCmdPkg *build.Package
|
||||||
|
appPath string
|
||||||
|
appName string
|
||||||
|
basePath string
|
||||||
|
importPath string
|
||||||
|
skeletonPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
func newApp(args []string) {
|
||||||
|
// check for proper args by count
|
||||||
|
if len(args) == 0 {
|
||||||
|
errorf("No import path given.\nRun 'revel help new' for usage.\n")
|
||||||
|
}
|
||||||
|
if len(args) > 2 {
|
||||||
|
errorf("Too many arguments provided.\nRun 'revel help new' for usage.\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
revel.ERROR.SetFlags(log.LstdFlags)
|
||||||
|
|
||||||
|
// checking and setting go paths
|
||||||
|
initGoPaths()
|
||||||
|
|
||||||
|
// checking and setting application
|
||||||
|
setApplicationPath(args)
|
||||||
|
|
||||||
|
// checking and setting skeleton
|
||||||
|
setSkeletonPath(args)
|
||||||
|
|
||||||
|
// copy files to new app directory
|
||||||
|
copyNewAppFiles()
|
||||||
|
|
||||||
|
// goodbye world
|
||||||
|
fmt.Fprintln(os.Stdout, "Your application is ready:\n ", appPath)
|
||||||
|
fmt.Fprintln(os.Stdout, "\nYou can run it with:\n revel run", importPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
|
||||||
|
func generateSecret() string {
|
||||||
|
chars := make([]byte, 64)
|
||||||
|
for i := 0; i < 64; i++ {
|
||||||
|
chars[i] = alphaNumeric[rand.Intn(len(alphaNumeric))]
|
||||||
|
}
|
||||||
|
return string(chars)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup and set Go related variables
|
||||||
|
func initGoPaths() {
|
||||||
|
// lookup go path
|
||||||
|
gopath = build.Default.GOPATH
|
||||||
|
if gopath == "" {
|
||||||
|
errorf("Abort: GOPATH environment variable is not set. " +
|
||||||
|
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for go executable
|
||||||
|
var err error
|
||||||
|
gocmd, err = exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
errorf("Go executable not found in PATH.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// revel/revel#1004 choose go path relative to current working directory
|
||||||
|
workingDir, _ := os.Getwd()
|
||||||
|
goPathList := filepath.SplitList(gopath)
|
||||||
|
for _, path := range goPathList {
|
||||||
|
if strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
|
||||||
|
srcRoot = path
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
path, _ = filepath.EvalSymlinks(path)
|
||||||
|
if len(path) > 0 && strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
|
||||||
|
srcRoot = path
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(srcRoot) == 0 {
|
||||||
|
revel.ERROR.Fatalln("Abort: could not create a Revel application outside of GOPATH.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// set go src path
|
||||||
|
srcRoot = filepath.Join(srcRoot, "src")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setApplicationPath(args []string) {
|
||||||
|
var err error
|
||||||
|
importPath = args[0]
|
||||||
|
|
||||||
|
// revel/revel#1014 validate relative path, we cannot use built-in functions
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = build.Import(importPath, "", build.FindOnly)
|
||||||
|
if err == nil {
|
||||||
|
errorf("Abort: Import path %s already exists.\n", importPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
if basePath == "." {
|
||||||
|
// we need to remove the a single '.' when
|
||||||
|
// the app is in the $GOROOT/src directory
|
||||||
|
basePath = ""
|
||||||
|
} else {
|
||||||
|
// we need to append a '/' when the app is
|
||||||
|
// is a subdirectory such as $GOROOT/src/path/to/revelapp
|
||||||
|
basePath += "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSkeletonPath(args []string) {
|
||||||
|
var err error
|
||||||
|
if len(args) == 2 { // user specified
|
||||||
|
skeletonName := args[1]
|
||||||
|
_, err = build.Import(skeletonName, "", build.FindOnly)
|
||||||
|
if err != nil {
|
||||||
|
// Execute "go get <pkg>"
|
||||||
|
getCmd := exec.Command(gocmd, "get", "-d", skeletonName)
|
||||||
|
fmt.Println("Exec:", getCmd.Args)
|
||||||
|
getOutput, err := getCmd.CombinedOutput()
|
||||||
|
|
||||||
|
// check getOutput for no buildible string
|
||||||
|
bpos := bytes.Index(getOutput, []byte("no buildable Go source files in"))
|
||||||
|
if err != nil && bpos == -1 {
|
||||||
|
errorf("Abort: Could not find or 'go get' Skeleton source code: %s\n%s\n", getOutput, skeletonName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// use the
|
||||||
|
skeletonPath = filepath.Join(srcRoot, skeletonName)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// use the revel default
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyNewAppFiles() {
|
||||||
|
var err error
|
||||||
|
err = os.MkdirAll(appPath, 0777)
|
||||||
|
panicOnError(err, "Failed to create directory "+appPath)
|
||||||
|
|
||||||
|
_ = mustCopyDir(appPath, skeletonPath, map[string]interface{}{
|
||||||
|
// app.conf
|
||||||
|
"AppName": appName,
|
||||||
|
"BasePath": basePath,
|
||||||
|
"Secret": generateSecret(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore.
|
||||||
|
gitignore := ".gitignore"
|
||||||
|
mustCopyFile(filepath.Join(appPath, gitignore), filepath.Join(skeletonPath, gitignore))
|
||||||
|
|
||||||
|
}
|
69
cmd/leanote_revel/package.go
Executable file
69
cmd/leanote_revel/package.go
Executable file
@ -0,0 +1,69 @@
|
|||||||
|
// 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"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/revel/revel"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdPackage = &Command{
|
||||||
|
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/examples/chat
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmdPackage.Run = packageApp
|
||||||
|
}
|
||||||
|
|
||||||
|
func packageApp(args []string) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Fprint(os.Stderr, cmdPackage.Long)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the run mode.
|
||||||
|
mode := DefaultRunMode
|
||||||
|
if len(args) >= 2 {
|
||||||
|
mode = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
appImportPath := args[0]
|
||||||
|
revel.Init(mode, appImportPath, "")
|
||||||
|
|
||||||
|
// Remove the archive if it already exists.
|
||||||
|
destFile := filepath.Base(revel.BasePath) + ".tar.gz"
|
||||||
|
if err := os.Remove(destFile); err != nil && !os.IsNotExist(err) {
|
||||||
|
revel.ERROR.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, mode})
|
||||||
|
|
||||||
|
// Create the zip file.
|
||||||
|
archiveName := mustTarGzDir(destFile, tmpDir)
|
||||||
|
|
||||||
|
fmt.Println("Your archive is ready:", archiveName)
|
||||||
|
}
|
2
cmd/leanote_revel/package_run.bat.template
Executable file
2
cmd/leanote_revel/package_run.bat.template
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
{{.BinName}} -importPath {{.ImportPath}} -srcPath %CD%\src -runMode {{.Mode}}
|
3
cmd/leanote_revel/package_run.sh.template
Executable file
3
cmd/leanote_revel/package_run.sh.template
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
||||||
|
"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}
|
144
cmd/leanote_revel/rev.go
Executable file
144
cmd/leanote_revel/rev.go
Executable file
@ -0,0 +1,144 @@
|
|||||||
|
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||||
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// The command line tool for running Revel apps.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/agtorre/gocolorize"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RevelCmdImportPath Revel framework cmd tool import path
|
||||||
|
RevelCmdImportPath = "github.com/revel/cmd"
|
||||||
|
|
||||||
|
// DefaultRunMode for revel's application
|
||||||
|
DefaultRunMode = "dev"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command structure cribbed from the genius organization of the "go" command.
|
||||||
|
type Command struct {
|
||||||
|
Run func(args []string)
|
||||||
|
UsageLine, Short, Long string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns command name from usage line
|
||||||
|
func (cmd *Command) Name() string {
|
||||||
|
name := cmd.UsageLine
|
||||||
|
i := strings.Index(name, " ")
|
||||||
|
if i >= 0 {
|
||||||
|
name = name[:i]
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
var commands = []*Command{
|
||||||
|
cmdNew,
|
||||||
|
cmdRun,
|
||||||
|
cmdBuild,
|
||||||
|
cmdPackage,
|
||||||
|
cmdClean,
|
||||||
|
cmdTest,
|
||||||
|
cmdVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
gocolorize.SetPlain(true)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stdout, gocolorize.NewColor("blue").Paint(header))
|
||||||
|
flag.Usage = func() { usage(1) }
|
||||||
|
flag.Parse()
|
||||||
|
args := flag.Args()
|
||||||
|
|
||||||
|
if len(args) < 1 || args[0] == "help" {
|
||||||
|
if len(args) == 1 {
|
||||||
|
usage(0)
|
||||||
|
}
|
||||||
|
if len(args) > 1 {
|
||||||
|
for _, cmd := range commands {
|
||||||
|
if cmd.Name() == args[1] {
|
||||||
|
tmpl(os.Stdout, helpTemplate, cmd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usage(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commands use panic to abort execution when something goes wrong.
|
||||||
|
// Panics are logged at the point of error. Ignore those.
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
if _, ok := err.(LoggedError); !ok {
|
||||||
|
// This panic was not expected / logged.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, cmd := range commands {
|
||||||
|
if cmd.Name() == args[0] {
|
||||||
|
cmd.Run(args[1:])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorf("unknown command %q\nRun 'revel help' for usage.\n", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorf(format string, args ...interface{}) {
|
||||||
|
// Ensure the user's command prompt starts on the next line.
|
||||||
|
if !strings.HasSuffix(format, "\n") {
|
||||||
|
format += "\n"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, format, args...)
|
||||||
|
panic(LoggedError{}) // Panic instead of os.Exit so that deferred will run.
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = `~
|
||||||
|
~ revel! http://revel.github.io
|
||||||
|
~
|
||||||
|
`
|
||||||
|
|
||||||
|
const usageTemplate = `usage: revel command [arguments]
|
||||||
|
|
||||||
|
The commands are:
|
||||||
|
{{range .}}
|
||||||
|
{{.Name | printf "%-11s"}} {{.Short}}{{end}}
|
||||||
|
|
||||||
|
Use "revel help [command]" for more information.
|
||||||
|
`
|
||||||
|
|
||||||
|
var helpTemplate = `usage: revel {{.UsageLine}}
|
||||||
|
{{.Long}}
|
||||||
|
`
|
||||||
|
|
||||||
|
func usage(exitCode int) {
|
||||||
|
tmpl(os.Stderr, usageTemplate, commands)
|
||||||
|
os.Exit(exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tmpl(w io.Writer, text string, data interface{}) {
|
||||||
|
t := template.New("top")
|
||||||
|
template.Must(t.Parse(text))
|
||||||
|
if err := t.Execute(w, data); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
}
|
172
cmd/leanote_revel/run.go
Executable file
172
cmd/leanote_revel/run.go
Executable file
@ -0,0 +1,172 @@
|
|||||||
|
// 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 (
|
||||||
|
"go/build"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/leanote/leanote/cmd/harness"
|
||||||
|
"github.com/revel/revel"
|
||||||
|
// "fmt"
|
||||||
|
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdRun = &Command{
|
||||||
|
UsageLine: "run [import path] [run mode] [port]",
|
||||||
|
Short: "run a Revel application",
|
||||||
|
Long: `
|
||||||
|
Run the Revel web application named by the given import path.
|
||||||
|
|
||||||
|
For example, to run the chat room sample application:
|
||||||
|
|
||||||
|
revel run github.com/revel/examples/chat dev
|
||||||
|
|
||||||
|
The run mode is used to select which set of app.conf configuration should
|
||||||
|
apply and may be used to determine logic in the application itself.
|
||||||
|
|
||||||
|
Run mode defaults to "dev".
|
||||||
|
|
||||||
|
You can set a port as an optional third parameter. For example:
|
||||||
|
|
||||||
|
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 parseRunArgs(args []string) *RunArgs {
|
||||||
|
inputArgs := RunArgs{
|
||||||
|
ImportPath: importPathFromCurrentDir(),
|
||||||
|
Mode: DefaultRunMode,
|
||||||
|
Port: revel.HTTPPort,
|
||||||
|
}
|
||||||
|
switch len(args) {
|
||||||
|
case 3:
|
||||||
|
// Possibile 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:
|
||||||
|
// Possibile 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:
|
||||||
|
// Possibile combinations
|
||||||
|
// 1. revel run [import-path]
|
||||||
|
// 2. revel run [port]
|
||||||
|
// 3. revel run [run-mode]
|
||||||
|
if _, err := build.Import(args[0], "", build.FindOnly); 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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &inputArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// findSrcPaths uses the "go/build" package to find the source root for Revel
|
||||||
|
// and the app.
|
||||||
|
func findSrcPaths(importPath string) (appSourcePath string) {
|
||||||
|
var (
|
||||||
|
gopaths = filepath.SplitList(build.Default.GOPATH)
|
||||||
|
goroot = build.Default.GOROOT
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(gopaths) == 0 {
|
||||||
|
revel.ERROR.Fatalln("GOPATH environment variable is not set. ",
|
||||||
|
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if revel.ContainsString(gopaths, goroot) {
|
||||||
|
revel.ERROR.Fatalf("GOPATH (%s) must not include your GOROOT (%s). "+
|
||||||
|
"Please refer to http://golang.org/doc/code.html to configure your Go environment.",
|
||||||
|
gopaths, goroot)
|
||||||
|
}
|
||||||
|
|
||||||
|
appPkg, err := build.Import(importPath, "", build.FindOnly)
|
||||||
|
if err != nil {
|
||||||
|
revel.ERROR.Fatalln("Failed to import", importPath, "with error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return appPkg.SrcRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
func runApp(args []string) {
|
||||||
|
runArgs := parseRunArgs(args)
|
||||||
|
|
||||||
|
// Find and parse app.conf
|
||||||
|
// fmt.Println(runArgs.ImportPath + "/vendor")
|
||||||
|
// revel.Init(runArgs.Mode, runArgs.ImportPath, runArgs.ImportPath + "/vendor")
|
||||||
|
srcPath := findSrcPaths(runArgs.ImportPath)
|
||||||
|
srcPath = ""
|
||||||
|
revel.Init(runArgs.Mode, runArgs.ImportPath, srcPath)
|
||||||
|
revel.LoadMimeConfig()
|
||||||
|
|
||||||
|
// fallback to default port
|
||||||
|
if runArgs.Port == 0 {
|
||||||
|
runArgs.Port = revel.HTTPPort
|
||||||
|
}
|
||||||
|
|
||||||
|
revel.INFO.Printf("Running %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, runArgs.Mode)
|
||||||
|
revel.TRACE.Println("Base 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 = runArgs.Port
|
||||||
|
harness.NewHarness().Run() // Never returns.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, just build and run the app.
|
||||||
|
revel.TRACE.Println("Running in live build mode.")
|
||||||
|
app, err := harness.Build()
|
||||||
|
if err != nil {
|
||||||
|
errorf("Failed to build app: %s", err)
|
||||||
|
}
|
||||||
|
app.Port = runArgs.Port
|
||||||
|
app.Cmd().Run()
|
||||||
|
}
|
3
cmd/leanote_revel/skeleton/.gitignore
vendored
Executable file
3
cmd/leanote_revel/skeleton/.gitignore
vendored
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
test-results/
|
||||||
|
tmp/
|
||||||
|
routes/
|
43
cmd/leanote_revel/skeleton/README.md
Executable file
43
cmd/leanote_revel/skeleton/README.md
Executable 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
cmd/leanote_revel/skeleton/app/controllers/app.go
Executable file
13
cmd/leanote_revel/skeleton/app/controllers/app.go
Executable 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()
|
||||||
|
}
|
59
cmd/leanote_revel/skeleton/app/init.go
Executable file
59
cmd/leanote_revel/skeleton/app/init.go
Executable file
@ -0,0 +1,59 @@
|
|||||||
|
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
|
||||||
|
// TODO turn this into revel.HeaderFilter
|
||||||
|
// should probably also have a filter for CSRF
|
||||||
|
// not sure if it can go in the same filter or not
|
||||||
|
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
cmd/leanote_revel/skeleton/app/views/App/Index.html
Executable file
21
cmd/leanote_revel/skeleton/app/views/App/Index.html
Executable 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
cmd/leanote_revel/skeleton/app/views/debug.html
Executable file
64
cmd/leanote_revel/skeleton/app/views/debug.html
Executable 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
cmd/leanote_revel/skeleton/app/views/errors/404.html
Executable file
20
cmd/leanote_revel/skeleton/app/views/errors/404.html
Executable 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
cmd/leanote_revel/skeleton/app/views/errors/500.html
Executable file
16
cmd/leanote_revel/skeleton/app/views/errors/500.html
Executable 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
cmd/leanote_revel/skeleton/app/views/flash.html
Executable file
18
cmd/leanote_revel/skeleton/app/views/flash.html
Executable 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
cmd/leanote_revel/skeleton/app/views/footer.html
Executable file
5
cmd/leanote_revel/skeleton/app/views/footer.html
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
{{if eq .RunMode "dev"}}
|
||||||
|
{{template "debug.html" .}}
|
||||||
|
{{end}}
|
||||||
|
</body>
|
||||||
|
</html>
|
19
cmd/leanote_revel/skeleton/app/views/header.html
Executable file
19
cmd/leanote_revel/skeleton/app/views/header.html
Executable 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>
|
258
cmd/leanote_revel/skeleton/conf/app.conf.template
Executable file
258
cmd/leanote_revel/skeleton/conf/app.conf.template
Executable file
@ -0,0 +1,258 @@
|
|||||||
|
################################################################################
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
# Prefixes for each log message line.
|
||||||
|
# User can override these prefix values within any section
|
||||||
|
# For e.g: [dev], [prod], etc
|
||||||
|
log.trace.prefix = "TRACE "
|
||||||
|
log.info.prefix = "INFO "
|
||||||
|
log.warn.prefix = "WARN "
|
||||||
|
log.error.prefix = "ERROR "
|
||||||
|
|
||||||
|
|
||||||
|
# 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.trace.output = off
|
||||||
|
log.info.output = stderr
|
||||||
|
log.warn.output = stderr
|
||||||
|
log.error.output = stderr
|
||||||
|
|
||||||
|
|
||||||
|
# Revel log flags. Possible flags defined by the Go `log` package. Go log is
|
||||||
|
# "Bits OR'ed together to control what's printed
|
||||||
|
# See:
|
||||||
|
# https://golang.org/pkg/log/#pkg-constants
|
||||||
|
# Values:
|
||||||
|
# "0"
|
||||||
|
# Just log the message, turn off the flags.
|
||||||
|
# "3"
|
||||||
|
# log.LstdFlags (log.Ldate|log.Ltime)
|
||||||
|
# "19"
|
||||||
|
# log.Ldate|log.Ltime|log.Lshortfile
|
||||||
|
# "23"
|
||||||
|
# log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile
|
||||||
|
log.trace.flags = 19
|
||||||
|
log.info.flags = 19
|
||||||
|
log.warn.flags = 19
|
||||||
|
log.error.flags = 19
|
||||||
|
|
||||||
|
|
||||||
|
# Revel request access log
|
||||||
|
# Access log line format:
|
||||||
|
# RequestStartTime ClientIP ResponseStatus RequestLatency HTTPMethod URLPath
|
||||||
|
# Sample format:
|
||||||
|
# 2016/05/25 17:46:37.112 127.0.0.1 200 270.157µs GET /
|
||||||
|
log.request.output = stderr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# 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.trace.output = off
|
||||||
|
log.info.output = off
|
||||||
|
log.warn.output = log/%(app.name)s.log
|
||||||
|
log.error.output = log/%(app.name)s.log
|
||||||
|
|
||||||
|
# Revel log flags. Possible flags defined by the Go `log` package,
|
||||||
|
# please refer https://golang.org/pkg/log/#pkg-constants
|
||||||
|
# Go log is "Bits or'ed together to control what's printed"
|
||||||
|
# Examples:
|
||||||
|
# 0 => just log the message, turn off the flags
|
||||||
|
# 3 => log.LstdFlags (log.Ldate|log.Ltime)
|
||||||
|
# 19 => log.Ldate|log.Ltime|log.Lshortfile
|
||||||
|
# 23 => log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile
|
||||||
|
log.trace.flags = 3
|
||||||
|
log.info.flags = 3
|
||||||
|
log.warn.flags = 3
|
||||||
|
log.error.flags = 3
|
||||||
|
|
||||||
|
|
||||||
|
# Revel request access log
|
||||||
|
# Access log line format:
|
||||||
|
# RequestStartTime ClientIP ResponseStatus RequestLatency HTTPMethod URLPath
|
||||||
|
# Sample format:
|
||||||
|
# 2016/05/25 17:46:37.112 127.0.0.1 200 270.157µs GET /
|
||||||
|
# Example:
|
||||||
|
# log.request.output = %(app.name)s-request.log
|
||||||
|
log.request.output = off
|
19
cmd/leanote_revel/skeleton/conf/routes
Executable file
19
cmd/leanote_revel/skeleton/conf/routes
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
# 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
|
||||||
|
* /:controller/:action :controller.:action
|
7
cmd/leanote_revel/skeleton/messages/sample.en
Executable file
7
cmd/leanote_revel/skeleton/messages/sample.en
Executable 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
cmd/leanote_revel/skeleton/public/css/bootstrap-3.3.6.min.css
vendored
Executable file
5
cmd/leanote_revel/skeleton/public/css/bootstrap-3.3.6.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
BIN
cmd/leanote_revel/skeleton/public/fonts/glyphicons-halflings-regular.ttf
Executable file
BIN
cmd/leanote_revel/skeleton/public/fonts/glyphicons-halflings-regular.ttf
Executable file
Binary file not shown.
BIN
cmd/leanote_revel/skeleton/public/fonts/glyphicons-halflings-regular.woff
Executable file
BIN
cmd/leanote_revel/skeleton/public/fonts/glyphicons-halflings-regular.woff
Executable file
Binary file not shown.
BIN
cmd/leanote_revel/skeleton/public/fonts/glyphicons-halflings-regular.woff2
Executable file
BIN
cmd/leanote_revel/skeleton/public/fonts/glyphicons-halflings-regular.woff2
Executable file
Binary file not shown.
BIN
cmd/leanote_revel/skeleton/public/img/favicon.png
Executable file
BIN
cmd/leanote_revel/skeleton/public/img/favicon.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
7
cmd/leanote_revel/skeleton/public/js/bootstrap-3.3.6.min.js
vendored
Executable file
7
cmd/leanote_revel/skeleton/public/js/bootstrap-3.3.6.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
4
cmd/leanote_revel/skeleton/public/js/jquery-2.2.4.min.js
vendored
Executable file
4
cmd/leanote_revel/skeleton/public/js/jquery-2.2.4.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
23
cmd/leanote_revel/skeleton/tests/apptest.go
Executable file
23
cmd/leanote_revel/skeleton/tests/apptest.go
Executable 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")
|
||||||
|
}
|
314
cmd/leanote_revel/test.go
Executable file
314
cmd/leanote_revel/test.go
Executable file
@ -0,0 +1,314 @@
|
|||||||
|
// 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"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/harness"
|
||||||
|
"github.com/revel/modules/testrunner/app/controllers"
|
||||||
|
"github.com/revel/revel"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdTest = &Command{
|
||||||
|
UsageLine: "test [import path] [run mode] [suite.method]",
|
||||||
|
Short: "run all tests from the command-line",
|
||||||
|
Long: `
|
||||||
|
Run all tests for the Revel app named by the given import path.
|
||||||
|
|
||||||
|
For example, to run the booking sample application's tests:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Run mode defaults to "dev".
|
||||||
|
|
||||||
|
You can run a specific suite (and function) by specifying a third parameter.
|
||||||
|
For example, to run all of UserTest:
|
||||||
|
|
||||||
|
revel test outspoken test UserTest
|
||||||
|
|
||||||
|
or one of UserTest's methods:
|
||||||
|
|
||||||
|
revel test outspoken test UserTest.Test1
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmdTest.Run = testApp
|
||||||
|
}
|
||||||
|
|
||||||
|
func testApp(args []string) {
|
||||||
|
var err error
|
||||||
|
if len(args) == 0 {
|
||||||
|
errorf("No import path given.\nRun 'revel help test' for usage.\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := DefaultRunMode
|
||||||
|
if len(args) >= 2 {
|
||||||
|
mode = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find and parse app.conf
|
||||||
|
revel.Init(mode, args[0], "")
|
||||||
|
|
||||||
|
// Ensure that the testrunner is loaded in this mode.
|
||||||
|
checkTestRunner()
|
||||||
|
|
||||||
|
// Create a directory to hold the test result files.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if err = os.Mkdir(resultPath, 0777); err != nil {
|
||||||
|
errorf("Failed to create test result directory %s: %s", resultPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct all the output into a file in the test-results directory.
|
||||||
|
file, err := os.OpenFile(filepath.Join(resultPath, "app.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
errorf("Failed to create test result log file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app, reverr := harness.Build()
|
||||||
|
if reverr != nil {
|
||||||
|
errorf("Error building: %s", reverr)
|
||||||
|
}
|
||||||
|
cmd := app.Cmd()
|
||||||
|
cmd.Stderr = io.MultiWriter(cmd.Stderr, file)
|
||||||
|
cmd.Stdout = io.MultiWriter(cmd.Stderr, file)
|
||||||
|
|
||||||
|
// Start the app...
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
errorf("%s", err)
|
||||||
|
}
|
||||||
|
defer cmd.Kill()
|
||||||
|
revel.INFO.Printf("Testing %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, mode)
|
||||||
|
|
||||||
|
var httpAddr = revel.HTTPAddr
|
||||||
|
if httpAddr == "" {
|
||||||
|
httpAddr = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
testSuiteCount := len(*testSuites)
|
||||||
|
fmt.Printf("\n%d test suite%s to run.\n", testSuiteCount, pluralize(testSuiteCount, "", "s"))
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// Run each suite.
|
||||||
|
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 {
|
||||||
|
fmt.Printf("Failures:\n")
|
||||||
|
for _, result := range failedResult.Results {
|
||||||
|
if !result.Passed {
|
||||||
|
fmt.Printf("%s.%s\n", failedResult.Name, result.Name)
|
||||||
|
fmt.Printf("%s\n\n", result.ErrorSummary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeResultFile(resultPath, "result.failed", "failed")
|
||||||
|
errorf("Some tests failed. See file://%s for results.", resultPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeResultFile(resultPath, name, content string) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pluralize(num int, singular, plural string) string {
|
||||||
|
if num == 1 {
|
||||||
|
return singular
|
||||||
|
}
|
||||||
|
return plural
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filters test suites and individual tests to match
|
||||||
|
// the parsed command line parameter
|
||||||
|
func filterTestSuites(suites *[]controllers.TestSuiteDesc, suiteArgument string) *[]controllers.TestSuiteDesc {
|
||||||
|
var suiteName, testName string
|
||||||
|
argArray := strings.Split(suiteArgument, ".")
|
||||||
|
suiteName = argArray[0]
|
||||||
|
if suiteName == "" {
|
||||||
|
return suites
|
||||||
|
}
|
||||||
|
if len(argArray) == 2 {
|
||||||
|
testName = argArray[1]
|
||||||
|
}
|
||||||
|
for _, suite := range *suites {
|
||||||
|
if suite.Name != suiteName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if testName == "" {
|
||||||
|
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{
|
||||||
|
{
|
||||||
|
Name: suite.Name,
|
||||||
|
Tests: []controllers.TestDesc{test},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errorf("Couldn't find test %s in suite %s", testName, suiteName)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
176
cmd/leanote_revel/util.go
Executable file
176
cmd/leanote_revel/util.go
Executable file
@ -0,0 +1,176 @@
|
|||||||
|
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||||
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/revel/revel"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoggedError is wrapper to differentiate logged panics from unexpected ones.
|
||||||
|
type LoggedError struct{ error }
|
||||||
|
|
||||||
|
func panicOnError(err error, msg string) {
|
||||||
|
if revErr, ok := err.(*revel.Error); (ok && revErr != nil) || (!ok && err != nil) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Abort: %s: %s\n", msg, err)
|
||||||
|
panic(LoggedError{err})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustCopyFile(destFilename, srcFilename string) {
|
||||||
|
destFile, err := os.Create(destFilename)
|
||||||
|
panicOnError(err, "Failed to create file "+destFilename)
|
||||||
|
|
||||||
|
srcFile, err := os.Open(srcFilename)
|
||||||
|
panicOnError(err, "Failed to open file "+srcFilename)
|
||||||
|
|
||||||
|
_, err = io.Copy(destFile, srcFile)
|
||||||
|
panicOnError(err,
|
||||||
|
fmt.Sprintf("Failed to copy data from %s to %s", srcFile.Name(), destFile.Name()))
|
||||||
|
|
||||||
|
err = destFile.Close()
|
||||||
|
panicOnError(err, "Failed to close file "+destFile.Name())
|
||||||
|
|
||||||
|
err = srcFile.Close()
|
||||||
|
panicOnError(err, "Failed to close file "+srcFile.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustRenderTemplate(destPath, srcPath string, data map[string]interface{}) {
|
||||||
|
tmpl, err := template.ParseFiles(srcPath)
|
||||||
|
panicOnError(err, "Failed to parse template "+srcPath)
|
||||||
|
|
||||||
|
f, err := os.Create(destPath)
|
||||||
|
panicOnError(err, "Failed to create "+destPath)
|
||||||
|
|
||||||
|
err = tmpl.Execute(f, data)
|
||||||
|
panicOnError(err, "Failed to render template "+srcPath)
|
||||||
|
|
||||||
|
err = f.Close()
|
||||||
|
panicOnError(err, "Failed to close "+f.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustChmod(filename string, mode os.FileMode) {
|
||||||
|
err := os.Chmod(filename, mode)
|
||||||
|
panicOnError(err, fmt.Sprintf("Failed to chmod %d %q", mode, filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyDir copies a directory tree over to a new directory. Any files ending in
|
||||||
|
// ".template" are treated as a Go template and rendered using the given data.
|
||||||
|
// Additionally, the trailing ".template" is stripped from the file name.
|
||||||
|
// Also, dot files and dot directories are skipped.
|
||||||
|
func mustCopyDir(destDir, srcDir string, data map[string]interface{}) error {
|
||||||
|
return revel.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
|
||||||
|
// Get the relative path from the source base, and the corresponding path in
|
||||||
|
// the dest directory.
|
||||||
|
relSrcPath := strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator))
|
||||||
|
destPath := filepath.Join(destDir, relSrcPath)
|
||||||
|
|
||||||
|
// Skip dot files and dot directories.
|
||||||
|
if strings.HasPrefix(relSrcPath, ".") {
|
||||||
|
if info.IsDir() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a subdirectory if necessary.
|
||||||
|
if info.IsDir() {
|
||||||
|
err := os.MkdirAll(filepath.Join(destDir, relSrcPath), 0777)
|
||||||
|
if !os.IsExist(err) {
|
||||||
|
panicOnError(err, "Failed to create directory")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this file ends in ".template", render it as a template.
|
||||||
|
if strings.HasSuffix(relSrcPath, ".template") {
|
||||||
|
mustRenderTemplate(destPath[:len(destPath)-len(".template")], srcPath, data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, just copy it over.
|
||||||
|
mustCopyFile(destPath, srcPath)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustTarGzDir(destFilename, srcDir string) string {
|
||||||
|
zipFile, err := os.Create(destFilename)
|
||||||
|
panicOnError(err, "Failed to create archive")
|
||||||
|
defer func() {
|
||||||
|
_ = zipFile.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
gzipWriter := gzip.NewWriter(zipFile)
|
||||||
|
defer func() {
|
||||||
|
_ = gzipWriter.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
tarWriter := tar.NewWriter(gzipWriter)
|
||||||
|
defer func() {
|
||||||
|
_ = tarWriter.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
_ = revel.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
srcFile, err := os.Open(srcPath)
|
||||||
|
panicOnError(err, "Failed to read source file")
|
||||||
|
defer func() {
|
||||||
|
_ = srcFile.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = tarWriter.WriteHeader(&tar.Header{
|
||||||
|
Name: strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator)),
|
||||||
|
Size: info.Size(),
|
||||||
|
Mode: int64(info.Mode()),
|
||||||
|
ModTime: info.ModTime(),
|
||||||
|
})
|
||||||
|
panicOnError(err, "Failed to write tar entry header")
|
||||||
|
|
||||||
|
_, err = io.Copy(tarWriter, srcFile)
|
||||||
|
panicOnError(err, "Failed to copy")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return zipFile.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func exists(filename string) bool {
|
||||||
|
_, err := os.Stat(filename)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty returns true if the given directory is empty.
|
||||||
|
// the directory must exist.
|
||||||
|
func empty(dirname string) bool {
|
||||||
|
dir, err := os.Open(dirname)
|
||||||
|
if err != nil {
|
||||||
|
errorf("error opening directory: %s", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = dir.Close()
|
||||||
|
}()
|
||||||
|
results, _ := dir.Readdir(1)
|
||||||
|
return len(results) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func importPathFromCurrentDir() string {
|
||||||
|
pwd, _ := os.Getwd()
|
||||||
|
importPath, _ := filepath.Rel(filepath.Join(build.Default.GOPATH, "src"), pwd)
|
||||||
|
return filepath.ToSlash(importPath)
|
||||||
|
}
|
38
cmd/leanote_revel/version.go
Executable file
38
cmd/leanote_revel/version.go
Executable 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