Compare commits

..

72 Commits

Author SHA1 Message Date
Steve
b606ec999c Merge pull request #138 from notzippy/develop
Re added the requirement for the -a, without this the flags would not…
2018-09-22 15:32:30 -07:00
NotZippy
01ccd695d4 Re added the requirement for the -a, without this the flags would not error out and cause issues 2018-09-22 15:28:22 -07:00
Steve
cfe5bf4b0c Merge pull request #137 from notzippy/develop
Added a version file to revel/cmd
2018-09-22 13:38:17 -07:00
NotZippy
7a4e741d1c Added a version file to revel/cmd
Updated import path detection to make it smarter. You can now use absolute paths etc..
2018-09-22 13:37:27 -07:00
Steve
92943b2121 Merge pull request #136 from notzippy/develop
New Enhancement
2018-09-21 10:19:49 -07:00
NotZippy
69e59efb14 New Enhancement
Added ability to create a new revel applicaiton without any sources.
Automatically download all sources required
2018-09-21 10:08:37 -07:00
Steve
5973b438c1 Merge pull request #135 from notzippy/develop
Added message for debugging
2018-09-19 14:44:05 -07:00
NotZippy
c47f44762a Added message for debugging
Added process state message to be returned to wait channel to help resolve the reason for the "app died" message
2018-09-19 14:43:15 -07:00
Steve
b138e35f6d Merge pull request #134 from notzippy/develop
Split main file
2018-09-19 10:38:27 -07:00
NotZippy
17459d14e6 Split main file
Added code to split the generated main file into two separate files. This allows other code to launch the web application inline.
2018-09-19 09:47:51 -07:00
Steve
c87d53eafa Merge pull request #133 from notzippy/develop
Updated readme, Updated travis
2018-09-16 20:05:19 -07:00
NotZippy
4d7a290247 Updated readme, Updated travis 2018-09-16 20:04:40 -07:00
Steve
7eff69f3cb Merge pull request #130 from notzippy/develop
Added CI
2018-09-16 07:11:28 -07:00
NotZippy
34bc650ea8 Added CI 2018-09-16 06:54:57 -07:00
Steve
2c53671706 Merge pull request #129 from notzippy/develop
Command line update
2018-09-15 16:28:32 -07:00
NotZippy
3ad381d45b Enhanced package and build to by default not include any source code 2018-09-14 21:32:20 -07:00
NotZippy
d0baaeb9e9 Initial rewrite of revel/cmd to provide vendor support and enhanced CLI options 2018-09-14 21:26:25 -07:00
notzippy
d2ac018544 Merge pull request #120 from lokhman/issue119
Fix DefaultValidationKeys generated with wrong line for multiline check
2018-04-15 21:14:55 -07:00
notzippy
d0e5c797cb Merge pull request #122 from notzippy/develop
Added missed GPL license to command
2018-04-15 21:13:23 -07:00
NotZippy
7e501b8a65 Added missed GPL license to command 2018-04-15 21:09:59 -07:00
Alexander Lokhman
fe56bdd8a3 Fix DefaultValidationKeys generated with wrong line for multiline check 2018-02-12 15:37:35 +00:00
notzippy
97ec142262 Merge pull request #117 from tike/master
fix import path trimming during main.go generation
2018-02-04 16:32:57 -08:00
tike
dfc873bc15 fix import path trimming during main.go generation
The importPathFromPath function invoked during `revel build`
in callchain Build -> ProcessSource ->  importPathFromPath
assumes that the vendor folder is in the app's root directory
when trimming import paths for inclusion into autogenerated
templates.

Consequently vendor detection fails if the vendor folder
is located at another hiher layer in the directory tree
and /prefix/path/to/vendor/ is not stripped from the
import path, leading to inclusion of invalid importpaths,
resulting in compilation error and build abortion.

This fix makes the vendor folder detection more flexible,
allowing for the vendor folder to be present at any higher
level in the directory hirachy.
2018-02-01 15:25:13 +01:00
notzippy
cca02dd5ff Merge pull request #116 from notzippy/log-update
Added check to ignore functions which have no body (external functions)
2018-01-30 09:47:56 -08:00
NotZippy
91f43bf94c Added check to ignore functions which have no body (external functions)
Added missing sort package
2018-01-30 09:23:21 -08:00
notzippy
0583fe7d32 Merge pull request #108 from rokeller/develop
Generate same value of AppVersion regardless of where revel is run
2018-01-29 21:28:08 -08:00
notzippy
6ca1d73b61 Merge pull request #112 from nathantchan/stable_controllers
Sort controllers so that builds are reproducible.
2018-01-29 21:27:18 -08:00
notzippy
4c87861642 Merge pull request #114 from vin01/master
Adding referrer policy security header
2018-01-29 21:26:33 -08:00
notzippy
a2d7517ca0 Merge pull request #115 from runner-mei/master
add support to map as a argument in the controller action
2018-01-29 21:25:49 -08:00
meifakun
8efaff19ce map as a argument in the controller action 2018-01-15 16:07:46 +08:00
vin01
ac056d17af Adding referrer policy security header
It will set a default strict `Referrer-Policy ``strict-origin-when-cross-origin`` that controls what referrer information shall be included with requests.
More: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy, https://scotthelme.co.uk/a-new-security-header-referrer-policy/
It can prevent issues like: https://robots.thoughtbot.com/is-your-site-leaking-password-reset-links
2018-01-06 14:05:20 +05:30
Nathan Chan
fc904827cd Make sorting compatible with go >= 1.6 2017-12-11 09:55:26 -08:00
Nathan Chan
c240b05369 Sort controllers so that builds are reproducible.
Ordering of controllers in routes.go and main.go is unstable in
successive runs of revel build.  This change will assure that the
ordering is stable.
2017-12-08 11:37:28 -08:00
notzippy
29e594435c Merge pull request #104 from notzippy/log-update
Prerelease items
2017-10-31 08:12:16 -07:00
Roger Keller
a2acbe32bf Make sure AppVersion is set without the -dirty suffix for non-dirty work trees regardless of where revel is run from. 2017-10-28 21:20:28 +02:00
NotZippy
29c6237caf Removed the catch all route, and added comment about security issue 2017-10-22 09:31:18 -07:00
NotZippy
2d4ccf289c Set line number to use left parenthesis not right 2017-10-07 21:41:23 -07:00
notzippy
f38fb6a15d Merge pull request #103 from notzippy/log-update
Updated skeleton to added critical.
2017-10-07 21:41:05 -07:00
NotZippy
637ccbd250 Updated skeleton to added critical.
modified db.import to support multiple packages
2017-10-07 21:39:13 -07:00
notzippy
2da4734499 Merge pull request #101 from notzippy/log-update
Vendor changes
2017-09-25 09:10:40 -07:00
NotZippy
aa9e0f8600 Added code to make vendoring work 2017-09-25 07:36:48 -07:00
notzippy
db4054233b Merge pull request #99 from Acidic9/master
Remove abort with 'revel new' on empty directory
2017-09-24 13:17:32 -07:00
Ari Seyhun
3907c6575e Clean code 2017-09-23 14:20:09 +09:30
notzippy
27e9fab270 Merge pull request #100 from notzippy/log-update
Logging error added more compile warnings
2017-09-21 10:50:28 -07:00
NotZippy
17e7d40d31 Fixed missing debug context parameter name
Added check to see if specfication was not exported
Added warnings if expected types did not match specification
2017-09-20 17:49:34 -07:00
Ari Seyhun
54ce8d3699 Remove abort with 'revel new' on empty directory
If you use 'revel new ...' on an empty directory, revel will abort complaining the directory exists.

With this commit, it will no longer abort if the directory is empty.
2017-09-16 15:24:37 +09:30
notzippy
8ab98db556 Merge pull request #98 from notzippy/log-update
Updated command to use new logging
2017-09-14 17:16:59 -07:00
NotZippy
baf5e9f848 Added check to see if parameter was a local object, if so parse it 2017-09-14 17:15:22 -07:00
NotZippy
9d57681ae6 Updated command to use new logging 2017-09-02 09:10:21 -07:00
notzippy
3f136726db Merge pull request #97 from notzippy/listener-fix
Changed listener to be a pointer receiver
2017-08-25 15:55:14 -07:00
NotZippy
c0a515facf Changed listener to be a pointer receiver so setting the channel to nil actually persists 2017-08-24 21:48:49 -07:00
notzippy
01494f75fb Merge pull request #96 from notzippy/autorun
Added mutex lock on Refresh, removed check for app existence
2017-08-07 20:55:21 -07:00
NotZippy
e6b34786bb Added mutex lock on Refresh, removed check for app existence 2017-08-07 16:56:19 -07:00
notzippy
79b2afb5e5 Merge pull request #95 from notzippy/autorun
Made develop mode autorun on start
2017-08-03 20:00:26 -07:00
NotZippy
5fcde12193 Moved watcher inside harness
Modified proxy so application is launched on startup
2017-07-28 13:27:28 -07:00
notzippy
ad68773b9e Merge pull request #91 from notzippy/server-engine-2
Server Engine 2
2017-07-24 12:49:34 -07:00
NotZippy
e5255cd373 Updated as requested 2017-07-06 15:31:43 -07:00
NotZippy
3cf6d5094e Changed skeleton back to original 2017-06-07 09:45:09 -07:00
NotZippy
efcd02de37 Modified harness to bootstrap using the go engine. Skeleton app updated to use new request code 2017-06-07 09:45:09 -07:00
notzippy
7eda33eb71 Merge pull request #93 from notzippy/cmd-fix
Fixed captialization
2017-05-31 20:25:56 -07:00
NotZippy
1c5fb4a6f8 Fixed captialization 2017-05-31 20:25:04 -07:00
Brenden Soares
a699dab33d Merge pull request #61 from krhubert/develop
Use config.http.addr and config.http.ssl for create baseURL local server
2017-05-30 21:46:51 -07:00
NotZippy
e1776bda3c Rollback a change that was committed by mistake to develop branch 2017-04-26 21:04:12 -07:00
notzippy
d68b27ae81 Merge pull request #86 from tw4452852/version
fix version check against devel
2017-04-21 08:55:01 -07:00
Brenden Soares
ce84b78204 Merge pull request #85 from revel/app.conf-cleanup-1
Adding consistent values and example formatting
2017-04-15 18:57:56 -07:00
Tw
19ca52182d fix version check against devel
Signed-off-by: Tw <tw19881113@gmail.com>
2017-04-10 22:31:37 +08:00
Brenden Soares
bf30aab381 Adding consistent values and example formatting 2017-04-07 12:05:14 -07:00
notzippy
bd4663b651 Merge pull request #84 from revel/app.conf-cleanup-1
App.conf cleanup 1
2017-04-07 12:05:08 -07:00
Brenden Soares
b81860de5f Remove unneeded quotes 2017-04-07 11:34:44 -07:00
NotZippy
d2b1730439 Makes it so harness can bootstrap using the new GoRequest / response wrappers 2017-04-04 17:17:23 -07:00
krhubert
0381636044 Typo in httpProto 2016-08-17 10:20:13 +02:00
krhubert
fb3980ce9d Use config.http.addr and config.http.ssl for create baseURL test server 2016-08-11 11:41:48 +02:00
47 changed files with 5027 additions and 1267 deletions

11
.codebeatsettings Normal file
View File

@@ -0,0 +1,11 @@
{
"GOLANG": {
"ABC":[15, 25, 50, 70],
"BLOCK_NESTING":[5, 6, 7, 8],
"CYCLO":[20, 30, 45, 60],
"TOO_MANY_IVARS": [15, 18, 20, 25],
"TOO_MANY_FUNCTIONS": [20, 30, 40, 50],
"TOTAL_COMPLEXITY": [150, 250, 400, 500],
"LOC": [50, 75, 90, 120]
}
}

85
.travis.yml Normal file
View File

@@ -0,0 +1,85 @@
language: go
go:
- "1.8"
- "1.9"
- "1.10"
- "1.11"
- "tip"
os:
- linux
- osx
- windows
sudo: false
branches:
only:
- master
- develop
services:
- memcache
- redis-server
before_install:
# TRAVIS_OS_NAME - linux and osx
- echo $TRAVIS_OS_NAME
- |
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
brew update && brew install memcached redis && brew services start redis && brew services start memcached
fi
- redis-server --daemonize yes
- redis-cli info
install:
# Setting environments variables
- export PATH=$PATH:$HOME/gopath/bin
- export REVEL_BRANCH="develop"
- 'if [[ "$TRAVIS_BRANCH" == "master" ]]; then export REVEL_BRANCH="master"; fi'
- 'echo "Travis branch: $TRAVIS_BRANCH, Revel dependency branch: $REVEL_BRANCH"'
- git clone -b $REVEL_BRANCH git://github.com/revel/modules ../modules/
- git clone -b $REVEL_BRANCH git://github.com/revel/revel ../revel/
- git clone -b $REVEL_BRANCH git://github.com/revel/config ../config/
- git clone -b $REVEL_BRANCH git://github.com/revel/cron ../cron/
- git clone -b $REVEL_BRANCH git://github.com/revel/examples ../examples/
- go get -v github.com/revel/revel/...
- go get -v github.com/revel/cmd/revel
- go get -u github.com/golang/dep/cmd/dep
script:
- go test -v github.com/revel/cmd/...
# Ensure the new-app flow works (plus the other commands).
- revel version
- revel new my/testapp
- revel test my/testapp
- revel clean my/testapp
- revel build my/testapp build/testapp
- revel build my/testapp build/testapp prod
- revel package my/testapp
- revel package my/testapp prod
# Ensure the new-app flow works (plus the other commands).
- revel new -a my/testapp2
- revel test -a my/testapp2
- revel clean -a my/testapp2
- revel build -a my/testapp2 -t build/testapp
- revel build -a my/testapp2 -t build/testapp -m prod
- revel package -a my/testapp2
- revel package -a my/testapp2 -m prod
- revel new -a my/testapp3 -V
- revel test -a my/testapp3
- revel clean -a my/testapp3
- revel build -a my/testapp3 -t build/testapp
- revel build -a my/testapp3 -t build/testapp -m prod
- revel package -a my/testapp3
- revel package -a my/testapp3 -m prod
matrix:
allow_failures:
- go: tip
- go: 1.6
os: osx

20
LICENSE Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (C) 2012-2018 The Revel Framework Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,5 +1,9 @@
# Revel command line tools
[![Build Status](https://secure.travis-ci.org/revel/cmd.svg?branch=master)](http://travis-ci.org/revel/cmd)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Go Report Card](https://goreportcard.com/badge/github.com/revel/cmd)](https://goreportcard.com/report/github.com/revel/cmd)
Provides the `revel` command, used to create and run Revel apps.
- More info at http://revel.github.io/manual/tool.html
@@ -7,5 +11,13 @@ Provides the `revel` command, used to create and run Revel apps.
Install
------------
```bash
go get github.com/revel/cmd/revel
go get -u github.com/revel/cmd/revel
```
New Application
-------------
Create a new application
```commandline
revel new my/app
```

View File

@@ -13,7 +13,9 @@ import (
"os/exec"
"time"
"github.com/revel/revel"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"log"
)
// App contains the configuration for running a Revel app. (Not for the app itself)
@@ -22,16 +24,17 @@ 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.
Paths *model.RevelContainer
}
// NewApp returns app instance with binary path in it
func NewApp(binPath string) *App {
return &App{BinaryPath: binPath}
func NewApp(binPath string, paths *model.RevelContainer) *App {
return &App{BinaryPath: binPath, Paths: paths, Port: paths.HTTPPort}
}
// 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)
func (a *App) Cmd(runMode string) AppCmd {
a.cmd = NewAppCmd(a.BinaryPath, a.Port, runMode, a.Paths)
return a.cmd
}
@@ -47,29 +50,30 @@ type AppCmd struct {
}
// NewAppCmd returns the AppCmd with parameters initialized for running app
func NewAppCmd(binPath string, port int) AppCmd {
func NewAppCmd(binPath string, port int, runMode string, paths *model.RevelContainer) AppCmd {
cmd := exec.Command(binPath,
fmt.Sprintf("-port=%d", port),
fmt.Sprintf("-importPath=%s", revel.ImportPath),
fmt.Sprintf("-runMode=%s", revel.RunMode))
fmt.Sprintf("-importPath=%s", paths.ImportPath),
fmt.Sprintf("-runMode=%s", 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)}
func (cmd AppCmd) Start(c *model.CommandConfig) error {
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool),c}
cmd.Stdout = listeningWriter
revel.TRACE.Println("Exec app:", cmd.Path, cmd.Args)
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args)
if err := cmd.Cmd.Start(); err != nil {
revel.ERROR.Fatalln("Error running:", err)
utils.Logger.Fatal("Error running:", "error", err)
}
select {
case <-cmd.waitChan():
return errors.New("revel/harness: app died")
case exitState := <-cmd.waitChan():
return errors.New("revel/harness: app died reason: " + exitState)
case <-time.After(30 * time.Second):
case <-time.After(60 * time.Second):
utils.Logger.Error("Killing revel server process did not respond after wait timeout.", "processid", cmd.Process.Pid)
cmd.Kill()
return errors.New("revel/harness: app timed out")
@@ -83,45 +87,58 @@ func (cmd AppCmd) Start() error {
// Run the app server inline. Never returns.
func (cmd AppCmd) Run() {
revel.TRACE.Println("Exec app:", cmd.Path, cmd.Args)
log.Println("Exec app:", "path", cmd.Path, "args", cmd.Args)
if err := cmd.Cmd.Run(); err != nil {
revel.ERROR.Fatalln("Error running:", err)
utils.Logger.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.TRACE.Println("Killing revel server pid", cmd.Process.Pid)
utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid)
err := cmd.Process.Kill()
if err != nil {
revel.ERROR.Fatalln("Failed to kill revel server:", err)
utils.Logger.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)
func (cmd AppCmd) waitChan() <-chan string {
ch := make(chan string, 1)
go func() {
_ = cmd.Wait()
ch <- struct{}{}
state := cmd.ProcessState
exitStatus := " unknown "
if state!=nil {
exitStatus = state.String()
}
ch <- exitStatus
}()
return ch
}
// A io.Writer that copies to the destination, and listens for "Listening on.."
// A io.Writer that copies to the destination, and listens for "Revel engine is listening on.."
// in the stream. (Which tells us when the revel server has finished starting up)
// This is super ghetto, but by far the simplest thing that should work.
type startupListeningWriter struct {
dest io.Writer
notifyReady chan bool
c *model.CommandConfig
}
func (w startupListeningWriter) Write(p []byte) (n int, err error) {
if w.notifyReady != nil && bytes.Contains(p, []byte("Listening")) {
func (w *startupListeningWriter) Write(p []byte) (int, error) {
if w.notifyReady != nil && bytes.Contains(p, []byte("Revel engine is listening on")) {
w.notifyReady <- true
w.notifyReady = nil
}
if w.c.HistoricMode {
if w.notifyReady != nil && bytes.Contains(p, []byte("Listening on")) {
w.notifyReady <- true
w.notifyReady = nil
}
}
return w.dest.Write(p)
}

372
harness/build.go Executable file → Normal file
View File

@@ -13,62 +13,116 @@ import (
"path/filepath"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
"text/template"
"time"
"github.com/revel/revel"
"github.com/revel/cmd/model"
"github.com/revel/cmd/parser"
"github.com/revel/cmd/utils"
)
var importErrorPattern = regexp.MustCompile("cannot find package \"([^\"]+)\"")
type ByString []*model.TypeInfo
func (c ByString) Len() int { return len(c) }
func (c ByString) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c ByString) Less(i, j int) bool { return c[i].String() < c[j].String() }
// 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) {
func Build(c *model.CommandConfig, paths *model.RevelContainer) (app *App, compileError *utils.Error) {
// First, clear the generated files (to avoid them messing with ProcessSource).
cleanSource("tmp", "routes")
cleanSource(paths, "tmp", "routes")
sourceInfo, compileError := ProcessSource(revel.CodePaths)
sourceInfo, compileError := parser.ProcessSource(paths)
if compileError != nil {
return nil, compileError
return
}
// Add the db.import to the import paths.
if dbImportPath, found := revel.Config.String("db.import"); found {
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, dbImportPath)
if dbImportPath, found := paths.Config.String("db.import"); found {
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, strings.Split(dbImportPath, ",")...)
}
// Sort controllers so that file generation is reproducible
controllers := sourceInfo.ControllerSpecs()
sort.Stable(ByString(controllers))
// Generate two source files.
templateArgs := map[string]interface{}{
"Controllers": sourceInfo.ControllerSpecs(),
"ImportPath": paths.ImportPath,
"Controllers": controllers,
"ValidationKeys": sourceInfo.ValidationKeys,
"ImportPaths": calcImportAliases(sourceInfo),
"TestSuites": sourceInfo.TestSuites(),
}
genSource("tmp", "main.go", RevelMainTemplate, templateArgs)
genSource("routes", "routes.go", RevelRoutesTemplate, templateArgs)
// Generate code for the main, run and routes file.
// The run file allows external programs to launch and run the application
// without being the main thread
cleanSource(paths, "tmp", "routes")
genSource(paths, "tmp", "main.go", RevelMainTemplate, templateArgs)
genSource(paths, filepath.Join("tmp", "run"), "run.go", RevelRunTemplate, templateArgs)
genSource(paths, "routes", "routes.go", RevelRoutesTemplate, templateArgs)
// Read build config.
buildTags := revel.Config.StringDefault("build.tags", "")
buildTags := paths.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.ERROR.Fatalf("Go executable not found in PATH.")
utils.Logger.Fatal("Go executable not found in PATH.")
}
pkg, err := build.Default.Import(revel.ImportPath, "", build.FindOnly)
// Detect if deps tool should be used (is there a vendor folder ?)
useVendor := utils.DirExists(filepath.Join(paths.BasePath, "vendor"))
basePath := paths.BasePath
for !useVendor {
basePath = filepath.Dir(basePath)
found := false
// Check to see if we are still in the GOPATH
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
if strings.HasPrefix(basePath, gopath) {
found = true
break
}
}
if !found {
break
} else {
useVendor = utils.DirExists(filepath.Join(basePath, "vendor"))
}
}
var depPath string
if useVendor {
utils.Logger.Info("Vendor folder detected, scanning for deps in path")
depPath, err = exec.LookPath("dep")
if err != nil {
// Do not halt build unless a new package needs to be imported
utils.Logger.Warn("Build: `dep` executable not found in PATH, but vendor folder detected." +
"Packages can only be added automatically to the vendor folder using the `dep` tool. " +
"You can install the `dep` tool by doing a `go get -u github.com/golang/dep/cmd/dep`")
}
} else {
utils.Logger.Info("No vendor folder detected, not using dependency manager to import files")
}
pkg, err := build.Default.Import(paths.ImportPath, "", build.FindOnly)
if err != nil {
revel.ERROR.Fatalln("Failure importing", revel.ImportPath)
utils.Logger.Fatal("Failure importing", "path", paths.ImportPath)
}
// Binary path is a combination of $GOBIN/revel.d directory, app's import path and its name.
binName := filepath.Join(pkg.BinDir, "revel.d", revel.ImportPath, filepath.Base(revel.BasePath))
binName := filepath.Join(pkg.BinDir, "revel.d", paths.ImportPath, filepath.Base(paths.BasePath))
// Change binary path for Windows build
goos := runtime.GOOS
@@ -80,70 +134,126 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
}
gotten := make(map[string]struct{})
contains := func(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
for {
appVersion := getAppVersion()
appVersion := getAppVersion(paths)
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)
paths.ImportPath, appVersion, paths.ImportPath, buildTime)
// TODO remove version check for versionLinkerFlags after Revel becomes Go min version to go1.5
goVersion, _ := strconv.ParseFloat(runtime.Version()[2:5], 64)
if goVersion < 1.5 {
versionLinkerFlags = fmt.Sprintf("-X %s/app.AppVersion \"%s\" -X %s/app.BuildTime \"%s\"",
revel.ImportPath, appVersion, revel.ImportPath, buildTime)
// Append any build flags specified, they will override existing flags
flags := []string{}
if len(c.BuildFlags) == 0 {
flags = []string{
"build",
"-i",
"-ldflags", versionLinkerFlags,
"-tags", buildTags,
"-o", binName}
} else {
if !contains(c.BuildFlags, "build") {
flags = []string{"build"}
}
flags = append(flags, c.BuildFlags...)
if !contains(flags, "-ldflags") {
flags = append(flags, "-ldflags", versionLinkerFlags)
}
if !contains(flags, "-tags") {
flags = append(flags, "-tags", buildTags)
}
if !contains(flags, "-o") {
flags = append(flags, "-o", binName)
}
}
flags := []string{
"build",
"-i",
"-ldflags", versionLinkerFlags,
"-tags", buildTags,
"-o", binName}
// Add in build flags
flags = append(flags, buildFlags...)
flags = append(flags, c.BuildFlags...)
// This is Go main path
gopath := c.GoPath
for _, o := range paths.ModulePathMap {
gopath += string(filepath.ListSeparator) + o
}
// Note: It's not applicable for filepath.* usage
flags = append(flags, path.Join(revel.ImportPath, "app", "tmp"))
flags = append(flags, path.Join(paths.ImportPath, "app", "tmp"))
buildCmd := exec.Command(goPath, flags...)
revel.TRACE.Println("Exec:", buildCmd.Args)
buildCmd.Env = append(os.Environ(),
"GOPATH="+gopath,
)
utils.Logger.Info("Exec:", "args", buildCmd.Args)
output, err := buildCmd.CombinedOutput()
// If the build succeeded, we're done.
if err == nil {
return NewApp(binName), nil
utils.Logger.Info("Build successful continuing")
return NewApp(binName, paths), nil
}
revel.ERROR.Println(string(output))
// Since there was an error, capture the output in case we need to report it
stOutput := string(output)
// See if it was an import error that we can go get.
matches := importErrorPattern.FindStringSubmatch(string(output))
matches := importErrorPattern.FindAllStringSubmatch(stOutput, -1)
utils.Logger.Info("Build failed checking for missing imports", "message", stOutput, "missing_imports", len(matches))
if matches == nil {
return nil, newCompileError(output)
utils.Logger.Info("Build failed no missing imports", "message", stOutput)
return nil, newCompileError(paths, output)
}
utils.Logger.Warn("Detected missing packages, importing them", "packages", len(matches))
for _, match := range matches {
// Ensure we haven't already tried to go get it.
pkgName := match[1]
utils.Logger.Info("Trying to import ", "package", pkgName)
if _, alreadyTried := gotten[pkgName]; alreadyTried {
utils.Logger.Error("Failed to import ", "package", pkgName)
return nil, newCompileError(paths, output)
}
gotten[pkgName] = struct{}{}
// Ensure we haven't already tried to go get it.
pkgName := matches[1]
if _, alreadyTried := gotten[pkgName]; alreadyTried {
return nil, newCompileError(output)
}
gotten[pkgName] = struct{}{}
// Execute "go get <pkg>"
// Or dep `dep ensure -add <pkg>` if it is there
var getCmd *exec.Cmd
if useVendor {
if depPath == "" {
utils.Logger.Warn("Build: Vendor folder found, but the `dep` tool was not found, " +
"if you use a different vendoring (package management) tool please add the following packages by hand, " +
"or install the `dep` tool into your gopath by doing a `go get -u github.com/golang/dep/cmd/dep`. " +
"For more information and usage of the tool please see http://github.com/golang/dep")
for _, pkg := range matches {
utils.Logger.Warn("Missing package", "package", pkg[1])
}
}
getCmd = exec.Command(depPath, "ensure", "-add", pkgName)
getCmd.Dir = paths.AppPath
// Execute "go get <pkg>"
getCmd := exec.Command(goPath, "get", pkgName)
revel.TRACE.Println("Exec:", getCmd.Args)
getOutput, err := getCmd.CombinedOutput()
if err != nil {
revel.ERROR.Println(string(getOutput))
return nil, newCompileError(output)
} else {
getCmd = exec.Command(goPath, "get", pkgName)
}
utils.Logger.Info("Exec:", "args", getCmd.Args)
getOutput, err := getCmd.CombinedOutput()
if err != nil {
utils.Logger.Error("Build failed", "message", stOutput, "error", err)
utils.Logger.Error("Failed to fetch the output", "getOutput", string(getOutput))
return nil, newCompileError(paths, output)
}
}
// Success getting the import, attempt to build again.
}
// TODO remove this unreachable code and document it
revel.ERROR.Fatalf("Not reachable")
utils.Logger.Fatal("Not reachable")
return nil, nil
}
@@ -153,7 +263,7 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
// 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 {
func getAppVersion(paths *model.RevelContainer) string {
if version := os.Getenv("APP_VERSION"); version != "" {
return version
}
@@ -161,17 +271,17 @@ func getAppVersion() string {
// Check for the git binary
if gitPath, err := exec.LookPath("git"); err == nil {
// Check for the .git directory
gitDir := filepath.Join(revel.BasePath, ".git")
gitDir := filepath.Join(paths.BasePath, ".git")
info, err := os.Stat(gitDir)
if (err != nil && os.IsNotExist(err)) || !info.IsDir() {
return ""
}
gitCmd := exec.Command(gitPath, "--git-dir="+gitDir, "describe", "--always", "--dirty")
revel.TRACE.Println("Exec:", gitCmd.Args)
gitCmd := exec.Command(gitPath, "--git-dir="+gitDir, "--work-tree="+paths.BasePath, "describe", "--always", "--dirty")
utils.Logger.Info("Exec:", "args", gitCmd.Args)
output, err := gitCmd.Output()
if err != nil {
revel.WARN.Println("Cannot determine git repository version:", err)
utils.Logger.Error("Cannot determine git repository version:", "error", err)
return ""
}
@@ -181,19 +291,19 @@ func getAppVersion() string {
return ""
}
func cleanSource(dirs ...string) {
func cleanSource(paths *model.RevelContainer, dirs ...string) {
for _, dir := range dirs {
cleanDir(dir)
cleanDir(paths, dir)
}
}
func cleanDir(dir string) {
revel.INFO.Println("Cleaning dir " + dir)
tmpPath := filepath.Join(revel.AppPath, dir)
func cleanDir(paths *model.RevelContainer, dir string) {
utils.Logger.Info("Cleaning dir ", "dir", dir)
tmpPath := filepath.Join(paths.AppPath, dir)
f, err := os.Open(tmpPath)
if err != nil {
if !os.IsNotExist(err) {
revel.ERROR.Println("Failed to clean dir:", err)
utils.Logger.Error("Failed to clean dir:", "error", err)
}
} else {
defer func() {
@@ -203,20 +313,20 @@ func cleanDir(dir string) {
infos, err := f.Readdir(0)
if err != nil {
if !os.IsNotExist(err) {
revel.ERROR.Println("Failed to clean dir:", err)
utils.Logger.Fatal("Failed to clean dir:", "error", err)
}
} else {
for _, info := range infos {
path := filepath.Join(tmpPath, info.Name())
pathName := filepath.Join(tmpPath, info.Name())
if info.IsDir() {
err := os.RemoveAll(path)
err := os.RemoveAll(pathName)
if err != nil {
revel.ERROR.Println("Failed to remove dir:", err)
utils.Logger.Fatal("Failed to remove dir:", "error", err)
}
} else {
err := os.Remove(path)
err := os.Remove(pathName)
if err != nil {
revel.ERROR.Println("Failed to remove file:", err)
utils.Logger.Fatal("Failed to remove file:", "error", err)
}
}
}
@@ -226,39 +336,20 @@ func cleanDir(dir string) {
// genSource renders the given template to produce source code, which it writes
// to the given directory and file.
func genSource(dir, filename, templateSource string, args map[string]interface{}) {
sourceCode := revel.ExecuteTemplate(
template.Must(template.New("").Parse(templateSource)),
args)
func genSource(paths *model.RevelContainer, dir, filename, templateSource string, args map[string]interface{}) {
// Create a fresh dir.
cleanSource(dir)
tmpPath := filepath.Join(revel.AppPath, dir)
err := os.Mkdir(tmpPath, 0777)
if err != nil && !os.IsExist(err) {
revel.ERROR.Fatalf("Failed to make '%v' directory: %v", dir, err)
}
// Create the file
file, err := os.Create(filepath.Join(tmpPath, filename))
err := utils.MustGenerateTemplate(filepath.Join(paths.AppPath, dir, filename), templateSource, args)
if err != nil {
revel.ERROR.Fatalf("Failed to create file: %v", err)
}
defer func() {
_ = file.Close()
}()
if _, err = file.WriteString(sourceCode); err != nil {
revel.ERROR.Fatalf("Failed to write to file: %v", err)
utils.Logger.Fatal("Failed to generate template for source file", "error", 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 {
func calcImportAliases(src *model.SourceInfo) map[string]string {
aliases := make(map[string]string)
typeArrays := [][]*TypeInfo{src.ControllerSpecs(), src.TestSuites()}
typeArrays := [][]*model.TypeInfo{src.ControllerSpecs(), src.TestSuites()}
for _, specs := range typeArrays {
for _, spec := range specs {
addAlias(aliases, spec.ImportPath, spec.PackageName)
@@ -285,6 +376,7 @@ func calcImportAliases(src *SourceInfo) map[string]string {
return aliases
}
// Adds an alias to the map of alias names
func addAlias(aliases map[string]string, importPath, pkgName string) {
alias, ok := aliases[importPath]
if ok {
@@ -294,16 +386,18 @@ func addAlias(aliases map[string]string, importPath, pkgName string) {
aliases[importPath] = alias
}
// Generates a package alias
func makePackageAlias(aliases map[string]string, pkgName string) string {
i := 0
alias := pkgName
for containsValue(aliases, alias) {
for containsValue(aliases, alias) || alias == "revel" {
alias = fmt.Sprintf("%s%d", pkgName, i)
i++
}
return alias
}
// Returns true if this value is in the map
func containsValue(m map[string]string, val string) bool {
for _, v := range m {
if v == val {
@@ -315,15 +409,15 @@ func containsValue(m map[string]string, val string) bool {
// Parse the output of the "go build" command.
// Return a detailed Error.
func newCompileError(output []byte) *revel.Error {
func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error {
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
FindSubmatch(output)
if errorMatch == nil {
errorMatch = regexp.MustCompile(`(?m)^(.*?)\:(\d+)\:\s(.*?)$`).FindSubmatch(output)
errorMatch = regexp.MustCompile(`(?m)^(.*?):(\d+):\s(.*?)$`).FindSubmatch(output)
if errorMatch == nil {
revel.ERROR.Println("Failed to parse build errors:\n", string(output))
return &revel.Error{
utils.Logger.Error("Failed to parse build errors", "error", string(output))
return &utils.Error{
SourceType: "Go code",
Title: "Go Compilation Error",
Description: "See console for build error.",
@@ -332,16 +426,30 @@ func newCompileError(output []byte) *revel.Error {
errorMatch = append(errorMatch, errorMatch[3])
revel.ERROR.Println("Build errors:\n", string(output))
utils.Logger.Error("Build errors", "errors", string(output))
}
findInPaths := func(relFilename string) string {
// Extract the paths from the gopaths, and search for file there first
gopaths := filepath.SplitList(build.Default.GOPATH)
for _, gp := range gopaths {
newPath := filepath.Join(gp, relFilename)
if utils.Exists(newPath) {
return newPath
}
}
newPath, _ := filepath.Abs(relFilename)
utils.Logger.Warn("Could not find in GO path", "file", relFilename)
return newPath
}
// Read the source for the offending file.
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{
relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go"
absFilename = findInPaths(relFilename)
line, _ = strconv.Atoi(string(errorMatch[2]))
description = string(errorMatch[4])
compileError = &utils.Error{
SourceType: "Go code",
Title: "Go Compilation Error",
Path: relFilename,
@@ -350,16 +458,16 @@ func newCompileError(output []byte) *revel.Error {
}
)
errorLink := revel.Config.StringDefault("error.link", "")
errorLink := paths.Config.StringDefault("error.link", "")
if errorLink != "" {
compileError.SetLink(errorLink)
}
fileStr, err := revel.ReadLines(absFilename)
fileStr, err := utils.ReadLines(absFilename)
if err != nil {
compileError.MetaError = absFilename + ": " + err.Error()
revel.ERROR.Println(compileError.MetaError)
utils.Logger.Error("Unable to readlines "+compileError.MetaError, "error", err)
return compileError
}
@@ -368,11 +476,13 @@ func newCompileError(output []byte) *revel.Error {
}
// RevelMainTemplate template for app/tmp/main.go
const RevelMainTemplate = `// GENERATED CODE - DO NOT EDIT
package main
const RevelRunTemplate = `// GENERATED CODE - DO NOT EDIT
// This file is the run file for Revel.
// It registers all the controllers and provides details for the Revel server engine to
// properly inject parameters directly into the action endpoints.
package run
import (
"flag"
"reflect"
"github.com/revel/revel"{{range $k, $v := $.ImportPaths}}
{{$v}} "{{$k}}"{{end}}
@@ -380,19 +490,19 @@ import (
)
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.INFO.Println("Running revel server")
// Register and run the application
func Run(port int) {
Register()
revel.Run(port)
}
// Register all the controllers
func Register() {
revel.AppLog.Info("Running revel server")
{{range $i, $c := .Controllers}}
revel.RegisterController((*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),
[]*revel.MethodType{
@@ -418,13 +528,39 @@ func main() {
testing.TestSuites = []interface{}{ {{range .TestSuites}}
(*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),{{end}}
}
}
`
const RevelMainTemplate = `// GENERATED CODE - DO NOT EDIT
// This file is the main file for Revel.
// It registers all the controllers and provides details for the Revel server engine to
// properly inject parameters directly into the action endpoints.
package main
revel.Run(*port)
import (
"flag"
"{{.ImportPath}}/app/tmp/run"
"github.com/revel/revel"
)
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.")
)
func main() {
flag.Parse()
revel.Init(*runMode, *importPath, *srcPath)
run.Run(*port)
}
`
// RevelRoutesTemplate template for app/conf/routes
const RevelRoutesTemplate = `// GENERATED CODE - DO NOT EDIT
// This file provides a way of creating URL's based on all the actions
// found in all the controllers.
package routes
import "github.com/revel/revel"

View File

@@ -28,11 +28,15 @@ import (
"strings"
"sync/atomic"
"github.com/revel/revel"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"github.com/revel/cmd/watcher"
"sync"
"html/template"
"io/ioutil"
)
var (
watcher *revel.Watcher
doNotWatch = []string{"tmp", "views", "routes"}
lastRequestHadError int32
@@ -41,16 +45,79 @@ var (
// 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
app *App // The application
useProxy bool // True if proxy is in use
serverHost string // The proxy server host
port int // The proxy serber port
proxy *httputil.ReverseProxy // The proxy
watcher *watcher.Watcher // The file watched
mutex *sync.Mutex // A mutex to prevent concurrent updates
paths *model.RevelContainer // The Revel container
config *model.CommandConfig // The configuration
runMode string // The runmode the harness is running in
}
func renderError(w http.ResponseWriter, r *http.Request, err error) {
req, resp := revel.NewRequest(r), revel.NewResponse(w)
c := revel.NewController(req, resp)
c.RenderError(err).Apply(req, resp)
func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err error) {
// Render error here
// Grab the template from three places
// 1) Application/views/errors
// 2) revel_home/views/errors
// 3) views/errors
if err==nil {
utils.Logger.Panic("Caller passed in a nil error")
}
templateSet := template.New("__root__")
seekViewOnPath:=func(view string) (path string) {
path = filepath.Join(h.paths.ViewsPath, "errors", view)
if !utils.Exists(path) {
path = filepath.Join(h.paths.RevelPath, "templates", "errors", view)
}
data,err := ioutil.ReadFile(path)
if err!=nil {
utils.Logger.Error("Unable to read template file", path)
}
_,err = templateSet.New("errors/"+view).Parse(string(data))
if err!=nil {
utils.Logger.Error("Unable to parse template file", path)
}
return
}
target := []string{seekViewOnPath("500.html"),seekViewOnPath("500-dev.html")}
if !utils.Exists(target[0]) {
fmt.Fprintf(iw, "Target template not found not found %s<br />\n", target[0])
fmt.Fprintf(iw, "An error ocurred %s", err.Error())
return
}
var revelError *utils.Error
switch e := err.(type) {
case *utils.Error:
revelError = e
case error:
revelError = &utils.Error{
Title: "Server Error",
Description: e.Error(),
}
}
if revelError == nil {
panic("no error provided")
}
viewArgs := map[string]interface{}{}
viewArgs["RunMode"] = h.paths.RunMode
viewArgs["DevMode"] = h.paths.DevMode
viewArgs["Error"] = revelError
// Render the template from the file
err = templateSet.ExecuteTemplate(iw,"errors/500.html",viewArgs)
if err!=nil {
utils.Logger.Error("Failed to execute","error",err)
}
fmt.Println("template ",templateSet.Templates()[0].Name(), templateSet.Templates()[1].Name())
//utils.MustRenderTemplateToStream(iw,target, viewArgs)
}
// ServeHTTP handles all requests.
@@ -63,18 +130,23 @@ func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Flush any change events and rebuild app if necessary.
// Render an error page if the rebuild / restart failed.
err := watcher.Notify()
err := h.watcher.Notify()
if err != nil {
// In a thread safe manner update the flag so that a request for
// /favicon.ico does not trigger a rebuild
atomic.CompareAndSwapInt32(&lastRequestHadError, 0, 1)
renderError(w, r, err)
h.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)
h.proxyWebsocket(w, r, h.serverHost)
} else {
h.proxy.ServeHTTP(w, r)
}
@@ -82,24 +154,26 @@ func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// NewHarness method returns a reverse proxy that forwards requests
// to the given port.
func NewHarness() *Harness {
func NewHarness(c *model.CommandConfig, paths *model.RevelContainer, runMode string, noProxy bool) *Harness {
// Get a template loader to render errors.
// 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.ERROR.Println(err)
}
//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)
addr := paths.HTTPAddr
port := paths.Config.IntDefault("harness.port", 0)
scheme := "http"
if revel.HTTPSsl {
if paths.HTTPSsl {
scheme = "https"
}
// If the server is running on the wildcard address, use "localhost"
if addr == "" {
utils.Logger.Warn("No http.addr specified in the app.conf listening on localhost interface only. " +
"This will not allow external access to your application")
addr = "localhost"
}
@@ -109,38 +183,59 @@ func NewHarness() *Harness {
serverURL, _ := url.ParseRequestURI(fmt.Sprintf(scheme+"://%s:%d", addr, port))
harness := &Harness{
serverHarness := &Harness{
port: port,
serverHost: serverURL.String()[len(scheme+"://"):],
proxy: httputil.NewSingleHostReverseProxy(serverURL),
mutex: &sync.Mutex{},
paths: paths,
useProxy: !noProxy,
config: c,
runMode: runMode,
}
if revel.HTTPSsl {
harness.proxy.Transport = &http.Transport{
if paths.HTTPSsl {
serverHarness.proxy.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
return harness
return serverHarness
}
// Refresh method rebuilds the Revel application and run it on the given port.
func (h *Harness) Refresh() (err *revel.Error) {
// called by the watcher
func (h *Harness) Refresh() (err *utils.Error) {
// Allow only one thread to rebuild the process
// If multiple requests to rebuild are queued only the last one is executed on
// So before a build is started we wait for a second to determine if
// more requests for a build are triggered.
// Once no more requests are triggered the build will be processed
h.mutex.Lock()
defer h.mutex.Unlock()
if h.app != nil {
h.app.Kill()
}
revel.TRACE.Println("Rebuild")
h.app, err = Build()
utils.Logger.Info("Rebuild Called")
h.app, err = Build(h.config, h.paths)
if err != nil {
utils.Logger.Error("Build detected an error", "error", err)
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(),
if h.useProxy {
h.app.Port = h.port
if err2 := h.app.Cmd(h.runMode).Start(h.config); err2 != nil {
utils.Logger.Error("Could not start application", "error", err2)
return &utils.Error{
Title: "App failed to start up",
Description: err2.Error(),
}
}
} else {
h.app = nil
}
return
@@ -149,11 +244,11 @@ func (h *Harness) Refresh() (err *revel.Error) {
// 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())
return !utils.ContainsString(doNotWatch, info.Name())
}
// WatchFile method returns true given filename HasSuffix of ".go"
// otheriwse false
// otheriwse false - implements revel.DiscerningListener
func (h *Harness) WatchFile(filename string) bool {
return strings.HasSuffix(filename, ".go")
}
@@ -162,33 +257,40 @@ func (h *Harness) WatchFile(filename string) bool {
// server, which it runs and rebuilds as necessary.
func (h *Harness) Run() {
var paths []string
if revel.Config.BoolDefault("watch.gopath", false) {
if h.paths.Config.BoolDefault("watch.gopath", false) {
gopaths := filepath.SplitList(build.Default.GOPATH)
paths = append(paths, gopaths...)
}
paths = append(paths, revel.CodePaths...)
watcher = revel.NewWatcher()
watcher.Listen(h, paths...)
paths = append(paths, h.paths.CodePaths...)
h.watcher = watcher.NewWatcher(h.paths, false)
h.watcher.Listen(h, paths...)
h.watcher.Notify()
go func() {
addr := fmt.Sprintf("%s:%d", revel.HTTPAddr, revel.HTTPPort)
revel.INFO.Printf("Listening on %s", addr)
if h.useProxy {
go func() {
// Check the port to start on a random port
if h.paths.HTTPPort==0 {
h.paths.HTTPPort = getFreePort()
}
addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort)
utils.Logger.Infof("Proxy server is 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.ERROR.Fatalln("Failed to start reverse proxy:", err)
}
}()
var err error
if h.paths.HTTPSsl {
err = http.ListenAndServeTLS(
addr,
h.paths.HTTPSslCert,
h.paths.HTTPSslKey,
h)
} else {
err = http.ListenAndServe(addr, h)
}
if err != nil {
utils.Logger.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)
@@ -203,25 +305,25 @@ func (h *Harness) Run() {
func getFreePort() (port int) {
conn, err := net.Listen("tcp", ":0")
if err != nil {
revel.ERROR.Fatal(err)
utils.Logger.Fatal("Unable to fetch a freee port address", "error", err)
}
port = conn.Addr().(*net.TCPAddr).Port
err = conn.Close()
if err != nil {
revel.ERROR.Fatal(err)
utils.Logger.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) {
func (h *Harness) proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
var (
d net.Conn
err error
)
if revel.HTTPSsl {
if h.paths.HTTPSsl {
// since this proxy isn't used in production,
// it's OK to set InsecureSkipVerify to true
// no need to add another configuration option.
@@ -231,7 +333,7 @@ func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
}
if err != nil {
http.Error(w, "Error contacting backend server.", 500)
revel.ERROR.Printf("Error dialing websocket backend %s: %v", host, err)
utils.Logger.Error("Error dialing websocket backend ", "host", host, "error", err)
return
}
hj, ok := w.(http.Hijacker)
@@ -241,21 +343,21 @@ func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
}
nc, _, err := hj.Hijack()
if err != nil {
revel.ERROR.Printf("Hijack error: %v", err)
utils.Logger.Error("Hijack error", "error", err)
return
}
defer func() {
if err = nc.Close(); err != nil {
revel.ERROR.Println(err)
utils.Logger.Error("Connection close error", "error", err)
}
if err = d.Close(); err != nil {
revel.ERROR.Println(err)
utils.Logger.Error("Dial close error", "error", err)
}
}()
err = r.Write(d)
if err != nil {
revel.ERROR.Printf("Error copying request to target: %v", err)
utils.Logger.Error("Error copying request to target", "error", err)
return
}

11
logger/doc.go Normal file
View File

@@ -0,0 +1,11 @@
/*
Package logger contains filters and handles for the logging utilities in Revel.
These facilities all currently use the logging library called log15 at
https://github.com/inconshreveable/log15
Wrappers for the handlers are written here to provide a kind of isolation layer for Revel
in case sometime in the future we would like to switch to another source to implement logging
*/
package logger

206
logger/format.go Normal file
View File

@@ -0,0 +1,206 @@
package logger
import (
"bytes"
"fmt"
"github.com/revel/log15"
"reflect"
"strconv"
"sync"
"time"
)
const (
timeFormat = "2006-01-02T15:04:05-0700"
termTimeFormat = "2006/01/02 15:04:05"
termSmallTimeFormat = "15:04:05"
floatFormat = 'f'
errorKey = "REVEL_ERROR"
)
var (
// Name the log level
toRevel = map[log15.Lvl]string{log15.LvlDebug: "DEBUG",
log15.LvlInfo: "INFO", log15.LvlWarn: "WARN", log15.LvlError: "ERROR", log15.LvlCrit: "CRIT"}
)
// Outputs to the terminal in a format like below
// INFO 09:11:32 server-engine.go:169: Request Stats
func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
dateFormat := termTimeFormat
if smallDate {
dateFormat = termSmallTimeFormat
}
return log15.FormatFunc(func(r *log15.Record) []byte {
// Bash coloring http://misc.flogisoft.com/bash/tip_colors_and_formatting
var color = 0
switch r.Lvl {
case log15.LvlCrit:
// Magenta
color = 35
case log15.LvlError:
// Red
color = 31
case log15.LvlWarn:
// Yellow
color = 33
case log15.LvlInfo:
// Green
color = 32
case log15.LvlDebug:
// Cyan
color = 36
}
b := &bytes.Buffer{}
caller := findInContext("caller", r.Ctx)
module := findInContext("module", r.Ctx)
if noColor == false && color > 0 {
if len(module) > 0 {
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %6s %13s: %-40s ", color, toRevel[r.Lvl], r.Time.Format(dateFormat), module, caller, r.Msg)
} else {
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %13s: %-40s ", color, toRevel[r.Lvl], r.Time.Format(dateFormat), caller, r.Msg)
}
} else {
fmt.Fprintf(b, "%-5s %s %6s %13s: %-40s", toRevel[r.Lvl], r.Time.Format(dateFormat), module, caller, r.Msg)
}
for i := 0; i < len(r.Ctx); i += 2 {
if i != 0 {
b.WriteByte(' ')
}
k, ok := r.Ctx[i].(string)
if k == "caller" || k == "fn" || k == "module" {
continue
}
v := formatLogfmtValue(r.Ctx[i+1])
if !ok {
k, v = errorKey, formatLogfmtValue(k)
}
// TODO: we should probably check that all of your key bytes aren't invalid
if noColor == false && color > 0 {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m=%s", color, k, v)
} else {
b.WriteString(k)
b.WriteByte('=')
b.WriteString(v)
}
}
b.WriteByte('\n')
return b.Bytes()
})
}
func findInContext(key string, ctx []interface{}) string {
for i := 0; i < len(ctx); i += 2 {
k := ctx[i].(string)
if key == k {
return formatLogfmtValue(ctx[i+1])
}
}
return ""
}
// formatValue formats a value for serialization
func formatLogfmtValue(value interface{}) string {
if value == nil {
return "nil"
}
if t, ok := value.(time.Time); ok {
// Performance optimization: No need for escaping since the provided
// timeFormat doesn't have any escape characters, and escaping is
// expensive.
return t.Format(termTimeFormat)
}
value = formatShared(value)
switch v := value.(type) {
case bool:
return strconv.FormatBool(v)
case float32:
return strconv.FormatFloat(float64(v), floatFormat, 3, 64)
case float64:
return strconv.FormatFloat(v, floatFormat, 7, 64)
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
return fmt.Sprintf("%d", value)
case string:
return escapeString(v)
default:
return escapeString(fmt.Sprintf("%+v", value))
}
}
func formatShared(value interface{}) (result interface{}) {
defer func() {
if err := recover(); err != nil {
if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() {
result = "nil"
} else {
panic(err)
}
}
}()
switch v := value.(type) {
case time.Time:
return v.Format(timeFormat)
case error:
return v.Error()
case fmt.Stringer:
return v.String()
default:
return v
}
}
var stringBufPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
func escapeString(s string) string {
needsQuotes := false
needsEscape := false
for _, r := range s {
if r <= ' ' || r == '=' || r == '"' {
needsQuotes = true
}
if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' {
needsEscape = true
}
}
if needsEscape == false && needsQuotes == false {
return s
}
e := stringBufPool.Get().(*bytes.Buffer)
e.WriteByte('"')
for _, r := range s {
switch r {
case '\\', '"':
e.WriteByte('\\')
e.WriteByte(byte(r))
case '\n':
e.WriteString("\\n")
case '\r':
e.WriteString("\\r")
case '\t':
e.WriteString("\\t")
default:
e.WriteRune(r)
}
}
e.WriteByte('"')
var ret string
if needsQuotes {
ret = e.String()
} else {
ret = string(e.Bytes()[1 : e.Len()-1])
}
e.Reset()
stringBufPool.Put(e)
return ret
}

350
logger/handlers.go Normal file
View File

@@ -0,0 +1,350 @@
package logger
import (
"io"
"os"
colorable "github.com/mattn/go-colorable"
"github.com/revel/log15"
"gopkg.in/natefinch/lumberjack.v2"
)
// Filters out records which do not match the level
// Uses the `log15.FilterHandler` to perform this task
func LevelHandler(lvl LogLevel, h LogHandler) LogHandler {
l15Lvl := log15.Lvl(lvl)
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
return r.Lvl == l15Lvl
}, h)
}
// Filters out records which do not match the level
// Uses the `log15.FilterHandler` to perform this task
func MinLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
l15Lvl := log15.Lvl(lvl)
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
return r.Lvl <= l15Lvl
}, h)
}
// Filters out records which match the level
// Uses the `log15.FilterHandler` to perform this task
func NotLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
l15Lvl := log15.Lvl(lvl)
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
return r.Lvl != l15Lvl
}, h)
}
// Adds in a context called `caller` to the record (contains file name and line number like `foo.go:12`)
// Uses the `log15.CallerFileHandler` to perform this task
func CallerFileHandler(h LogHandler) LogHandler {
return log15.CallerFileHandler(h)
}
// Adds in a context called `caller` to the record (contains file name and line number like `foo.go:12`)
// Uses the `log15.CallerFuncHandler` to perform this task
func CallerFuncHandler(h LogHandler) LogHandler {
return log15.CallerFuncHandler(h)
}
// Filters out records which match the key value pair
// Uses the `log15.MatchFilterHandler` to perform this task
func MatchHandler(key string, value interface{}, h LogHandler) LogHandler {
return log15.MatchFilterHandler(key, value, h)
}
// If match then A handler is called otherwise B handler is called
func MatchAbHandler(key string, value interface{}, a, b LogHandler) LogHandler {
return log15.FuncHandler(func(r *log15.Record) error {
for i := 0; i < len(r.Ctx); i += 2 {
if r.Ctx[i] == key {
if r.Ctx[i+1] == value {
if a != nil {
return a.Log(r)
}
return nil
}
}
}
if b != nil {
return b.Log(r)
}
return nil
})
}
// The nil handler is used if logging for a specific request needs to be turned off
func NilHandler() LogHandler {
return log15.FuncHandler(func(r *log15.Record) error {
return nil
})
}
// Match all values in map to log
func MatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler {
return matchMapHandler(matchMap, false, a)
}
// Match !(Match all values in map to log) The inverse of MatchMapHandler
func NotMatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler {
return matchMapHandler(matchMap, true, a)
}
// Rather then chaining multiple filter handlers, process all here
func matchMapHandler(matchMap map[string]interface{}, inverse bool, a LogHandler) LogHandler {
return log15.FuncHandler(func(r *log15.Record) error {
checkMap := map[string]bool{}
// Copy the map to a bool
for i := 0; i < len(r.Ctx); i += 2 {
if value, found := matchMap[r.Ctx[i].(string)]; found && value == r.Ctx[i+1] {
checkMap[r.Ctx[i].(string)] = true
}
}
if len(checkMap) == len(matchMap) {
if !inverse {
return a.Log(r)
}
} else if inverse {
return a.Log(r)
}
return nil
})
}
// Filters out records which do not match the key value pair
// Uses the `log15.FilterHandler` to perform this task
func NotMatchHandler(key string, value interface{}, h LogHandler) LogHandler {
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
switch key {
case r.KeyNames.Lvl:
return r.Lvl != value
case r.KeyNames.Time:
return r.Time != value
case r.KeyNames.Msg:
return r.Msg != value
}
for i := 0; i < len(r.Ctx); i += 2 {
if r.Ctx[i] == key {
return r.Ctx[i+1] == value
}
}
return true
}, h)
}
func MultiHandler(hs ...LogHandler) LogHandler {
// Convert the log handlers to log15.Handlers
handlers := []log15.Handler{}
for _, h := range hs {
if h != nil {
handlers = append(handlers, h)
}
}
return log15.MultiHandler(handlers...)
}
// Outputs the records to the passed in stream
// Uses the `log15.StreamHandler` to perform this task
func StreamHandler(wr io.Writer, fmtr LogFormat) LogHandler {
return log15.StreamHandler(wr, fmtr)
}
// Filter handler, this is the only
// Uses the `log15.FilterHandler` to perform this task
func FilterHandler(fn func(r *log15.Record) bool, h LogHandler) LogHandler {
return log15.FilterHandler(fn, h)
}
type ListLogHandler struct {
handlers []LogHandler
}
func NewListLogHandler(h1, h2 LogHandler) *ListLogHandler {
ll := &ListLogHandler{handlers: []LogHandler{h1, h2}}
return ll
}
func (ll *ListLogHandler) Log(r *log15.Record) (err error) {
for _, handler := range ll.handlers {
if err == nil {
err = handler.Log(r)
} else {
handler.Log(r)
}
}
return
}
func (ll *ListLogHandler) Add(h LogHandler) {
if h != nil {
ll.handlers = append(ll.handlers, h)
}
}
func (ll *ListLogHandler) Del(h LogHandler) {
if h != nil {
for i, handler := range ll.handlers {
if handler == h {
ll.handlers = append(ll.handlers[:i], ll.handlers[i+1:]...)
}
}
}
}
type CompositeMultiHandler struct {
DebugHandler LogHandler
InfoHandler LogHandler
WarnHandler LogHandler
ErrorHandler LogHandler
CriticalHandler LogHandler
}
func NewCompositeMultiHandler() (*CompositeMultiHandler, LogHandler) {
cw := &CompositeMultiHandler{}
return cw, cw
}
func (h *CompositeMultiHandler) Log(r *log15.Record) (err error) {
var handler LogHandler
switch r.Lvl {
case log15.LvlInfo:
handler = h.InfoHandler
case log15.LvlDebug:
handler = h.DebugHandler
case log15.LvlWarn:
handler = h.WarnHandler
case log15.LvlError:
handler = h.ErrorHandler
case log15.LvlCrit:
handler = h.CriticalHandler
}
// Embed the caller function in the context
if handler != nil {
handler.Log(r)
}
return
}
func (h *CompositeMultiHandler) SetHandler(handler LogHandler, replace bool, level LogLevel) {
if handler == nil {
// Ignore empty handler
return
}
source := &h.DebugHandler
switch level {
case LvlDebug:
source = &h.DebugHandler
case LvlInfo:
source = &h.InfoHandler
case LvlWarn:
source = &h.WarnHandler
case LvlError:
source = &h.ErrorHandler
case LvlCrit:
source = &h.CriticalHandler
}
if !replace && *source != nil {
// If this already was a list add a new logger to it
if ll, found := (*source).(*ListLogHandler); found {
ll.Add(handler)
} else {
*source = NewListLogHandler(*source, handler)
}
} else {
*source = handler
}
}
func (h *CompositeMultiHandler) SetHandlers(handler LogHandler, options *LogOptions) {
if len(options.Levels) == 0 {
options.Levels = LvlAllList
}
// Set all levels
for _, lvl := range options.Levels {
h.SetHandler(handler, options.ReplaceExistingHandler, lvl)
}
}
func (h *CompositeMultiHandler) SetJson(writer io.Writer, options *LogOptions) {
handler := CallerFileHandler(StreamHandler(writer, log15.JsonFormatEx(
options.GetBoolDefault("pretty", false),
options.GetBoolDefault("lineSeparated", true),
)))
if options.HandlerWrap != nil {
handler = options.HandlerWrap.SetChild(handler)
}
h.SetHandlers(handler, options)
}
// Use built in rolling function
func (h *CompositeMultiHandler) SetJsonFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{
Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days
MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true),
}
h.SetJson(writer, options)
}
func (h *CompositeMultiHandler) SetTerminal(writer io.Writer, options *LogOptions) {
streamHandler := StreamHandler(
writer,
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
if os.Stdout == writer {
streamHandler = StreamHandler(
colorable.NewColorableStdout(),
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
} else if os.Stderr == writer {
streamHandler = StreamHandler(
colorable.NewColorableStderr(),
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
}
handler := CallerFileHandler(streamHandler)
if options.HandlerWrap != nil {
handler = options.HandlerWrap.SetChild(handler)
}
h.SetHandlers(handler, options)
}
// Use built in rolling function
func (h *CompositeMultiHandler) SetTerminalFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{
Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days
MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true),
}
h.SetTerminal(writer, options)
}
func (h *CompositeMultiHandler) Disable(levels ...LogLevel) {
if len(levels) == 0 {
levels = LvlAllList
}
for _, level := range levels {
switch level {
case LvlDebug:
h.DebugHandler = nil
case LvlInfo:
h.InfoHandler = nil
case LvlWarn:
h.WarnHandler = nil
case LvlError:
h.ErrorHandler = nil
case LvlCrit:
h.CriticalHandler = nil
}
}
}

656
logger/log.go.old Normal file
View File

@@ -0,0 +1,656 @@
package logger
// LoggedError is wrapper to differentiate logged panics from unexpected ones.
import (
"os"
"fmt"
"strings"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"sync"
"go.uber.org/zap/buffer"
"time"
"encoding/base64"
"unicode/utf8"
"encoding/json"
"math"
"io"
)
type (
MultiLogger interface {
//log15.Logger
//// New returns a new Logger that has this logger's context plus the given context
New(ctx ...interface{}) MultiLogger
//
// The encoders job is to encode the
SetHandler(h LogHandler)
SetStackDepth(int) MultiLogger
//
//// Log a message at the given level with context key/value pairs
Debug(msg string, ctx ...interface{})
Debugf(msg string, params ...interface{})
Info(msg string, ctx ...interface{})
Infof(msg string, params ...interface{})
Warn(msg string, ctx ...interface{})
Warnf(msg string, params ...interface{})
Error(msg string, ctx ...interface{})
Errorf(msg string, params ...interface{})
Crit(msg string, ctx ...interface{})
Critf(msg string, params ...interface{})
//// Logs a message as an Crit and exits
Fatal(msg string, ctx ...interface{})
Fatalf(msg string, params ...interface{})
//// Logs a message as an Crit and panics
Panic(msg string, ctx ...interface{})
Panicf(msg string, params ...interface{})
}
// The log han
LogHandler interface {
Encode(Record) ([]byte, error)
GetLevel() Level
GetWriter() io.Writer
}
// The Record
Record struct {
Level Level
Time time.Time
LoggerName string
Message string
Caller EntryCaller
Stack string
Context []Field
}
// The fields passed in
Field interface {
GetKey() string
GetValueAsString() string
GetValue() interface{}
}
EntryCaller interface {
IsDefined() bool
GetPC() uintptr
GetFile() string
GetLine() int
}
// Called only if the logger needs
ResolveLaterLogger func() interface{}
FieldType int
Level int
)
type (
zapLogger struct {
logger *zap.SugaredLogger
coreList []*zapcore.Core
}
zapField struct {
Key string
Type FieldType
Integer int64
String string
Interface interface{}
}
zapEntryCaller struct {
Defined bool
PC uintptr
File string
Line int
}
zapEncoder struct {
lh LogHandler
}
)
func newLogger(addCaller bool) MultiLogger {
logger := zap.New(nil).WithOptions(zap.AddCaller())
l := &zapLogger{logger:logger.Sugar()}
return l
}
// It is up to the handler to determine the synchronization to the output
// streams
func (z *zapLogger) SetHandler(lh LogHandler) {
// Swap out the logger when a new handler is attached
encoder := &zapEncoder{lh}
levelHandler := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.Level(lh.GetLevel())
})
logger := zap.New(zapcore.NewCore(encoder, nil, levelHandler)).WithOptions(zap.AddCaller())
Logger.With("foo","bar").Desugar().Core()
}
var Logger *zap.SugaredLogger
func InitLogger(logLevel zapcore.Level) {
config :=zap.NewDevelopmentEncoderConfig()
config.EncodeLevel = zapcore.CapitalColorLevelEncoder
consoleEncoder := NewConsoleEncoder(config)
lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= logLevel
})
consoleDebugging := zapcore.Lock(os.Stdout)
core := zapcore.NewTee(
zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
)
logger := zap.New(core).WithOptions(zap.AddCaller())
Logger = logger.Sugar()
}
type LoggedError struct{ error }
func NewLoggedError(err error) *LoggedError {
return &LoggedError{err}
}
func Errorf(format string, args ...interface{}) {
// Ensure the user's command prompt starts on the next line.
if !strings.HasSuffix(format, "\n") {
format += "\n"
}
fmt.Fprintf(os.Stderr, format, args...)
panic(format) // Panic instead of os.Exit so that deferred will run.
}
// This is all for the Console logger - a little wordy but it works
var _sliceEncoderPool = sync.Pool{
New: func() interface{} {
return &sliceArrayEncoder{elems: make([]interface{}, 0, 2)}
},
}
func getSliceEncoder() *sliceArrayEncoder {
return _sliceEncoderPool.Get().(*sliceArrayEncoder)
}
func putSliceEncoder(e *sliceArrayEncoder) {
e.elems = e.elems[:0]
_sliceEncoderPool.Put(e)
}
type consoleEncoder struct {
*zapcore.EncoderConfig
openNamespaces int
buf *buffer.Buffer
reflectBuf *buffer.Buffer
reflectEnc *json.Encoder
}
var (
_pool = buffer.NewPool()
// Get retrieves a buffer from the pool, creating one if necessary.
Get = _pool.Get
)
// NewConsoleEncoder creates an encoder whose output is designed for human -
// rather than machine - consumption. It serializes the core log entry data
// (message, level, timestamp, etc.) in a plain-text format and leaves the
// structured context as JSON.
//
// Note that although the console encoder doesn't use the keys specified in the
// encoder configuration, it will omit any element whose key is set to the empty
// string.
func NewConsoleEncoder(cfg zapcore.EncoderConfig) zapcore.Encoder {
ec := &consoleEncoder{buf : Get(), reflectBuf: Get()}
ec.EncoderConfig = &cfg
return ec
}
func (c consoleEncoder) Clone() zapcore.Encoder {
return &consoleEncoder{buf : Get(), reflectBuf: Get()}
}
func (c consoleEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
line := Get()
var color = 0
switch ent.Level {
case zap.PanicLevel:
// Magenta
color = 35
case zap.ErrorLevel:
// Red
color = 31
case zap.WarnLevel:
// Yellow
color = 33
case zap.InfoLevel:
// Green
color = 32
case zap.DebugLevel:
// Cyan
color = 36
}
// We don't want the entry's metadata to be quoted and escaped (if it's
// encoded as strings), which means that we can't use the JSON encoder. The
// simplest option is to use the memory encoder and fmt.Fprint.
//
// If this ever becomes a performance bottleneck, we can implement
// ArrayEncoder for our plain-text format.
arr := getSliceEncoder()
if c.LevelKey != "" && c.EncodeLevel != nil {
arr.AppendString(fmt.Sprintf("\x1b[%dm%-5s\x1b[0m",color,ent.Level.CapitalString()))
}
if ent.LoggerName != "" && c.NameKey != "" {
nameEncoder := c.EncodeName
if nameEncoder == nil {
// Fall back to FullNameEncoder for backward compatibility.
nameEncoder = zapcore.FullNameEncoder
}
nameEncoder(ent.LoggerName, arr)
}
if c.TimeKey != "" && c.EncodeTime != nil {
arr.AppendString(ent.Time.Format("15:04:05"))
}
if ent.Caller.Defined && c.CallerKey != "" && c.EncodeCaller != nil {
c.EncodeCaller(ent.Caller, arr)
}
for i := range arr.elems {
if i > 0 {
line.AppendByte(' ')
}
fmt.Fprint(line, arr.elems[i])
}
putSliceEncoder(arr)
// Add the message itself.
if c.MessageKey != "" {
c.addTabIfNecessary(line)
line.AppendString(ent.Message)
}
// Add any structured context.
c.writeContext(line, fields)
// If there's no stacktrace key, honor that; this allows users to force
// single-line output.
if ent.Stack != "" && c.StacktraceKey != "" {
line.AppendByte('\n')
line.AppendString(ent.Stack)
}
if c.LineEnding != "" {
line.AppendString(c.LineEnding)
} else {
line.AppendString(zapcore.DefaultLineEnding)
}
return line, nil
}
func (c consoleEncoder) writeContext(line *buffer.Buffer, extra []zapcore.Field) {
context := c.Clone().(*consoleEncoder)
defer context.buf.Free()
//
addFields(context, extra)
context.closeOpenNamespaces()
if context.buf.Len() == 0 {
return
}
//
line.Write(context.buf.Bytes())
}
func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) {
for i := range fields {
fields[i].AddTo(enc)
}
}
func (c consoleEncoder) addTabIfNecessary(line *buffer.Buffer) {
if line.Len() > 0 {
line.AppendByte('\t')
}
}
func (enc *consoleEncoder) AddArray(key string, arr zapcore.ArrayMarshaler) error {
enc.addKey(key)
return enc.AppendArray(arr)
}
func (enc *consoleEncoder) AddObject(key string, obj zapcore.ObjectMarshaler) error {
enc.addKey(key)
return enc.AppendObject(obj)
}
func (enc *consoleEncoder) AddBinary(key string, val []byte) {
enc.AddString(key, base64.StdEncoding.EncodeToString(val))
}
func (enc *consoleEncoder) AddByteString(key string, val []byte) {
enc.addKey(key)
enc.AppendByteString(val)
}
func (enc *consoleEncoder) AddBool(key string, val bool) {
enc.addKey(key)
enc.AppendBool(val)
}
func (enc *consoleEncoder) AddComplex128(key string, val complex128) {
enc.addKey(key)
enc.AppendComplex128(val)
}
func (enc *consoleEncoder) AddDuration(key string, val time.Duration) {
enc.addKey(key)
enc.AppendDuration(val)
}
func (enc *consoleEncoder) AddFloat64(key string, val float64) {
enc.addKey(key)
enc.AppendFloat64(val)
}
func (enc *consoleEncoder) AddInt64(key string, val int64) {
enc.addKey(key)
enc.AppendInt64(val)
}
func (enc *consoleEncoder) AddReflected(key string, obj interface{}) error {
enc.resetReflectBuf()
err := enc.reflectEnc.Encode(obj)
if err != nil {
return err
}
enc.reflectBuf.TrimNewline()
enc.addKey(key)
_, err = enc.buf.Write(enc.reflectBuf.Bytes())
return err
}
func (enc *consoleEncoder) OpenNamespace(key string) {
enc.addKey(key)
enc.buf.AppendByte('{')
enc.openNamespaces++
}
func (enc *consoleEncoder) AddString(key, val string) {
enc.addKey(key)
enc.AppendString(val)
}
func (enc *consoleEncoder) AddTime(key string, val time.Time) {
enc.addKey(key)
enc.AppendTime(val)
}
func (enc *consoleEncoder) AddUint64(key string, val uint64) {
enc.addKey(key)
enc.AppendUint64(val)
}
func (enc *consoleEncoder) addKey(key string) {
// Print key in different color
enc.buf.AppendString(fmt.Sprintf(" \x1b[%dm%s\x1b[0m",36,key))
enc.buf.AppendByte('=')
}
func (enc *consoleEncoder) AppendArray(arr zapcore.ArrayMarshaler) error {
enc.buf.AppendByte('[')
err := arr.MarshalLogArray(enc)
enc.buf.AppendByte(']')
return err
}
func (enc *consoleEncoder) AppendObject(obj zapcore.ObjectMarshaler) error {
enc.buf.AppendByte('{')
err := obj.MarshalLogObject(enc)
enc.buf.AppendByte('}')
return err
}
func (enc *consoleEncoder) AppendBool(val bool) {
enc.buf.AppendBool(val)
}
func (enc *consoleEncoder) AppendByteString(val []byte) {
enc.buf.AppendByte('"')
enc.safeAddByteString(val)
enc.buf.AppendByte('"')
}
func (enc *consoleEncoder) AppendComplex128(val complex128) {
// Cast to a platform-independent, fixed-size type.
r, i := float64(real(val)), float64(imag(val))
enc.buf.AppendByte('"')
// Because we're always in a quoted string, we can use strconv without
// special-casing NaN and +/-Inf.
enc.buf.AppendFloat(r, 64)
enc.buf.AppendByte('+')
enc.buf.AppendFloat(i, 64)
enc.buf.AppendByte('i')
enc.buf.AppendByte('"')
}
func (enc *consoleEncoder) AppendDuration(val time.Duration) {
cur := enc.buf.Len()
enc.EncodeDuration(val, enc)
if cur == enc.buf.Len() {
// User-supplied EncodeDuration is a no-op. Fall back to nanoseconds to keep
// JSON valid.
enc.AppendInt64(int64(val))
}
}
func (enc *consoleEncoder) AppendInt64(val int64) {
enc.buf.AppendInt(val)
}
func (enc *consoleEncoder) resetReflectBuf() {
if enc.reflectBuf == nil {
enc.reflectBuf = Get()
enc.reflectEnc = json.NewEncoder(enc.reflectBuf)
} else {
enc.reflectBuf.Reset()
}
}
func (enc *consoleEncoder) AppendReflected(val interface{}) error {
enc.resetReflectBuf()
err := enc.reflectEnc.Encode(val)
if err != nil {
return err
}
enc.reflectBuf.TrimNewline()
_, err = enc.buf.Write(enc.reflectBuf.Bytes())
return err
}
func (enc *consoleEncoder) AppendString(val string) {
enc.safeAddString(val)
}
func (enc *consoleEncoder) AppendTime(val time.Time) {
cur := enc.buf.Len()
enc.EncodeTime(val, enc)
if cur == enc.buf.Len() {
// User-supplied EncodeTime is a no-op. Fall back to nanos since epoch to keep
// output JSON valid.
enc.AppendInt64(val.UnixNano())
}
}
func (enc *consoleEncoder) AppendUint64(val uint64) {
enc.buf.AppendUint(val)
}
func (enc *consoleEncoder) appendFloat(val float64, bitSize int) {
switch {
case math.IsNaN(val):
enc.buf.AppendString(`"NaN"`)
case math.IsInf(val, 1):
enc.buf.AppendString(`"+Inf"`)
case math.IsInf(val, -1):
enc.buf.AppendString(`"-Inf"`)
default:
enc.buf.AppendFloat(val, bitSize)
}
}
// safeAddString JSON-escapes a string and appends it to the internal buffer.
// Unlike the standard library's encoder, it doesn't attempt to protect the
// user from browser vulnerabilities or JSONP-related problems.
func (enc *consoleEncoder) safeAddString(s string) {
for i := 0; i < len(s); {
if enc.tryAddRuneSelf(s[i]) {
i++
continue
}
r, size := utf8.DecodeRuneInString(s[i:])
if enc.tryAddRuneError(r, size) {
i++
continue
}
enc.buf.AppendString(s[i : i+size])
i += size
}
}
// safeAddByteString is no-alloc equivalent of safeAddString(string(s)) for s []byte.
func (enc *consoleEncoder) safeAddByteString(s []byte) {
for i := 0; i < len(s); {
if enc.tryAddRuneSelf(s[i]) {
i++
continue
}
r, size := utf8.DecodeRune(s[i:])
if enc.tryAddRuneError(r, size) {
i++
continue
}
enc.buf.Write(s[i : i+size])
i += size
}
}
// tryAddRuneSelf appends b if it is valid UTF-8 character represented in a single byte.
func (enc *consoleEncoder) tryAddRuneSelf(b byte) bool {
if b >= utf8.RuneSelf {
return false
}
if 0x20 <= b && b != '\\' && b != '"' {
enc.buf.AppendByte(b)
return true
}
switch b {
case '\\', '"':
enc.buf.AppendByte('\\')
enc.buf.AppendByte(b)
case '\n':
enc.buf.AppendByte('\\')
enc.buf.AppendByte('n')
case '\r':
enc.buf.AppendByte('\\')
enc.buf.AppendByte('r')
case '\t':
enc.buf.AppendByte('\\')
enc.buf.AppendByte('t')
default:
// Encode bytes < 0x20, except for the escape sequences above.
enc.buf.AppendString(`\u00`)
enc.buf.AppendByte(_hex[b>>4])
enc.buf.AppendByte(_hex[b&0xF])
}
return true
}
func (enc *consoleEncoder) closeOpenNamespaces() {
for i := 0; i < enc.openNamespaces; i++ {
enc.buf.AppendByte('}')
}
}
func (enc *consoleEncoder) tryAddRuneError(r rune, size int) bool {
if r == utf8.RuneError && size == 1 {
enc.buf.AppendString(`\ufffd`)
return true
}
return false
}
func (enc *consoleEncoder) AddComplex64(k string, v complex64) { enc.AddComplex128(k, complex128(v)) }
func (enc *consoleEncoder) AddFloat32(k string, v float32) { enc.AddFloat64(k, float64(v)) }
func (enc *consoleEncoder) AddInt(k string, v int) { enc.AddInt64(k, int64(v)) }
func (enc *consoleEncoder) AddInt32(k string, v int32) { enc.AddInt64(k, int64(v)) }
func (enc *consoleEncoder) AddInt16(k string, v int16) { enc.AddInt64(k, int64(v)) }
func (enc *consoleEncoder) AddInt8(k string, v int8) { enc.AddInt64(k, int64(v)) }
func (enc *consoleEncoder) AddUint(k string, v uint) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AddUint32(k string, v uint32) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AddUint16(k string, v uint16) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AddUint8(k string, v uint8) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AddUintptr(k string, v uintptr) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AppendComplex64(v complex64) { enc.AppendComplex128(complex128(v)) }
func (enc *consoleEncoder) AppendFloat64(v float64) { enc.appendFloat(v, 64) }
func (enc *consoleEncoder) AppendFloat32(v float32) { enc.appendFloat(float64(v), 32) }
func (enc *consoleEncoder) AppendInt(v int) { enc.AppendInt64(int64(v)) }
func (enc *consoleEncoder) AppendInt32(v int32) { enc.AppendInt64(int64(v)) }
func (enc *consoleEncoder) AppendInt16(v int16) { enc.AppendInt64(int64(v)) }
func (enc *consoleEncoder) AppendInt8(v int8) { enc.AppendInt64(int64(v)) }
func (enc *consoleEncoder) AppendUint(v uint) { enc.AppendUint64(uint64(v)) }
func (enc *consoleEncoder) AppendUint32(v uint32) { enc.AppendUint64(uint64(v)) }
func (enc *consoleEncoder) AppendUint16(v uint16) { enc.AppendUint64(uint64(v)) }
func (enc *consoleEncoder) AppendUint8(v uint8) { enc.AppendUint64(uint64(v)) }
func (enc *consoleEncoder) AppendUintptr(v uintptr) { enc.AppendUint64(uint64(v)) }
const _hex = "0123456789abcdef"
type sliceArrayEncoder struct {
elems []interface{}
}
func (s *sliceArrayEncoder) AppendArray(v zapcore.ArrayMarshaler) error {
enc := &sliceArrayEncoder{}
err := v.MarshalLogArray(enc)
s.elems = append(s.elems, enc.elems)
return err
}
func (s *sliceArrayEncoder) AppendObject(v zapcore.ObjectMarshaler) error {
m := zapcore.NewMapObjectEncoder()
err := v.MarshalLogObject(m)
s.elems = append(s.elems, m.Fields)
return err
}
func (s *sliceArrayEncoder) AppendReflected(v interface{}) error {
s.elems = append(s.elems, v)
return nil
}
func (s *sliceArrayEncoder) AppendBool(v bool) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendByteString(v []byte) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendComplex128(v complex128) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendComplex64(v complex64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendDuration(v time.Duration) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendFloat64(v float64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendFloat32(v float32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt(v int) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt64(v int64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt32(v int32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt16(v int16) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt8(v int8) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendString(v string) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendTime(v time.Time) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint(v uint) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint64(v uint64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint32(v uint32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint16(v uint16) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint8(v uint8) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUintptr(v uintptr) { s.elems = append(s.elems, v) }

227
logger/logger.go Normal file
View File

@@ -0,0 +1,227 @@
package logger
import (
"fmt"
"github.com/revel/config"
"github.com/revel/log15"
"log"
"os"
)
// The LogHandler defines the interface to handle the log records
type (
// The Multilogger reduces the number of exposed defined logging variables,
// and allows the output to be easily refined
MultiLogger interface {
//log15.Logger
//// New returns a new Logger that has this logger's context plus the given context
New(ctx ...interface{}) MultiLogger
//
//// SetHandler updates the logger to write records to the specified handler.
SetHandler(h LogHandler)
SetStackDepth(int) MultiLogger
//
//// Log a message at the given level with context key/value pairs
Debug(msg string, ctx ...interface{})
Debugf(msg string, params ...interface{})
Info(msg string, ctx ...interface{})
Infof(msg string, params ...interface{})
Warn(msg string, ctx ...interface{})
Warnf(msg string, params ...interface{})
Error(msg string, ctx ...interface{})
Errorf(msg string, params ...interface{})
Crit(msg string, ctx ...interface{})
Critf(msg string, params ...interface{})
//// Logs a message as an Crit and exits
Fatal(msg string, ctx ...interface{})
Fatalf(msg string, params ...interface{})
//// Logs a message as an Crit and panics
Panic(msg string, ctx ...interface{})
Panicf(msg string, params ...interface{})
}
LogHandler interface {
log15.Handler
}
LogStackHandler interface {
LogHandler
GetStack() int
}
ParentLogHandler interface {
SetChild(handler LogHandler) LogHandler
}
LogFormat interface {
log15.Format
}
LogLevel log15.Lvl
RevelLogger struct {
log15.Logger
}
// Used for the callback to LogFunctionMap
LogOptions struct {
Ctx *config.Context
ReplaceExistingHandler bool
HandlerWrap ParentLogHandler
Levels []LogLevel
ExtendedOptions map[string]interface{}
}
)
const (
LvlDebug = LogLevel(log15.LvlDebug)
LvlInfo = LogLevel(log15.LvlInfo)
LvlWarn = LogLevel(log15.LvlWarn)
LvlError = LogLevel(log15.LvlError)
LvlCrit = LogLevel(log15.LvlCrit)
)
// A list of all the log levels
var LvlAllList = []LogLevel{LvlDebug, LvlInfo, LvlWarn, LvlError, LvlCrit}
// The log function map can be added to, so that you can specify your own logging mechanism
var LogFunctionMap = map[string]func(*CompositeMultiHandler, *LogOptions){
// Do nothing - set the logger off
"off": func(c *CompositeMultiHandler, logOptions *LogOptions) {
// Only drop the results if there is a parent handler defined
if logOptions.HandlerWrap != nil {
for _, l := range logOptions.Levels {
c.SetHandler(logOptions.HandlerWrap.SetChild(NilHandler()), logOptions.ReplaceExistingHandler, l)
}
}
},
// Do nothing - set the logger off
"": func(*CompositeMultiHandler, *LogOptions) {},
// Set the levels to stdout, replace existing
"stdout": func(c *CompositeMultiHandler, logOptions *LogOptions) {
if logOptions.Ctx != nil {
logOptions.SetExtendedOptions(
"noColor", !logOptions.Ctx.BoolDefault("log.colorize", true),
"smallDate", logOptions.Ctx.BoolDefault("log.smallDate", true))
}
c.SetTerminal(os.Stdout, logOptions)
},
// Set the levels to stderr output to terminal
"stderr": func(c *CompositeMultiHandler, logOptions *LogOptions) {
c.SetTerminal(os.Stderr, logOptions)
},
}
// Set the systems default logger
// Default logs will be captured and handled by revel at level info
func SetDefaultLog(fromLog MultiLogger) {
log.SetOutput(loggerRewrite{Logger: fromLog, Level: log15.LvlInfo, hideDeprecated: true})
// No need to show date and time, that will be logged with revel
log.SetFlags(0)
}
// Formatted debug call
func (rl *RevelLogger) Debugf(msg string, param ...interface{}) {
rl.Debug(fmt.Sprintf(msg, param...))
}
// Formatted info call
func (rl *RevelLogger) Infof(msg string, param ...interface{}) {
rl.Info(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Warnf(msg string, param ...interface{}) {
rl.Warn(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Errorf(msg string, param ...interface{}) {
rl.Error(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Critf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Fatalf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...))
os.Exit(1)
}
func (rl *RevelLogger) Panicf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...))
panic(msg)
}
func (rl *RevelLogger) Fatal(msg string, ctx ...interface{}) {
rl.Crit(msg, ctx...)
os.Exit(1)
}
func (rl *RevelLogger) Panic(msg string, ctx ...interface{}) {
rl.Crit(msg, ctx...)
panic(msg)
}
// Override log15 method
func (rl *RevelLogger) New(ctx ...interface{}) MultiLogger {
old := &RevelLogger{Logger: rl.Logger.New(ctx...)}
return old
}
// Set the stack level to check for the caller
func (rl *RevelLogger) SetStackDepth(amount int) MultiLogger {
rl.Logger.SetStackDepth(amount) // Ignore the logger returned
return rl
}
// Create a new logger
func New(ctx ...interface{}) MultiLogger {
r := &RevelLogger{Logger: log15.New(ctx...)}
r.SetStackDepth(1)
return r
}
// Set the handler in the Logger
func (rl *RevelLogger) SetHandler(h LogHandler) {
rl.Logger.SetHandler(h)
}
type parentLogHandler struct {
setChild func(handler LogHandler) LogHandler
}
func NewParentLogHandler(callBack func(child LogHandler) LogHandler) ParentLogHandler {
return &parentLogHandler{callBack}
}
func (p *parentLogHandler) SetChild(child LogHandler) LogHandler {
return p.setChild(child)
}
// Create a new log options
func NewLogOptions(cfg *config.Context, replaceHandler bool, phandler ParentLogHandler, lvl ...LogLevel) (logOptions *LogOptions) {
logOptions = &LogOptions{
Ctx: cfg,
ReplaceExistingHandler: replaceHandler,
HandlerWrap: phandler,
Levels: lvl,
ExtendedOptions: map[string]interface{}{},
}
return
}
// Assumes options will be an even number and have a string, value syntax
func (l *LogOptions) SetExtendedOptions(options ...interface{}) {
for x := 0; x < len(options); x += 2 {
l.ExtendedOptions[options[x].(string)] = options[x+1]
}
}
func (l *LogOptions) GetStringDefault(option, value string) string {
if v, found := l.ExtendedOptions[option]; found {
return v.(string)
}
return value
}
func (l *LogOptions) GetIntDefault(option string, value int) int {
if v, found := l.ExtendedOptions[option]; found {
return v.(int)
}
return value
}
func (l *LogOptions) GetBoolDefault(option string, value bool) bool {
if v, found := l.ExtendedOptions[option]; found {
return v.(bool)
}
return value
}

277
logger/utils.go Normal file
View File

@@ -0,0 +1,277 @@
package logger
import (
"gopkg.in/stack.v0"
"github.com/revel/config"
"github.com/revel/log15"
"log"
"os"
"path/filepath"
"strings"
)
// Utility package to make existing logging backwards compatible
var (
// Convert the string to LogLevel
toLevel = map[string]LogLevel{"debug": LogLevel(log15.LvlDebug),
"info": LogLevel(log15.LvlInfo), "request": LogLevel(log15.LvlInfo), "warn": LogLevel(log15.LvlWarn),
"error": LogLevel(log15.LvlError), "crit": LogLevel(log15.LvlCrit),
"trace": LogLevel(log15.LvlDebug), // TODO trace is deprecated, replaced by debug
}
)
func GetLogger(name string, logger MultiLogger) (l *log.Logger) {
switch name {
case "trace": // TODO trace is deprecated, replaced by debug
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlDebug}, "", 0)
case "debug":
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlDebug}, "", 0)
case "info":
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlInfo}, "", 0)
case "warn":
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlWarn}, "", 0)
case "error":
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlError}, "", 0)
case "request":
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlInfo}, "", 0)
}
return l
}
// Get all handlers based on the Config (if available)
func InitializeFromConfig(basePath string, config *config.Context) (c *CompositeMultiHandler) {
// If running in test mode suppress anything that is not an error
if config!=nil && config.BoolDefault("testModeFlag",false) {
config.SetOption("log.info.output","none")
config.SetOption("log.debug.output","none")
config.SetOption("log.warn.output","none")
config.SetOption("log.error.output","stderr")
config.SetOption("log.crit.output","stderr")
}
// If the configuration has an all option we can skip some
c, _ = NewCompositeMultiHandler()
// Filters are assigned first, non filtered items override filters
initAllLog(c, basePath, config)
initLogLevels(c, basePath, config)
if c.CriticalHandler == nil && c.ErrorHandler != nil {
c.CriticalHandler = c.ErrorHandler
}
initFilterLog(c, basePath, config)
if c.CriticalHandler == nil && c.ErrorHandler != nil {
c.CriticalHandler = c.ErrorHandler
}
initRequestLog(c, basePath, config)
return c
}
// Init the log.all configuration options
func initAllLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
if config != nil {
extraLogFlag := config.BoolDefault("specialUseFlag", false)
if output, found := config.String("log.all.output"); found {
// Set all output for the specified handler
if extraLogFlag {
log.Printf("Adding standard handler for levels to >%s< ", output)
}
initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, LvlAllList...))
}
}
}
// Init the filter options
// log.all.filter ....
// log.error.filter ....
func initFilterLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
if config != nil {
extraLogFlag := config.BoolDefault("specialUseFlag", false)
// The commands to use
logFilterList := []struct {
LogPrefix, LogSuffix string
parentHandler func(map[string]interface{}) ParentLogHandler
}{{
"log.", ".filter",
func(keyMap map[string]interface{}) ParentLogHandler {
return NewParentLogHandler(func(child LogHandler) LogHandler {
return MatchMapHandler(keyMap, child)
})
},
}, {
"log.", ".nfilter",
func(keyMap map[string]interface{}) ParentLogHandler {
return NewParentLogHandler(func(child LogHandler) LogHandler {
return NotMatchMapHandler(keyMap, child)
})
},
}}
for _, logFilter := range logFilterList {
// Init for all filters
for _, name := range []string{"all", "debug", "info", "warn", "error", "crit",
"trace", // TODO trace is deprecated
} {
optionList := config.Options(logFilter.LogPrefix + name + logFilter.LogSuffix)
for _, option := range optionList {
splitOptions := strings.Split(option, ".")
keyMap := map[string]interface{}{}
for x := 3; x < len(splitOptions); x += 2 {
keyMap[splitOptions[x]] = splitOptions[x+1]
}
phandler := logFilter.parentHandler(keyMap)
if extraLogFlag {
log.Printf("Adding key map handler %s %s output %s", option, name, config.StringDefault(option, ""))
}
if name == "all" {
initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler))
} else {
initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler, toLevel[name]))
}
}
}
}
}
}
// Init the log.error, log.warn etc configuration options
func initLogLevels(c *CompositeMultiHandler, basePath string, config *config.Context) {
for _, name := range []string{"debug", "info", "warn", "error", "crit",
"trace", // TODO trace is deprecated
} {
if config != nil {
extraLogFlag := config.BoolDefault("specialUseFlag", false)
output, found := config.String("log." + name + ".output")
if found {
if extraLogFlag {
log.Printf("Adding standard handler %s output %s", name, output)
}
initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, toLevel[name]))
}
// Gets the list of options with said prefix
} else {
initHandlerFor(c, "stderr", basePath, NewLogOptions(config, true, nil, toLevel[name]))
}
}
}
// Init the request log options
func initRequestLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
// Request logging to a separate output handler
// This takes the InfoHandlers and adds a MatchAbHandler handler to it to direct
// context with the word "section=requestlog" to that handler.
// Note if request logging is not enabled the MatchAbHandler will not be added and the
// request log messages will be sent out the INFO handler
outputRequest := "stdout"
if config != nil {
outputRequest = config.StringDefault("log.request.output", "")
}
oldInfo := c.InfoHandler
c.InfoHandler = nil
if outputRequest != "" {
initHandlerFor(c, outputRequest, basePath, NewLogOptions(config, false, nil, LvlInfo))
}
if c.InfoHandler != nil || oldInfo != nil {
if c.InfoHandler == nil {
c.InfoHandler = oldInfo
} else {
c.InfoHandler = MatchAbHandler("section", "requestlog", c.InfoHandler, oldInfo)
}
}
}
// Returns a handler for the level using the output string
// Accept formats for output string are
// LogFunctionMap[value] callback function
// `stdout` `stderr` `full/file/path/to/location/app.log` `full/file/path/to/location/app.json`
func initHandlerFor(c *CompositeMultiHandler, output, basePath string, options *LogOptions) {
if options.Ctx != nil {
options.SetExtendedOptions(
"noColor", !options.Ctx.BoolDefault("log.colorize", true),
"smallDate", options.Ctx.BoolDefault("log.smallDate", true),
"maxSize", options.Ctx.IntDefault("log.maxsize", 1024*10),
"maxAge", options.Ctx.IntDefault("log.maxage", 14),
"maxBackups", options.Ctx.IntDefault("log.maxbackups", 14),
"compressBackups", !options.Ctx.BoolDefault("log.compressBackups", true),
)
}
output = strings.TrimSpace(output)
if funcHandler, found := LogFunctionMap[output]; found {
funcHandler(c, options)
} else {
switch output {
case "":
fallthrough
case "off":
// No handler, discard data
default:
// Write to file specified
if !filepath.IsAbs(output) {
output = filepath.Join(basePath, output)
}
if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil {
log.Panic(err)
}
if strings.HasSuffix(output, "json") {
c.SetJsonFile(output, options)
} else {
// Override defaults for a terminal file
options.SetExtendedOptions("noColor", true)
options.SetExtendedOptions("smallDate", false)
c.SetTerminalFile(output, options)
}
}
}
return
}
// This structure and method will handle the old output format and log it to the new format
type loggerRewrite struct {
Logger MultiLogger
Level log15.Lvl
hideDeprecated bool
}
var log_deprecated = []byte("* LOG DEPRECATED * ")
func (lr loggerRewrite) Write(p []byte) (n int, err error) {
if !lr.hideDeprecated {
p = append(log_deprecated, p...)
}
n = len(p)
if len(p) > 0 && p[n-1] == '\n' {
p = p[:n-1]
n--
}
switch lr.Level {
case log15.LvlInfo:
lr.Logger.Info(string(p))
case log15.LvlDebug:
lr.Logger.Debug(string(p))
case log15.LvlWarn:
lr.Logger.Warn(string(p))
case log15.LvlError:
lr.Logger.Error(string(p))
case log15.LvlCrit:
lr.Logger.Crit(string(p))
}
return
}
// For logging purposes the call stack can be used to record the stack trace of a bad error
// simply pass it as a context field in your log statement like
// `controller.Log.Critc("This should not occur","stack",revel.NewCallStack())`
func NewCallStack() interface{} {
return stack.Trace()
}

138
model/command_config.go Normal file
View File

@@ -0,0 +1,138 @@
package model
// The constants
import (
"go/build"
"os"
"path/filepath"
"strings"
"github.com/revel/cmd/utils"
)
const (
NEW COMMAND = iota + 1
RUN
BUILD
PACKAGE
CLEAN
TEST
VERSION
)
type (
// The Revel command type
COMMAND int
// The Command config for the line input
CommandConfig struct {
Index COMMAND // The index
Verbose bool `short:"v" long:"debug" description:"If set the logger is set to verbose"` // True if debug is active
HistoricMode bool `long:"historic-run-mode" description:"If set the runmode is passed a string not json"` // True if debug is active
ImportPath string // The import path (converted from various commands)
GoPath string // The GoPath
GoCmd string // The full path to the go executable
SrcRoot string // The source root
AppPath string // The application path
AppName string // The applicaiton name
BasePath string // The base path
SkeletonPath string // The skeleton path
BuildFlags []string `short:"X" long:"build-flags" description:"These flags will be used when building the application. May be specified multiple times, only applicable for Build, Run, Package, Test commands"`
// The new command
New struct {
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
Skeleton string `short:"s" long:"skeleton" description:"Path to skeleton folder (Must exist on GO PATH)" required:"false"`
Vendored bool `short:"V" long:"vendor" description:"True if project should contain a vendor folder to be initialized. Creates the vendor folder and the 'Gopkg.toml' file in the root"`
Run bool `short:"r" long:"run" description:"True if you want to run the application right away"`
} `command:"new"`
// The build command
Build struct {
TargetPath string `short:"t" long:"target-path" description:"Path to target folder. Folder will be completely deleted if it exists" required:"true"`
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"`
} `command:"build"`
// The run command
Run struct {
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
Port string `short:"p" long:"port" description:"The port to listen"`
NoProxy bool `short:"n" long:"no-proxy" description:"True if proxy server should not be started. This will only update the main and routes files on change"`
} `command:"run"`
// The package command
Package struct {
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"`
} `command:"package"`
// The clean command
Clean struct {
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
} `command:"clean"`
// The test command
Test struct {
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
Function string `short:"f" long:"suite-function" description:"The suite.function"`
} `command:"test"`
// The version command
Version struct {
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"false"`
} `command:"version"`
}
)
// Updates the import path depending on the command
func (c *CommandConfig) UpdateImportPath() bool {
var importPath string
required := true
switch c.Index {
case NEW:
importPath = c.New.ImportPath
case RUN:
importPath = c.Run.ImportPath
case BUILD:
importPath = c.Build.ImportPath
case PACKAGE:
importPath = c.Package.ImportPath
case CLEAN:
importPath = c.Clean.ImportPath
case TEST:
importPath = c.Test.ImportPath
case VERSION:
importPath = c.Version.ImportPath
required = false
}
if len(importPath) == 0 || filepath.IsAbs(importPath) || importPath[0] == '.' {
// Try to determine the import path from the GO paths and the command line
currentPath, err := os.Getwd()
if len(importPath) > 0 {
if importPath[0] == '.' {
// For a relative path
importPath = filepath.Join(currentPath, importPath)
}
// For an absolute path
currentPath, _ = filepath.Abs(importPath)
}
if err == nil {
for _, path := range strings.Split(build.Default.GOPATH, string(filepath.ListSeparator)) {
utils.Logger.Infof("Checking import path %s with %s", currentPath, path)
if strings.HasPrefix(currentPath, path) {
importPath = currentPath[len(path) + 1:]
// Remove the source from the path if it is there
if len(importPath)>4 && strings.ToLower(importPath[0:4]) == "src/" {
importPath = importPath[4:]
} else if importPath == "src" {
importPath = ""
}
utils.Logger.Info("Updated import path", "path", importPath)
}
}
}
}
c.ImportPath = importPath
utils.Logger.Info("Returned import path", "path", importPath, "buildpath",build.Default.GOPATH)
return (len(importPath) > 0 || !required)
}

View File

@@ -0,0 +1,11 @@
package model
// The embedded type name takes the import path and structure name
type EmbeddedTypeName struct {
ImportPath, StructName string
}
// Convert the type to a properly formatted import line
func (s *EmbeddedTypeName) String() string {
return s.ImportPath + "." + s.StructName
}

25
model/method.go Normal file
View File

@@ -0,0 +1,25 @@
package model
// methodCall describes a call to c.Render(..)
// It documents the argument names used, in order to propagate them to RenderArgs.
type MethodCall struct {
Path string // e.g. "myapp/app/controllers.(*Application).Action"
Line int
Names []string
}
// MethodSpec holds the information of one Method
type MethodSpec struct {
Name string // Name of the method, e.g. "Index"
Args []*MethodArg // Argument descriptors
RenderCalls []*MethodCall // Descriptions of Render() invocations from this Method.
}
// MethodArg holds the information of one argument
type MethodArg struct {
Name string // Name of the argument.
TypeExpr TypeExpr // The name of the type, e.g. "int", "*pkg.UserType"
ImportPath string // If the arg is of an imported type, this is the import path.
}

294
model/revel_container.go Normal file
View File

@@ -0,0 +1,294 @@
// This package will be shared between Revel and Revel CLI eventually
package model
import (
"github.com/revel/cmd/utils"
"github.com/revel/config"
"go/build"
"os"
"path/filepath"
"sort"
"strings"
)
const (
// Event type when templates are going to be refreshed (receivers are registered template engines added to the template.engine conf option)
TEMPLATE_REFRESH_REQUESTED = iota
// Event type when templates are refreshed (receivers are registered template engines added to the template.engine conf option)
TEMPLATE_REFRESH_COMPLETED
// Event type before all module loads, events thrown to handlers added to AddInitEventHandler
// Event type before all module loads, events thrown to handlers added to AddInitEventHandler
REVEL_BEFORE_MODULES_LOADED
// Event type called when a new module is found
REVEL_BEFORE_MODULE_LOADED
// Event type called when after a new module is found
REVEL_AFTER_MODULE_LOADED
// Event type after all module loads, events thrown to handlers added to AddInitEventHandler
REVEL_AFTER_MODULES_LOADED
// Event type before server engine is initialized, receivers are active server engine and handlers added to AddInitEventHandler
ENGINE_BEFORE_INITIALIZED
// Event type before server engine is started, receivers are active server engine and handlers added to AddInitEventHandler
ENGINE_STARTED
// Event type after server engine is stopped, receivers are active server engine and handlers added to AddInitEventHandler
ENGINE_SHUTDOWN
// Called before routes are refreshed
ROUTE_REFRESH_REQUESTED
// Called after routes have been refreshed
ROUTE_REFRESH_COMPLETED
)
type (
// The container object for describing all Revels variables
RevelContainer struct {
ImportPath string // The import path
SourcePath string // The full source path
RunMode string // The current run mode
RevelPath string // The path to the Revel source code
BasePath string // The base path to the application
AppPath string // The application path (BasePath + "/app"
ViewsPath string // The application views path
CodePaths []string // All the code paths
TemplatePaths []string // All the template paths
ConfPaths []string // All the configuration paths
Config *config.Context // The global config object
Packaged bool // True if packaged
DevMode bool // True if running in dev mode
HTTPPort int // The http port
HTTPAddr string // The http address
HTTPSsl bool // True if running https
HTTPSslCert string // The SSL certificate
HTTPSslKey string // The SSL key
AppName string // The application name
AppRoot string // The application root from the config `app.root`
CookiePrefix string // The cookie prefix
CookieDomain string // The cookie domain
CookieSecure bool // True if cookie is secure
SecretStr string // The secret string
MimeConfig *config.Context // The mime configuration
ModulePathMap map[string]string // The module path map
}
RevelCallback interface {
FireEvent(key int, value interface{}) (response int)
}
doNothingRevelCallback struct {
}
)
// Simple callback to pass to the RevelCallback that does nothing
var DoNothingRevelCallback = RevelCallback(&doNothingRevelCallback{})
func (_ *doNothingRevelCallback) FireEvent(key int, value interface{}) (response int) {
return
}
// RevelImportPath Revel framework import path
var RevelImportPath = "github.com/revel/revel"
// This function returns a container object describing the revel application
// eventually this type of function will replace the global variables.
func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp *RevelContainer) {
rp = &RevelContainer{ModulePathMap: map[string]string{}}
// Ignore trailing slashes.
rp.ImportPath = strings.TrimRight(importPath, "/")
rp.SourcePath = srcPath
rp.RunMode = mode
// If the SourcePath is not specified, find it using build.Import.
var revelSourcePath string // may be different from the app source path
if rp.SourcePath == "" {
revelSourcePath, rp.SourcePath = findSrcPaths(importPath)
} else {
// If the SourcePath was specified, assume both Revel and the app are within it.
rp.SourcePath = filepath.Clean(rp.SourcePath)
revelSourcePath = rp.SourcePath
}
// Setup paths for application
rp.RevelPath = filepath.Join(revelSourcePath, filepath.FromSlash(RevelImportPath))
rp.BasePath = filepath.Join(rp.SourcePath, filepath.FromSlash(importPath))
rp.AppPath = filepath.Join(rp.BasePath, "app")
rp.ViewsPath = filepath.Join(rp.AppPath, "views")
rp.CodePaths = []string{rp.AppPath}
rp.TemplatePaths = []string{}
if rp.ConfPaths == nil {
rp.ConfPaths = []string{}
}
// Config load order
// 1. framework (revel/conf/*)
// 2. application (conf/*)
// 3. user supplied configs (...) - User configs can override/add any from above
rp.ConfPaths = append(
[]string{
filepath.Join(rp.RevelPath, "conf"),
filepath.Join(rp.BasePath, "conf"),
},
rp.ConfPaths...)
var err error
rp.Config, err = config.LoadContext("app.conf", rp.ConfPaths)
if err != nil {
utils.Logger.Fatal("Unable to load configuartion file ","error", err)
os.Exit(1)
}
// Ensure that the selected runmode appears in app.conf.
// If empty string is passed as the mode, treat it as "DEFAULT"
if mode == "" {
mode = config.DefaultSection
}
if !rp.Config.HasSection(mode) {
utils.Logger.Fatal("app.conf: No mode found:","run-mode", mode)
}
rp.Config.SetSection(mode)
// Configure properties from app.conf
rp.DevMode = rp.Config.BoolDefault("mode.dev", false)
rp.HTTPPort = rp.Config.IntDefault("http.port", 9000)
rp.HTTPAddr = rp.Config.StringDefault("http.addr", "")
rp.HTTPSsl = rp.Config.BoolDefault("http.ssl", false)
rp.HTTPSslCert = rp.Config.StringDefault("http.sslcert", "")
rp.HTTPSslKey = rp.Config.StringDefault("http.sslkey", "")
if rp.HTTPSsl {
if rp.HTTPSslCert == "" {
utils.Logger.Fatal("No http.sslcert provided.")
}
if rp.HTTPSslKey == "" {
utils.Logger.Fatal("No http.sslkey provided.")
}
}
//
rp.AppName = rp.Config.StringDefault("app.name", "(not set)")
rp.AppRoot = rp.Config.StringDefault("app.root", "")
rp.CookiePrefix = rp.Config.StringDefault("cookie.prefix", "REVEL")
rp.CookieDomain = rp.Config.StringDefault("cookie.domain", "")
rp.CookieSecure = rp.Config.BoolDefault("cookie.secure", rp.HTTPSsl)
rp.SecretStr = rp.Config.StringDefault("app.secret", "")
callback.FireEvent(REVEL_BEFORE_MODULES_LOADED, nil)
rp.loadModules(callback)
callback.FireEvent(REVEL_AFTER_MODULES_LOADED, nil)
return
}
// LoadMimeConfig load mime-types.conf on init.
func (rp *RevelContainer) LoadMimeConfig() {
var err error
rp.MimeConfig, err = config.LoadContext("mime-types.conf", rp.ConfPaths)
if err != nil {
utils.Logger.Fatal("Failed to load mime type config:", "error", err)
}
}
// Loads modules based on the configuration setup.
// This will fire the REVEL_BEFORE_MODULE_LOADED, REVEL_AFTER_MODULE_LOADED
// for each module loaded. The callback will receive the RevelContainer, name, moduleImportPath and modulePath
// It will automatically add in the code paths for the module to the
// container object
func (rp *RevelContainer) loadModules(callback RevelCallback) {
keys := []string{}
for _, key := range rp.Config.Options("module.") {
keys = append(keys, key)
}
// Reorder module order by key name, a poor mans sort but at least it is consistent
sort.Strings(keys)
for _, key := range keys {
moduleImportPath := rp.Config.StringDefault(key, "")
if moduleImportPath == "" {
continue
}
modulePath, err := rp.ResolveImportPath(moduleImportPath)
if err != nil {
utils.Logger.Error("Failed to load module. Import of path failed", "modulePath", moduleImportPath, "error", err)
}
// Drop anything between module.???.<name of module>
name := key[len("module."):]
if index := strings.Index(name, "."); index > -1 {
name = name[index+1:]
}
callback.FireEvent(REVEL_BEFORE_MODULE_LOADED, []interface{}{rp, name, moduleImportPath, modulePath})
rp.addModulePaths(name, moduleImportPath, modulePath)
callback.FireEvent(REVEL_AFTER_MODULE_LOADED, []interface{}{rp, name, moduleImportPath, modulePath})
}
}
// Adds a module paths to the container object
func (rp *RevelContainer) addModulePaths(name, importPath, modulePath string) {
if codePath := filepath.Join(modulePath, "app"); utils.DirExists(codePath) {
rp.CodePaths = append(rp.CodePaths, codePath)
rp.ModulePathMap[name] = modulePath
if viewsPath := filepath.Join(modulePath, "app", "views"); utils.DirExists(viewsPath) {
rp.TemplatePaths = append(rp.TemplatePaths, viewsPath)
}
}
// Hack: There is presently no way for the testrunner module to add the
// "test" subdirectory to the CodePaths. So this does it instead.
if importPath == rp.Config.StringDefault("module.testrunner", "github.com/revel/modules/testrunner") {
joinedPath := filepath.Join(rp.BasePath, "tests")
rp.CodePaths = append(rp.CodePaths, joinedPath)
}
if testsPath := filepath.Join(modulePath, "tests"); utils.DirExists(testsPath) {
rp.CodePaths = append(rp.CodePaths, testsPath)
}
}
// ResolveImportPath returns the filesystem path for the given import path.
// Returns an error if the import path could not be found.
func (rp *RevelContainer) ResolveImportPath(importPath string) (string, error) {
if rp.Packaged {
return filepath.Join(rp.SourcePath, importPath), nil
}
modPkg, err := build.Import(importPath, rp.RevelPath, build.FindOnly)
if err != nil {
return "", err
}
return modPkg.Dir, nil
}
// Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory
func findSrcPaths(importPath string) (revelSourcePath, appSourcePath string) {
var (
gopaths = filepath.SplitList(build.Default.GOPATH)
goroot = build.Default.GOROOT
)
if len(gopaths) == 0 {
utils.Logger.Fatalf("GOPATH environment variable is not set. " +
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
}
if utils.ContainsString(gopaths, goroot) {
utils.Logger.Fatalf("GOPATH (%s) must not include your GOROOT (%s). "+
"Please refer to http://golang.org/doc/code.html to configure your Go environment.",
gopaths, goroot)
}
appPkg, err := build.Import(importPath, "", build.FindOnly)
if err != nil {
utils.Logger.Fatal("Failed to import "+importPath+" with error:", "error", err)
}
revelPkg, err := build.Import(RevelImportPath, appPkg.Dir, build.FindOnly)
if err != nil {
utils.Logger.Fatal("Failed to find Revel with error:", "error", err)
}
revelSourcePath, appSourcePath = revelPkg.Dir[:len(revelPkg.Dir)-len(RevelImportPath)], appPkg.SrcRoot
return
}

125
model/source_info.go Normal file
View File

@@ -0,0 +1,125 @@
package model
// SourceInfo is the top-level struct containing all extracted information
// about the app source code, used to generate main.go.
import (
"github.com/revel/cmd/utils"
"path/filepath"
"strings"
"unicode"
)
type SourceInfo struct {
// StructSpecs lists type info for all structs found under the code paths.
// They may be queried to determine which ones (transitively) embed certain types.
StructSpecs []*TypeInfo
// ValidationKeys provides a two-level lookup. The keys are:
// 1. The fully-qualified function name,
// e.g. "github.com/revel/examples/chat/app/controllers.(*Application).Action"
// 2. Within that func's file, the line number of the (overall) expression statement.
// e.g. the line returned from runtime.Caller()
// The result of the lookup the name of variable being validated.
ValidationKeys map[string]map[int]string
// A list of import paths.
// Revel notices files with an init() function and imports that package.
InitImportPaths []string
// controllerSpecs lists type info for all structs found under
// app/controllers/... that embed (directly or indirectly) revel.Controller
controllerSpecs []*TypeInfo
// testSuites list the types that constitute the set of application tests.
testSuites []*TypeInfo
}
// TypesThatEmbed returns all types that (directly or indirectly) embed the
// target type, which must be a fully qualified type name,
// e.g. "github.com/revel/revel.Controller"
func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered []*TypeInfo) {
// Do a search in the "embedded type graph", starting with the target type.
var (
nodeQueue = []string{targetType}
processed []string
)
for len(nodeQueue) > 0 {
typeSimpleName := nodeQueue[0]
nodeQueue = nodeQueue[1:]
processed = append(processed, typeSimpleName)
// Look through all known structs.
for _, spec := range s.StructSpecs {
// If this one has been processed or is already in nodeQueue, then skip it.
if utils.ContainsString(processed, spec.String()) ||
utils.ContainsString(nodeQueue, spec.String()) {
continue
}
// Look through the embedded types to see if the current type is among them.
for _, embeddedType := range spec.EmbeddedTypes {
// If so, add this type's simple name to the nodeQueue, and its spec to
// the filtered list.
if typeSimpleName == embeddedType.String() {
nodeQueue = append(nodeQueue, spec.String())
filtered = append(filtered, spec)
break
}
}
}
}
// Strip out any specifications that contain a lower case
for exit := false; !exit; exit = true {
for i, filteredItem := range filtered {
if unicode.IsLower([]rune(filteredItem.StructName)[0]) {
utils.Logger.Info("Debug: Skipping adding spec for unexported type",
"type", filteredItem.StructName,
"package", filteredItem.ImportPath)
filtered = append(filtered[:i], filtered[i+1:]...)
exit = false
break
}
}
}
// Check for any missed types that where from expected packages
for _, spec := range s.StructSpecs {
if spec.PackageName == packageFilter {
found := false
unfoundNames := ""
for _, filteredItem := range filtered {
if filteredItem.StructName == spec.StructName {
found = true
break
} else {
unfoundNames += filteredItem.StructName + ","
}
}
// Report non controller structures in controller folder.
if !found && !strings.HasPrefix(spec.StructName, "Test") {
utils.Logger.Warn("Type found in package: "+packageFilter+
", but did not embed from: "+filepath.Base(targetType),
"name", spec.StructName, "importpath", spec.ImportPath, "foundstructures", unfoundNames)
}
}
}
return
}
// ControllerSpecs returns the all the controllers that embeds
// `revel.Controller`
func (s *SourceInfo) ControllerSpecs() []*TypeInfo {
if s.controllerSpecs == nil {
s.controllerSpecs = s.TypesThatEmbed(RevelImportPath+".Controller", "controllers")
}
return s.controllerSpecs
}
// TestSuites returns the all the Application tests that embeds
// `testing.TestSuite`
func (s *SourceInfo) TestSuites() []*TypeInfo {
if s.testSuites == nil {
s.testSuites = s.TypesThatEmbed(RevelImportPath+"/testing.TestSuite", "testsuite")
}
return s.testSuites
}

102
model/type_expr.go Normal file
View File

@@ -0,0 +1,102 @@
package model
// TypeExpr provides a type name that may be rewritten to use a package name.
import (
"fmt"
"go/ast"
)
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
}
// Returns a new type from the data
func NewTypeExprFromData(expr, pkgName string, pkgIndex int, valid bool) TypeExpr {
return TypeExpr{expr, pkgName, pkgIndex, valid}
}
// NewTypeExpr returns the syntactic expression for referencing this type in Go.
func NewTypeExprFromAst(pkgName string, expr ast.Expr) TypeExpr {
error := ""
switch t := expr.(type) {
case *ast.Ident:
if IsBuiltinType(t.Name) {
pkgName = ""
}
return TypeExpr{t.Name, pkgName, 0, true}
case *ast.SelectorExpr:
e := NewTypeExprFromAst(pkgName, t.X)
return NewTypeExprFromData(t.Sel.Name, e.Expr, 0, e.Valid)
case *ast.StarExpr:
e := NewTypeExprFromAst(pkgName, t.X)
return NewTypeExprFromData("*"+e.Expr, e.PkgName, e.pkgIndex+1, e.Valid)
case *ast.ArrayType:
e := NewTypeExprFromAst(pkgName, t.Elt)
return NewTypeExprFromData("[]"+e.Expr, e.PkgName, e.pkgIndex+2, e.Valid)
case *ast.MapType:
if identKey, ok := t.Key.(*ast.Ident); ok && IsBuiltinType(identKey.Name) {
e := NewTypeExprFromAst(pkgName, t.Value)
return NewTypeExprFromData("map["+identKey.Name+"]"+e.Expr, e.PkgName, e.pkgIndex+len("map["+identKey.Name+"]"), e.Valid)
}
error = fmt.Sprintf("Failed to generate name for Map field :%v. Make sure the field name is valid.", t.Key)
case *ast.Ellipsis:
e := NewTypeExprFromAst(pkgName, t.Elt)
return NewTypeExprFromData("[]"+e.Expr, e.PkgName, e.pkgIndex+2, e.Valid)
default:
error = fmt.Sprintf("Failed to generate name for field: %v Package: %v. Make sure the field name is valid.", expr, pkgName)
}
return NewTypeExprFromData(error, "", 0, false)
}
// TypeName returns the fully-qualified type name for this expression.
// The caller may optionally specify a package name to override the default.
func (e TypeExpr) TypeName(pkgOverride string) string {
pkgName := FirstNonEmpty(pkgOverride, e.PkgName)
if pkgName == "" {
return e.Expr
}
return e.Expr[:e.pkgIndex] + pkgName + "." + e.Expr[e.pkgIndex:]
}
var builtInTypes = map[string]struct{}{
"bool": {},
"byte": {},
"complex128": {},
"complex64": {},
"error": {},
"float32": {},
"float64": {},
"int": {},
"int16": {},
"int32": {},
"int64": {},
"int8": {},
"rune": {},
"string": {},
"uint": {},
"uint16": {},
"uint32": {},
"uint64": {},
"uint8": {},
"uintptr": {},
}
// IsBuiltinType checks the given type is built-in types of Go
func IsBuiltinType(name string) bool {
_, ok := builtInTypes[name]
return ok
}
// Returns the first non empty string from a list of arguements
func FirstNonEmpty(strs ...string) string {
for _, str := range strs {
if len(str) > 0 {
return str
}
}
return ""
}

16
model/type_info.go Normal file
View File

@@ -0,0 +1,16 @@
package model
// TypeInfo summarizes information about a struct type in the app source code.
type TypeInfo struct {
StructName string // e.g. "Application"
ImportPath string // e.g. "github.com/revel/examples/chat/app/controllers"
PackageName string // e.g. "controllers"
MethodSpecs []*MethodSpec // Method specifications, the action functions
EmbeddedTypes []*EmbeddedTypeName // Used internally to identify controllers that indirectly embed *revel.Controller.
}
// Return the type information as a properly formatted import string
func (s *TypeInfo) String() string {
return s.ImportPath + "." + s.StructName
}

View File

@@ -2,7 +2,7 @@
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package harness
package parser
// This file handles the app code introspection.
// It catalogs the controllers, their methods, and their arguments.
@@ -13,99 +13,38 @@ import (
"go/parser"
"go/scanner"
"go/token"
"log"
"os"
"path/filepath"
"strings"
"github.com/revel/revel"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
)
// 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
type methodMap map[string][]*model.MethodSpec
// ProcessSource parses the app controllers directory and
// returns a list of the controller types found.
// Otherwise CompileError if the parsing fails.
func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
func ProcessSource(paths *model.RevelContainer) (*model.SourceInfo, *utils.Error) {
var (
srcInfo *SourceInfo
compileError *revel.Error
srcInfo *model.SourceInfo
compileError *utils.Error
)
for _, root := range roots {
for _, root := range paths.CodePaths {
rootImportPath := importPathFromPath(root)
if rootImportPath == "" {
revel.WARN.Println("Skipping code path", root)
utils.Logger.Info("Skipping empty code path", "path", root)
continue
}
// Start walking the directory tree.
_ = revel.Walk(root, func(path string, info os.FileInfo, err error) error {
_ = utils.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Println("Error scanning app source:", err)
utils.Logger.Error("Error scanning app source:", "error", err)
return nil
}
@@ -128,17 +67,17 @@ func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
if err != nil {
if errList, ok := err.(scanner.ErrorList); ok {
var pos = errList[0].Pos
compileError = &revel.Error{
compileError = &utils.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),
SourceLines: utils.MustReadLines(pos.Filename),
}
errorLink := revel.Config.StringDefault("error.link", "")
errorLink := paths.Config.StringDefault("error.link", "")
if errorLink != "" {
compileError.SetLink(errorLink)
@@ -147,14 +86,24 @@ func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
return compileError
}
// This is exception, err alredy checked above. Here just a print
// This is exception, err already checked above. Here just a print
ast.Print(nil, err)
log.Fatalf("Failed to parse dir: %s", err)
utils.Logger.Fatal("Failed to parse dir", "error", err)
}
// Skip "main" packages.
delete(pkgs, "main")
// Ignore packages that end with _test
// These cannot be included in source code that is not generated specifically as a test
for i := range pkgs {
if len(i) > 6 {
if string(i[len(i)-5:]) == "_test" {
delete(pkgs, i)
}
}
}
// If there is no code in this directory, skip it.
if len(pkgs) == 0 {
return nil
@@ -162,15 +111,23 @@ func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
// There should be only one package in this directory.
if len(pkgs) > 1 {
log.Println("Most unexpected! Multiple packages in a single directory:", pkgs)
for i := range pkgs {
println("Found package ", i)
}
utils.Logger.Fatal("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))
if pkg != nil {
srcInfo = appendSourceInfo(srcInfo, processPackage(fset, pkgImportPath, path, pkg))
} else {
utils.Logger.Info("Ignoring package, because it contained no packages", "path", path)
}
return nil
})
}
@@ -178,7 +135,7 @@ func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
return srcInfo, compileError
}
func appendSourceInfo(srcInfo1, srcInfo2 *SourceInfo) *SourceInfo {
func appendSourceInfo(srcInfo1, srcInfo2 *model.SourceInfo) *model.SourceInfo {
if srcInfo1 == nil {
return srcInfo2
}
@@ -187,7 +144,7 @@ func appendSourceInfo(srcInfo1, srcInfo2 *SourceInfo) *SourceInfo {
srcInfo1.InitImportPaths = append(srcInfo1.InitImportPaths, srcInfo2.InitImportPaths...)
for k, v := range srcInfo2.ValidationKeys {
if _, ok := srcInfo1.ValidationKeys[k]; ok {
log.Println("Key conflict when scanning validation calls:", k)
utils.Logger.Warn("Warn: Key conflict when scanning validation calls:", "key", k)
continue
}
srcInfo1.ValidationKeys[k] = v
@@ -195,9 +152,9 @@ func appendSourceInfo(srcInfo1, srcInfo2 *SourceInfo) *SourceInfo {
return srcInfo1
}
func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast.Package) *SourceInfo {
func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast.Package) *model.SourceInfo {
var (
structSpecs []*TypeInfo
structSpecs []*model.TypeInfo
initImportPaths []string
methodSpecs = make(methodMap)
@@ -209,8 +166,8 @@ func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast
)
// For each source file in the package...
for _, file := range pkg.Files {
utils.Logger.Info("Exaimining files in path", "package", pkgPath)
for fname, 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{}
@@ -221,16 +178,16 @@ func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast
if scanControllers {
// Match and add both structs and methods
structSpecs = appendStruct(structSpecs, pkgImportPath, pkg, decl, imports, fset)
structSpecs = appendStruct(fname, structSpecs, pkgImportPath, pkg, decl, imports, fset)
appendAction(fset, methodSpecs, decl, pkgImportPath, pkg.Name, imports)
} else if scanTests {
structSpecs = appendStruct(structSpecs, pkgImportPath, pkg, decl, imports, fset)
structSpecs = appendStruct(fname, structSpecs, pkgImportPath, pkg, decl, imports, fset)
}
// If this is a func...
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
// If this is a func... (ignore nil for external (non-Go) function)
if funcDecl, ok := decl.(*ast.FuncDecl); ok && funcDecl.Body != nil {
// Scan it for validation calls
lineKeys := getValidationKeys(fset, funcDecl, imports)
lineKeys := GetValidationKeys(fname, fset, funcDecl, imports)
if len(lineKeys) > 0 {
validationKeys[pkgImportPath+"."+getFuncName(funcDecl)] = lineKeys
}
@@ -248,7 +205,7 @@ func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast
spec.MethodSpecs = methodSpecs[spec.StructName]
}
return &SourceInfo{
return &model.SourceInfo{
StructSpecs: structSpecs,
ValidationKeys: validationKeys,
InitImportPaths: initImportPaths,
@@ -305,7 +262,7 @@ func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
// We expect this to happen for apps using reverse routing (since we
// have not yet generated the routes). Don't log that.
if !strings.HasSuffix(fullPath, "/app/routes") {
revel.TRACE.Println("Could not find import:", fullPath)
utils.Logger.Info("Debug: Could not find import:", "path", fullPath)
}
continue
}
@@ -318,18 +275,19 @@ func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
// If this Decl is a struct type definition, it is summarized and added to specs.
// Else, specs is returned unchanged.
func appendStruct(specs []*TypeInfo, pkgImportPath string, pkg *ast.Package, decl ast.Decl, imports map[string]string, fset *token.FileSet) []*TypeInfo {
func appendStruct(fileName string, specs []*model.TypeInfo, pkgImportPath string, pkg *ast.Package, decl ast.Decl, imports map[string]string, fset *token.FileSet) []*model.TypeInfo {
// Filter out non-Struct type declarations.
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{
controllerSpec := &model.TypeInfo{
StructName: spec.Name.Name,
ImportPath: pkgImportPath,
PackageName: pkg.Name,
@@ -383,12 +341,12 @@ func appendStruct(specs []*TypeInfo, pkgImportPath string, pkg *ast.Package, dec
} else {
var ok bool
if importPath, ok = imports[pkgName]; !ok {
log.Print("Failed to find import path for ", pkgName, ".", typeName)
utils.Logger.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName)
continue
}
}
controllerSpec.embeddedTypes = append(controllerSpec.embeddedTypes, &embeddedTypeName{
controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{
ImportPath: importPath,
StructName: typeName,
})
@@ -428,11 +386,11 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
if selExpr.Sel.Name != "Result" {
return
}
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != revel.RevelImportPath {
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != model.RevelImportPath {
return
}
method := &MethodSpec{
method := &model.MethodSpec{
Name: funcDecl.Name.Name,
}
@@ -440,18 +398,21 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
for _, field := range funcDecl.Type.Params.List {
for _, name := range field.Names {
var importPath string
typeExpr := NewTypeExpr(pkgName, field.Type)
typeExpr := model.NewTypeExprFromAst(pkgName, field.Type)
if !typeExpr.Valid {
log.Printf("Didn't understand argument '%s' of action %s. Ignoring.\n", name, getFuncName(funcDecl))
utils.Logger.Warn("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, getFuncName(funcDecl))
return // We didn't understand one of the args. Ignore this action.
}
if typeExpr.PkgName != "" {
// Local object
if typeExpr.PkgName == pkgName {
importPath = pkgImportPath
} else if typeExpr.PkgName != "" {
var ok bool
if importPath, ok = imports[typeExpr.PkgName]; !ok {
log.Println("Failed to find import for arg of type:", typeExpr.TypeName(""))
utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
}
}
method.Args = append(method.Args, &MethodArg{
method.Args = append(method.Args, &model.MethodArg{
Name: name.Name,
TypeExpr: typeExpr,
ImportPath: importPath,
@@ -461,7 +422,7 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
// Add a description of the calls to Render from the method.
// Inspect every node (e.g. always return true).
method.RenderCalls = []*methodCall{}
method.RenderCalls = []*model.MethodCall{}
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
// Is it a function call?
callExpr, ok := node.(*ast.CallExpr)
@@ -482,8 +443,8 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
}
// Add this call's args to the renderArgs.
pos := fset.Position(callExpr.Rparen)
methodCall := &methodCall{
pos := fset.Position(callExpr.Lparen)
methodCall := &model.MethodCall{
Line: pos.Line,
Names: []string{},
}
@@ -523,7 +484,7 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
//
// The end result is that we can set the default validation key for each call to
// be the same as the local variable.
func getValidationKeys(fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[string]string) map[int]string {
func GetValidationKeys(fname string, fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[string]string) map[int]string {
var (
lineKeys = make(map[int]string)
@@ -579,8 +540,11 @@ func getValidationKeys(fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[
return true
}
if typeExpr := NewTypeExpr("", key); typeExpr.Valid {
if typeExpr := model.NewTypeExprFromAst("", key); typeExpr.Valid {
lineKeys[fset.Position(callExpr.End()).Line] = typeExpr.TypeName("")
} else {
utils.Logger.Error("Error: Failed to generate key for field validation. Make sure the field name is valid.", "file", fname,
"line", fset.Position(callExpr.End()).Line, "function", funcDecl.Name.String())
}
return true
})
@@ -606,21 +570,13 @@ func getValidationParameter(funcDecl *ast.FuncDecl, imports map[string]string) *
continue
}
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == revel.RevelImportPath {
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == model.RevelImportPath {
return field.Names[0].Obj
}
}
return 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) {
@@ -634,7 +590,7 @@ func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec,
}
if len(genDecl.Specs) == 0 {
revel.WARN.Printf("Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
utils.Logger.Warn("Warn: Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
return
}
@@ -644,139 +600,9 @@ func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec,
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 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
}
}
}
}
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")
}
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")
}
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:
log.Println("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):])
if vendorIdx := strings.Index(root, "/vendor/"); vendorIdx != -1 {
return filepath.ToSlash(root[vendorIdx+8:])
}
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
srcPath := filepath.Join(gopath, "src")
@@ -787,10 +613,10 @@ func importPathFromPath(root string) string {
srcPath := filepath.Join(build.Default.GOROOT, "src", "pkg")
if strings.HasPrefix(root, srcPath) {
revel.WARN.Println("Code path should be in GOPATH, but is in GOROOT:", root)
utils.Logger.Warn("Code path should be in GOPATH, but is in GOROOT:", "path", root)
return filepath.ToSlash(root[len(srcPath)+1:])
}
revel.ERROR.Println("Unexpected! Code path is not in GOPATH:", root)
utils.Logger.Error("Unexpected! Code path is not in GOPATH:", "path", root)
return ""
}

View File

@@ -2,19 +2,18 @@
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package harness
package parser_test
import (
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"log"
"reflect"
"strings"
"testing"
"github.com/revel/revel"
"github.com/revel/cmd/model"
revelParser "github.com/revel/cmd/parser"
)
const validationKeysSource = `
@@ -82,7 +81,7 @@ func TestGetValidationKeys(t *testing.T) {
}
for i, decl := range file.Decls {
lineKeys := getValidationKeys(fset, decl.(*ast.FuncDecl), map[string]string{"revel": revel.RevelImportPath})
lineKeys := revelParser.GetValidationKeys("test", fset, decl.(*ast.FuncDecl), map[string]string{"revel": model.RevelImportPath})
for k, v := range expectedValidationKeys[i] {
if lineKeys[k] != v {
t.Errorf("Not found - %d: %v - Actual Map: %v", k, v, lineKeys)
@@ -95,19 +94,21 @@ func TestGetValidationKeys(t *testing.T) {
}
}
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},
var TypeExprs = map[string]model.TypeExpr{
"int": model.NewTypeExprFromData("int", "", 0, true),
"*int": model.NewTypeExprFromData("*int", "", 1, true),
"[]int": model.NewTypeExprFromData("[]int", "", 2, true),
"...int": model.NewTypeExprFromData("[]int", "", 2, true),
"[]*int": model.NewTypeExprFromData("[]*int", "", 3, true),
"...*int": model.NewTypeExprFromData("[]*int", "", 3, true),
"MyType": model.NewTypeExprFromData("MyType", "pkg", 0, true),
"*MyType": model.NewTypeExprFromData("*MyType", "pkg", 1, true),
"[]MyType": model.NewTypeExprFromData("[]MyType", "pkg", 2, true),
"...MyType": model.NewTypeExprFromData("[]MyType", "pkg", 2, true),
"[]*MyType": model.NewTypeExprFromData("[]*MyType", "pkg", 3, true),
"...*MyType": model.NewTypeExprFromData("[]*MyType", "pkg", 3, true),
"map[int]MyType": model.NewTypeExprFromData("map[int]MyType", "pkg", 8, true),
"map[int]*MyType": model.NewTypeExprFromData("map[int]*MyType", "pkg", 9, true),
}
func TestTypeExpr(t *testing.T) {
@@ -136,60 +137,9 @@ func TestTypeExpr(t *testing.T) {
expr = &ast.Ellipsis{Ellipsis: expr.Pos(), Elt: expr}
}
actual := NewTypeExpr("pkg", expr)
actual := model.NewTypeExprFromAst("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.TRACE = log.New(ioutil.Discard, "", 0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := ProcessSource(revel.CodePaths)
if err != nil {
b.Error("Unexpected error:", err)
}
}
}

13
proxy/proxy.go Normal file
View File

@@ -0,0 +1,13 @@
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
// Package proxy for a Revel Framework.
//
// It has a following responsibilities:
// 1. Build and run the users program in a proxy
// 2. Monitor the user source and restart the program when necessary.
//
// Source files are generated in the app/tmp directory.
package proxy

View File

@@ -5,70 +5,85 @@
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"fmt"
"github.com/revel/cmd/harness"
"github.com/revel/revel"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"go/build"
)
var cmdBuild = &Command{
UsageLine: "build [import path] [target path] [run mode]",
UsageLine: "build -i [import path] -t [target path] -r [run mode]",
Short: "build a Revel application (e.g. for deployment)",
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
revel build -a github.com/revel/examples/chat -t /tmp/chat
`,
}
func init() {
cmdBuild.Run = buildApp
cmdBuild.RunWith = buildApp
cmdBuild.UpdateConfig = updateBuildConfig
}
func buildApp(args []string) {
// The update config updates the configuration command so that it can run
func updateBuildConfig(c *model.CommandConfig, args []string) bool {
c.Index = model.BUILD
if len(args) < 2 {
fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long)
return
return false
}
c.Build.ImportPath = args[0]
c.Build.TargetPath = args[1]
if len(args) > 2 {
c.Build.Mode = args[2]
}
return true
}
// The main entry point to build application from command line
func buildApp(c *model.CommandConfig) {
appImportPath, destPath, mode := c.ImportPath, c.Build.TargetPath, DefaultRunMode
if len(c.Build.Mode) > 0 {
mode = c.Build.Mode
}
appImportPath, destPath, mode := args[0], args[1], DefaultRunMode
if len(args) >= 3 {
mode = args[2]
}
// Convert target to absolute path
destPath, _ = filepath.Abs(destPath)
if !revel.Initialized {
revel.Init(mode, appImportPath, "")
}
revel_paths := model.NewRevelPaths(mode, appImportPath, "", model.DoNothingRevelCallback)
// First, verify that it is either already empty or looks like a previous
// build (to avoid clobbering anything)
if exists(destPath) && !empty(destPath) && !exists(filepath.Join(destPath, "run.sh")) {
errorf("Abort: %s exists and does not look like a build directory.", destPath)
if utils.Exists(destPath) && !utils.Empty(destPath) && !utils.Exists(filepath.Join(destPath, "run.sh")) {
utils.Logger.Errorf("Abort: %s exists and does not look like a build directory.", destPath)
return
}
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
revel.ERROR.Fatalln(err)
utils.Logger.Error("Remove all error", "error", err)
return
}
if err := os.MkdirAll(destPath, 0777); err != nil {
revel.ERROR.Fatalln(err)
utils.Logger.Error("makedir error", "error", err)
return
}
app, reverr := harness.Build()
panicOnError(reverr, "Failed to build")
app, reverr := harness.Build(c, revel_paths)
if reverr != nil {
utils.Logger.Error("Failed to build application", "error", reverr)
return
}
// Included are:
// - run scripts
@@ -79,17 +94,43 @@ func buildApp(args []string) {
// 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)
tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(model.RevelImportPath))
utils.MustCopyFile(destBinaryPath, app.BinaryPath)
utils.MustChmod(destBinaryPath, 0755)
// Copy the templates from the revel
_ = utils.MustCopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revel_paths.RevelPath, "conf"), nil)
_ = utils.MustCopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revel_paths.RevelPath, "templates"), nil)
// Get the folders to be packaged
packageFolders := strings.Split(revel_paths.Config.StringDefault("package.folders", "conf,public,app/views"), ",")
for i,p:=range packageFolders {
// Clean spaces, reformat slash to filesystem
packageFolders[i]=filepath.FromSlash(strings.TrimSpace(p))
}
if c.Build.CopySource {
_ = utils.MustCopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revel_paths.BasePath, nil)
} else {
for _, folder := range packageFolders {
_ = utils.MustCopyDir(
filepath.Join(srcPath, filepath.FromSlash(appImportPath), folder),
filepath.Join(revel_paths.BasePath, folder),
nil)
}
}
// Find all the modules used and copy them over.
config := revel.Config.Raw()
config := revel_paths.Config.Raw()
modulePaths := make(map[string]string) // import path => filesystem path
// We should only copy over the section of options what the build is targeted for
// We will default to prod
for _, section := range config.Sections() {
// If the runmode is defined we will only import modules defined for that run mode
if c.Build.Mode != "" && c.Build.Mode != section {
continue
}
options, _ := config.SectionOptions(section)
for _, key := range options {
if !strings.HasPrefix(key, "module.") {
@@ -99,32 +140,59 @@ func buildApp(args []string) {
if moduleImportPath == "" {
continue
}
modulePath, err := revel.ResolveImportPath(moduleImportPath)
modPkg, err := build.Import(moduleImportPath, revel_paths.RevelPath, build.FindOnly)
if err != nil {
revel.ERROR.Fatalln("Failed to load module %s: %s", key[len("module."):], err)
utils.Logger.Fatalf("Failed to load module %s (%s): %s", key[len("module."):], c.ImportPath, err)
}
modulePaths[moduleImportPath] = modulePath
modulePaths[moduleImportPath] = modPkg.Dir
}
}
// Copy the the paths for each of the modules
for importPath, fsPath := range modulePaths {
_ = mustCopyDir(filepath.Join(srcPath, importPath), fsPath, nil)
utils.Logger.Info("Copy files ", "to", filepath.Join(srcPath, importPath), "from", fsPath)
if c.Build.CopySource {
_ = utils.MustCopyDir(filepath.Join(srcPath, importPath), fsPath, nil)
} else {
for _, folder := range packageFolders {
_ = utils.MustCopyDir(
filepath.Join(srcPath, importPath, folder),
filepath.Join(fsPath, folder),
nil)
}
}
//
}
tmplData, runShPath := map[string]interface{}{
tmplData := 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(
utils.MustGenerateTemplate(
filepath.Join(destPath, "run.sh"),
PACKAGE_RUN_SH,
tmplData,
)
utils.MustChmod(filepath.Join(destPath, "run.sh"), 0755)
utils.MustGenerateTemplate(
filepath.Join(destPath, "run.bat"),
filepath.Join(revel.RevelPath, "..", "cmd", "revel", "package_run.bat.template"),
tmplData)
PACKAGE_RUN_BAT,
tmplData,
)
fmt.Println("Your application has been built in:", destPath)
}
const PACKAGE_RUN_SH = `#!/bin/sh
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}
`
const PACKAGE_RUN_BAT = `@echo off
{{.BinName}} -importPath {{.ImportPath}} -srcPath "%CD%\src" -runMode {{.Mode}}
`

View File

@@ -6,39 +6,50 @@ package main
import (
"fmt"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"go/build"
"os"
"path/filepath"
)
var cmdClean = &Command{
UsageLine: "clean [import path]",
UsageLine: "clean -i [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
revel clean -a github.com/revel/examples/chat
It removes the app/tmp and app/routes directory.
`,
}
func init() {
cmdClean.Run = cleanApp
cmdClean.UpdateConfig = updateCleanConfig
cmdClean.RunWith = cleanApp
}
func cleanApp(args []string) {
// Update the clean command configuration, using old method
func updateCleanConfig(c *model.CommandConfig, args []string) bool {
c.Index = model.CLEAN
if len(args) == 0 {
fmt.Fprintf(os.Stderr, cmdClean.Long)
return
return false
}
c.Clean.ImportPath = args[0]
return true
}
appPkg, err := build.Import(args[0], "", build.FindOnly)
// Clean the source directory of generated files
func cleanApp(c *model.CommandConfig) {
appPkg, err := build.Import(c.ImportPath, "", build.FindOnly)
if err != nil {
fmt.Fprintln(os.Stderr, "Abort: Failed to find import path:", err)
return
utils.Logger.Fatal("Abort: Failed to find import path:", "error", err)
}
purgeDirs := []string{
@@ -50,7 +61,7 @@ func cleanApp(args []string) {
fmt.Println("Removing:", dir)
err = os.RemoveAll(dir)
if err != nil {
fmt.Fprintln(os.Stderr, "Abort:", err)
utils.Logger.Error("Failed to clean dir", "error", err)
return
}
}

View File

@@ -8,18 +8,18 @@ import (
"bytes"
"fmt"
"go/build"
"log"
"math/rand"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/revel/revel"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
)
var cmdNew = &Command{
UsageLine: "new [path] [skeleton]",
UsageLine: "new -i [path] -s [skeleton]",
Short: "create a skeleton Revel application",
Long: `
New creates a few files to get a new Revel application running quickly.
@@ -31,63 +31,109 @@ Skeleton is an optional argument, provided as an import path
For example:
revel new import/path/helloworld
revel new -a import/path/helloworld
revel new -a import/path/helloworld -s import/path/skeleton
revel new import/path/helloworld import/path/skeleton
`,
}
func init() {
cmdNew.Run = newApp
cmdNew.RunWith = newApp
cmdNew.UpdateConfig = updateNewConfig
}
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
// Called when unable to parse the command line automatically and assumes an old launch
func updateNewConfig(c *model.CommandConfig, args []string) bool {
c.Index = model.NEW
if len(args) == 0 {
errorf("No import path given.\nRun 'revel help new' for usage.\n")
fmt.Fprintf(os.Stderr, cmdNew.Long)
return false
}
if len(args) > 2 {
errorf("Too many arguments provided.\nRun 'revel help new' for usage.\n")
c.New.ImportPath = args[0]
if len(args)>1 {
c.New.Skeleton = args[1]
}
return true
}
// Call to create a new application
func newApp(c *model.CommandConfig) {
// check for proper args by count
c.SkeletonPath = c.New.Skeleton
// Check for an existing folder so we dont clober it
c.AppPath = filepath.Join(c.SrcRoot, filepath.FromSlash(c.ImportPath))
_, err := build.Import(c.ImportPath, "", build.FindOnly)
if err==nil || !utils.Empty(c.AppPath) {
utils.Logger.Fatal("Abort: Import path already exists.","path", c.ImportPath)
}
revel.ERROR.SetFlags(log.LstdFlags)
if c.New.Vendored {
depPath, err := exec.LookPath("dep")
if err != nil {
// Do not halt build unless a new package needs to be imported
utils.Logger.Fatal("New: `dep` executable not found in PATH, but vendor folder requested." +
"You must install the dep tool before creating a vendored project. " +
"You can install the `dep` tool by doing a `go get -u github.com/golang/dep/cmd/dep`")
}
vendorPath := filepath.Join(c.ImportPath,"vendor")
if !utils.DirExists(vendorPath) {
err := os.MkdirAll(vendorPath,os.ModePerm)
utils.PanicOnError(err, "Failed to create " + vendorPath)
}
// In order for dep to run there needs to be a source file in the folder
tempPath := filepath.Join(c.ImportPath,"tmp")
if !utils.DirExists(tempPath) {
err := os.MkdirAll(tempPath,os.ModePerm)
utils.PanicOnError(err, "Failed to create " + vendorPath)
err = utils.MustGenerateTemplate(filepath.Join(tempPath,"main.go"), NEW_MAIN_FILE, nil)
utils.PanicOnError(err, "Failed to create main file " + vendorPath)
}
packageFile := filepath.Join(c.ImportPath,"Gopkg.toml")
if !utils.Exists(packageFile) {
utils.MustGenerateTemplate(packageFile,VENDOR_GOPKG,nil)
} else {
utils.Logger.Info("Package file exists in skeleto, skipping adding")
}
getCmd := exec.Command(depPath, "ensure", "-v")
getCmd.Dir = c.ImportPath
utils.Logger.Info("Exec:", "args", getCmd.Args)
getCmd.Dir = c.ImportPath
getOutput, err := getCmd.CombinedOutput()
if err != nil {
utils.Logger.Fatal(string(getOutput))
}
}
// checking and setting go paths
initGoPaths()
// checking and setting application
setApplicationPath(args)
setApplicationPath(c)
// checking and setting skeleton
setSkeletonPath(args)
setSkeletonPath(c)
// copy files to new app directory
copyNewAppFiles()
copyNewAppFiles(c)
// 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)
fmt.Fprintln(os.Stdout, "Your application is ready:\n ", c.AppPath)
// Check to see if it should be run right off
if c.New.Run {
runApp(c)
} else {
fmt.Fprintln(os.Stdout, "\nYou can run it with:\n revel run -a ", c.ImportPath)
}
}
// Used to generate a new secret key
const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
// Generate a secret key
func generateSecret() string {
chars := make([]byte, 64)
for i := 0; i < 64; i++ {
@@ -96,128 +142,161 @@ func generateSecret() string {
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]
// Sets the applicaiton path
func setApplicationPath(c *model.CommandConfig) {
// 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)
if filepath.IsAbs(c.ImportPath) || strings.HasPrefix(c.ImportPath, ".") {
utils.Logger.Fatalf("Abort: '%s' looks like a directory. Please provide a Go import path instead.",
c.ImportPath)
}
_, err = build.Import(importPath, "", build.FindOnly)
if err == nil {
errorf("Abort: Import path %s already exists.\n", importPath)
// If we are running a vendored version of Revel we do not need to check for it.
if !c.New.Vendored {
var err error
_, err = build.Import(model.RevelImportPath, "", build.FindOnly)
if err != nil {
// Go get the revel project
getCmd := exec.Command(c.GoCmd, "get", model.RevelImportPath)
utils.Logger.Info("Exec:" + c.GoCmd, "args", getCmd.Args)
getOutput, err := getCmd.CombinedOutput()
if err != nil {
utils.Logger.Fatal("Failed to fetch revel " + model.RevelImportPath, "getOutput", string(getOutput))
}
}
}
revelPkg, err = build.Import(revel.RevelImportPath, "", build.FindOnly)
if err != nil {
errorf("Abort: Could not find Revel source code: %s\n", err)
}
c.AppName = filepath.Base(c.AppPath)
c.BasePath = filepath.ToSlash(filepath.Dir(c.ImportPath))
appPath = filepath.Join(srcRoot, filepath.FromSlash(importPath))
appName = filepath.Base(appPath)
basePath = filepath.ToSlash(filepath.Dir(importPath))
if basePath == "." {
if c.BasePath == "." {
// we need to remove the a single '.' when
// the app is in the $GOROOT/src directory
basePath = ""
c.BasePath = ""
} else {
// we need to append a '/' when the app is
// is a subdirectory such as $GOROOT/src/path/to/revelapp
basePath += "/"
c.BasePath += "/"
}
}
func setSkeletonPath(args []string) {
// Set the skeleton path
func setSkeletonPath(c *model.CommandConfig) {
var err error
if len(args) == 2 { // user specified
skeletonName := args[1]
_, err = build.Import(skeletonName, "", build.FindOnly)
if len(c.SkeletonPath) > 0 { // user specified
_, err = build.Import(c.SkeletonPath, "", build.FindOnly)
if err != nil {
// Execute "go get <pkg>"
getCmd := exec.Command(gocmd, "get", "-d", skeletonName)
getCmd := exec.Command(c.GoCmd, "get", "-d", c.SkeletonPath)
fmt.Println("Exec:", getCmd.Args)
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)
utils.Logger.Fatalf("Abort: Could not find or 'go get' Skeleton source code: %s\n%s\n", getOutput, c.SkeletonPath)
}
}
// use the
skeletonPath = filepath.Join(srcRoot, skeletonName)
c.SkeletonPath = filepath.Join(c.SrcRoot, c.SkeletonPath)
} else {
// use the revel default
revelCmdPkg, err = build.Import(RevelCmdImportPath, "", build.FindOnly)
revelCmdPkg, err := build.Import(RevelCmdImportPath, "", build.FindOnly)
if err != nil {
errorf("Abort: Could not find Revel Cmd source code: %s\n", err)
if err != nil {
// Go get the revel project
getCmd := exec.Command(c.GoCmd, "get", RevelCmdImportPath + "/revel")
utils.Logger.Info("Exec:" + c.GoCmd, "args", getCmd.Args)
getOutput, err := getCmd.CombinedOutput()
if err != nil {
utils.Logger.Fatal("Failed to fetch revel cmd " + RevelCmdImportPath, "getOutput", string(getOutput))
}
revelCmdPkg, err = build.Import(RevelCmdImportPath, "", build.FindOnly)
if err!= nil {
utils.Logger.Fatal("Failed to find source of revel cmd " + RevelCmdImportPath, "getOutput", string(getOutput), "error",err, "dir", revelCmdPkg.Dir)
}
}
}
skeletonPath = filepath.Join(revelCmdPkg.Dir, "revel", "skeleton")
c.SkeletonPath = filepath.Join(revelCmdPkg.Dir, "revel", "skeleton")
}
}
func copyNewAppFiles() {
func copyNewAppFiles(c *model.CommandConfig) {
var err error
err = os.MkdirAll(appPath, 0777)
panicOnError(err, "Failed to create directory "+appPath)
err = os.MkdirAll(c.AppPath, 0777)
utils.PanicOnError(err, "Failed to create directory "+c.AppPath)
_ = mustCopyDir(appPath, skeletonPath, map[string]interface{}{
_ = utils.MustCopyDir(c.AppPath, c.SkeletonPath, map[string]interface{}{
// app.conf
"AppName": appName,
"BasePath": basePath,
"AppName": c.AppName,
"BasePath": c.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))
utils.MustCopyFile(filepath.Join(c.AppPath, gitignore), filepath.Join(c.SkeletonPath, gitignore))
}
const (
VENDOR_GOPKG = `#
# Revel Gopkg.toml
#
# If you want to use a specific version of Revel change the branches below
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
required = ["github.com/revel/cmd/revel"]
[[override]]
branch = "master"
name = "github.com/revel/modules"
[[override]]
branch = "master"
name = "github.com/revel/revel"
[[override]]
branch = "master"
name = "github.com/revel/cmd"
[[override]]
branch = "master"
name = "github.com/revel/log15"
[[override]]
branch = "master"
name = "github.com/revel/cron"
[[override]]
branch = "master"
name = "github.com/xeonx/timeago"
`
NEW_MAIN_FILE = `package main
`
)

View File

@@ -10,11 +10,12 @@ import (
"os"
"path/filepath"
"github.com/revel/revel"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
)
var cmdPackage = &Command{
UsageLine: "package [import path] [run mode]",
UsageLine: "package -i [import path] -r [run mode]",
Short: "package a Revel application (e.g. for deployment)",
Long: `
Package the Revel web application named by the given import path.
@@ -27,43 +28,62 @@ Run mode defaults to "dev".
For example:
revel package github.com/revel/examples/chat
revel package -i github.com/revel/examples/chat
`,
}
func init() {
cmdPackage.Run = packageApp
cmdPackage.RunWith = packageApp
cmdPackage.UpdateConfig = updatePackageConfig
}
func packageApp(args []string) {
// Called when unable to parse the command line automatically and assumes an old launch
func updatePackageConfig(c *model.CommandConfig, args []string) bool {
c.Index = model.PACKAGE
if len(args) == 0 {
fmt.Fprint(os.Stderr, cmdPackage.Long)
return
fmt.Fprintf(os.Stderr, cmdPackage.Long)
return false
}
c.Package.ImportPath = args[0]
if len(args)>1 {
c.Package.Mode = args[1]
}
return true
}
func packageApp(c *model.CommandConfig) {
// Determine the run mode.
mode := DefaultRunMode
if len(args) >= 2 {
mode = args[1]
if len(c.Package.Mode) >= 0 {
mode = c.Package.Mode
}
appImportPath := args[0]
revel.Init(mode, appImportPath, "")
appImportPath := c.ImportPath
revel_paths := model.NewRevelPaths(mode, appImportPath, "", model.DoNothingRevelCallback)
// Remove the archive if it already exists.
destFile := filepath.Base(revel.BasePath) + ".tar.gz"
destFile := filepath.Base(revel_paths.BasePath) + ".tar.gz"
if err := os.Remove(destFile); err != nil && !os.IsNotExist(err) {
revel.ERROR.Fatal(err)
utils.Logger.Error("Unable to remove target file","error",err,"file",destFile)
os.Exit(1)
}
// Collect stuff in a temp directory.
tmpDir, err := ioutil.TempDir("", filepath.Base(revel.BasePath))
panicOnError(err, "Failed to get temp dir")
tmpDir, err := ioutil.TempDir("", filepath.Base(revel_paths.BasePath))
utils.PanicOnError(err, "Failed to get temp dir")
buildApp([]string{args[0], tmpDir, mode})
// Build expects the command the build to contain the proper data
if len(c.Package.Mode) >= 0 {
c.Build.Mode = c.Package.Mode
}
c.Build.TargetPath = tmpDir
c.Build.CopySource = c.Package.CopySource
buildApp(c)
// Create the zip file.
archiveName := mustTarGzDir(destFile, tmpDir)
archiveName := utils.MustTarGzDir(destFile, tmpDir)
fmt.Println("Your archive is ready:", archiveName)
}

View File

@@ -1,2 +0,0 @@
@echo off
{{.BinName}} -importPath {{.ImportPath}} -srcPath %CD%\src -runMode {{.Mode}}

View File

@@ -1,3 +0,0 @@
#!/bin/sh
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}

View File

@@ -1,144 +0,0 @@
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
// The command line tool for running Revel apps.
package main
import (
"flag"
"fmt"
"io"
"math/rand"
"os"
"runtime"
"strings"
"text/template"
"time"
"github.com/agtorre/gocolorize"
)
const (
// RevelCmdImportPath Revel framework cmd tool import path
RevelCmdImportPath = "github.com/revel/cmd"
// DefaultRunMode for revel's application
DefaultRunMode = "dev"
)
// Command structure cribbed from the genius organization of the "go" command.
type Command struct {
Run func(args []string)
UsageLine, Short, Long string
}
// Name returns command name from usage line
func (cmd *Command) Name() string {
name := cmd.UsageLine
i := strings.Index(name, " ")
if i >= 0 {
name = name[:i]
}
return name
}
var commands = []*Command{
cmdNew,
cmdRun,
cmdBuild,
cmdPackage,
cmdClean,
cmdTest,
cmdVersion,
}
func main() {
if runtime.GOOS == "windows" {
gocolorize.SetPlain(true)
}
fmt.Fprintf(os.Stdout, gocolorize.NewColor("blue").Paint(header))
flag.Usage = func() { usage(1) }
flag.Parse()
args := flag.Args()
if len(args) < 1 || args[0] == "help" {
if len(args) == 1 {
usage(0)
}
if len(args) > 1 {
for _, cmd := range commands {
if cmd.Name() == args[1] {
tmpl(os.Stdout, helpTemplate, cmd)
return
}
}
}
usage(2)
}
// Commands use panic to abort execution when something goes wrong.
// Panics are logged at the point of error. Ignore those.
defer func() {
if err := recover(); err != nil {
if _, ok := err.(LoggedError); !ok {
// This panic was not expected / logged.
panic(err)
}
os.Exit(1)
}
}()
for _, cmd := range commands {
if cmd.Name() == args[0] {
cmd.Run(args[1:])
return
}
}
errorf("unknown command %q\nRun 'revel help' for usage.\n", args[0])
}
func errorf(format string, args ...interface{}) {
// Ensure the user's command prompt starts on the next line.
if !strings.HasSuffix(format, "\n") {
format += "\n"
}
fmt.Fprintf(os.Stderr, format, args...)
panic(LoggedError{}) // Panic instead of os.Exit so that deferred will run.
}
const header = `~
~ revel! http://revel.github.io
~
`
const usageTemplate = `usage: revel command [arguments]
The commands are:
{{range .}}
{{.Name | printf "%-11s"}} {{.Short}}{{end}}
Use "revel help [command]" for more information.
`
var helpTemplate = `usage: revel {{.UsageLine}}
{{.Long}}
`
func usage(exitCode int) {
tmpl(os.Stderr, usageTemplate, commands)
os.Exit(exitCode)
}
func tmpl(w io.Writer, text string, data interface{}) {
t := template.New("top")
template.Must(t.Parse(text))
if err := t.Execute(w, data); err != nil {
panic(err)
}
}
func init() {
rand.Seed(time.Now().UnixNano())
}

301
revel/revel.go Normal file
View File

@@ -0,0 +1,301 @@
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
// The command line tool for running Revel apps.
package main
import (
"flag"
"fmt"
"io"
"math/rand"
"os"
"runtime"
"strings"
"text/template"
"time"
"github.com/jessevdk/go-flags"
"github.com/agtorre/gocolorize"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"github.com/revel/cmd/logger"
"os/exec"
"path/filepath"
"go/build"
)
const (
// RevelCmdImportPath Revel framework cmd tool import path
RevelCmdImportPath = "github.com/revel/cmd"
// DefaultRunMode for revel's application
DefaultRunMode = "dev"
)
// Command structure cribbed from the genius organization of the "go" command.
type Command struct {
UpdateConfig func(c *model.CommandConfig, args []string) bool
RunWith func(c *model.CommandConfig)
UsageLine, Short, Long string
}
// Name returns command name from usage line
func (cmd *Command) Name() string {
name := cmd.UsageLine
i := strings.Index(name, " ")
if i >= 0 {
name = name[:i]
}
return name
}
// The commands
var commands = []*Command{
nil, // Safety net, prevent missing index from running
cmdNew,
cmdRun,
cmdBuild,
cmdPackage,
cmdClean,
cmdTest,
cmdVersion,
}
func main() {
if runtime.GOOS == "windows" {
gocolorize.SetPlain(true)
}
c := &model.CommandConfig{}
wd,_ := os.Getwd()
utils.InitLogger(wd,logger.LvlError)
parser := flags.NewParser(c, flags.HelpFlag | flags.PassDoubleDash)
if ini:=flag.String("ini","none","");*ini!="none" {
if err:=flags.NewIniParser(parser).ParseFile(*ini);err!=nil {
utils.Logger.Error("Unable to load ini", "error",err)
}
} else {
if _, err := parser.Parse(); err != nil {
utils.Logger.Info("Command line options failed", "error", err.Error())
// Decode nature of error
if perr,ok:=err.(*flags.Error); ok {
if perr.Type == flags.ErrRequired {
// Try the old way
if !main_parse_old(c) {
println("Command line error:", err.Error())
parser.WriteHelp(os.Stdout)
os.Exit(1)
}
} else {
println("Command line error:", err.Error())
parser.WriteHelp(os.Stdout)
os.Exit(1)
}
} else {
println("Command line error:", err.Error())
parser.WriteHelp(os.Stdout)
os.Exit(1)
}
} else {
switch parser.Active.Name {
case "new":
c.Index = model.NEW
case "run":
c.Index = model.RUN
case "build":
c.Index = model.BUILD
case "package":
c.Index = model.PACKAGE
case "clean":
c.Index = model.CLEAN
case "test":
c.Index = model.TEST
case "version":
c.Index = model.VERSION
}
}
}
// Switch based on the verbose flag
if c.Verbose {
utils.InitLogger(wd, logger.LvlDebug)
} else {
utils.InitLogger(wd, logger.LvlWarn)
}
if c.Index==0 {
utils.Logger.Fatal("Unknown command line arguements")
}
if !c.UpdateImportPath() {
utils.Logger.Fatal("Unable to determine application path")
}
println("Revel executing:", commands[c.Index].Short)
// checking and setting go paths
initGoPaths(c)
commands[c.Index].RunWith(c)
}
// Try to populate the CommandConfig using the old techniques
func main_parse_old(c *model.CommandConfig) bool {
// Take the old command format and try to parse them
flag.Usage = func() { usage(1) }
flag.Parse()
args := flag.Args()
if len(args) < 1 || args[0] == "help" {
if len(args) == 1 {
usage(0)
}
if len(args) > 1 {
for _, cmd := range commands {
if cmd!=nil && cmd.Name() == args[1] {
tmpl(os.Stdout, helpTemplate, cmd)
return false
}
}
}
usage(2)
}
for _, cmd := range commands {
if cmd!=nil && cmd.Name() == args[0] {
println("Running", cmd.Name())
return cmd.UpdateConfig(c, args[1:])
}
}
return false
}
func main_old() {
if runtime.GOOS == "windows" {
gocolorize.SetPlain(true)
}
fmt.Fprintf(os.Stdout, gocolorize.NewColor("blue").Paint(header))
flag.Usage = func() { usage(1) }
flag.Parse()
args := flag.Args()
if len(args) < 1 || args[0] == "help" {
if len(args) == 1 {
usage(0)
}
if len(args) > 1 {
for _, cmd := range commands {
if cmd.Name() == args[1] {
tmpl(os.Stdout, helpTemplate, cmd)
return
}
}
}
usage(2)
}
// Commands use panic to abort execution when something goes wrong.
// Panics are logged at the point of error. Ignore those.
defer func() {
if err := recover(); err != nil {
if _, ok := err.(utils.LoggedError); !ok {
// This panic was not expected / logged.
panic(err)
}
os.Exit(1)
}
}()
//for _, cmd := range commands {
// if cmd.Name() == args[0] {
// cmd.UpdateConfig(args[1:])
// return
// }
//}
utils.Logger.Fatalf("unknown command %q\nRun 'revel help' for usage.\n", args[0])
}
const header = `~
~ revel! http://revel.github.io
~
`
const usageTemplate = `usage: revel command [arguments]
The commands are:
{{range .}}
{{.Name | printf "%-11s"}} {{.Short}}{{end}}
Use "revel help [command]" for more information.
`
var helpTemplate = `usage: revel {{.UsageLine}}
{{.Long}}
`
func usage(exitCode int) {
tmpl(os.Stderr, usageTemplate, commands)
os.Exit(exitCode)
}
func tmpl(w io.Writer, text string, data interface{}) {
t := template.New("top")
template.Must(t.Parse(text))
if err := t.Execute(w, data); err != nil {
panic(err)
}
}
func init() {
rand.Seed(time.Now().UnixNano())
}
// lookup and set Go related variables
func initGoPaths(c *model.CommandConfig) {
// lookup go path
c.GoPath = build.Default.GOPATH
if c.GoPath == "" {
utils.Logger.Fatal("Abort: GOPATH environment variable is not set. " +
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
}
// check for go executable
var err error
c.GoCmd, err = exec.LookPath("go")
if err != nil {
utils.Logger.Fatal("Go executable not found in PATH.")
}
// revel/revel#1004 choose go path relative to current working directory
workingDir, _ := os.Getwd()
goPathList := filepath.SplitList(c.GoPath)
for _, path := range goPathList {
if strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
c.SrcRoot = path
break
}
path, _ = filepath.EvalSymlinks(path)
if len(path) > 0 && strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
c.SrcRoot = path
break
}
}
if len(c.SrcRoot) == 0 {
if c.Index != model.VERSION {
utils.Logger.Fatal("Abort: could not create a Revel application outside of GOPATH.")
}
return
}
// set go src path
c.SrcRoot = filepath.Join(c.SrcRoot, "src")
}

View File

@@ -5,11 +5,13 @@
package main
import (
"go/build"
"strconv"
"fmt"
"github.com/revel/cmd/harness"
"github.com/revel/revel"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"go/build"
)
var cmdRun = &Command{
@@ -40,98 +42,107 @@ type RunArgs struct {
}
func init() {
cmdRun.Run = runApp
cmdRun.RunWith = runApp
cmdRun.UpdateConfig = updateRunConfig
}
func parseRunArgs(args []string) *RunArgs {
inputArgs := RunArgs{
ImportPath: importPathFromCurrentDir(),
Mode: DefaultRunMode,
Port: revel.HTTPPort,
}
func updateRunConfig(c *model.CommandConfig, args []string) bool {
switch len(args) {
case 3:
// Possibile combinations
// Possible combinations
// revel run [import-path] [run-mode] [port]
port, err := strconv.Atoi(args[2])
if err != nil {
errorf("Failed to parse port as integer: %s", args[2])
}
inputArgs.ImportPath = args[0]
inputArgs.Mode = args[1]
inputArgs.Port = port
c.Run.ImportPath = args[0]
c.Run.Mode = args[1]
c.Run.Port = args[2]
case 2:
// Possibile combinations
// Possible combinations
// 1. revel run [import-path] [run-mode]
// 2. revel run [import-path] [port]
// 3. revel run [run-mode] [port]
// Check to see if the import path evaluates out to something that may be on a gopath
if _, err := build.Import(args[0], "", build.FindOnly); err == nil {
// 1st arg is the import path
inputArgs.ImportPath = args[0]
if port, err := strconv.Atoi(args[1]); err == nil {
c.Run.ImportPath = args[0]
if _, err := strconv.Atoi(args[1]); err == nil {
// 2nd arg is the port number
inputArgs.Port = port
c.Run.Port = args[1]
} else {
// 2nd arg is the run mode
inputArgs.Mode = args[1]
c.Run.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
c.Run.Mode = args[0]
c.Run.Port = args[1]
}
case 1:
// Possibile combinations
// Possible 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 {
_, err := build.Import(args[0], "", build.FindOnly)
if err != nil {
utils.Logger.Warn("Unable to run using an import path, assuming import path is working directory %s %s", "Argument", args[0], "error", err.Error())
}
utils.Logger.Info("Trying to build with", args[0], err)
if err == nil {
// 1st arg is the import path
inputArgs.ImportPath = args[0]
} else if port, err := strconv.Atoi(args[0]); err == nil {
c.Run.ImportPath = args[0]
} else if _, err := strconv.Atoi(args[0]); err == nil {
// 1st arg is the port number
inputArgs.Port = port
c.Run.Port = args[0]
} else {
// 1st arg is the run mode
inputArgs.Mode = args[0]
c.Run.Mode = args[0]
}
case 0:
return false
}
return &inputArgs
c.Index = model.RUN
return true
}
func runApp(args []string) {
runArgs := parseRunArgs(args)
// Find and parse app.conf
revel.Init(runArgs.Mode, runArgs.ImportPath, "")
revel.LoadMimeConfig()
// fallback to default port
if runArgs.Port == 0 {
runArgs.Port = revel.HTTPPort
func runApp(c *model.CommandConfig) {
if c.Run.Mode == "" {
c.Run.Mode = "dev"
}
revel.INFO.Printf("Running %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, runArgs.Mode)
revel.TRACE.Println("Base path:", revel.BasePath)
revel_path := model.NewRevelPaths(c.Run.Mode, c.ImportPath, "", model.DoNothingRevelCallback)
if c.Run.Port != "" {
port, err := strconv.Atoi(c.Run.Port)
if err != nil {
utils.Logger.Fatalf("Failed to parse port as integer: %s", c.Run.Port)
}
revel_path.HTTPPort = port
}
utils.Logger.Infof("Running %s (%s) in %s mode\n", revel_path.AppName, revel_path.ImportPath, revel_path.RunMode)
utils.Logger.Debug("Base path:", "path", revel_path.BasePath)
// If the app is run in "watched" mode, use the harness to run it.
if 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.
if revel_path.Config.BoolDefault("watch", true) && revel_path.Config.BoolDefault("watch.code", true) {
utils.Logger.Info("Running in watched mode.")
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v}`, revel_path.RunMode, c.Verbose)
if c.HistoricMode {
runMode = revel_path.RunMode
}
// **** Never returns.
harness.NewHarness(c, revel_path, runMode, c.Run.NoProxy).Run()
}
// Else, just build and run the app.
revel.TRACE.Println("Running in live build mode.")
app, err := harness.Build()
utils.Logger.Debug("Running in live build mode.")
app, err := harness.Build(c, revel_path)
if err != nil {
errorf("Failed to build app: %s", err)
utils.Logger.Errorf("Failed to build app: %s", err)
}
app.Port = runArgs.Port
app.Cmd().Run()
app.Port = revel_path.HTTPPort
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v}`, app.Paths.RunMode, c.Verbose)
if c.HistoricMode {
runMode = revel_path.RunMode
}
app.Cmd(runMode).Run()
}

View File

@@ -26,11 +26,11 @@ func init() {
HeaderFilter, // Add some security based headers
revel.InterceptorFilter, // Run interceptors around the action.
revel.CompressFilter, // Compress the result.
revel.BeforeAfterFilter, // Call the before and after filter functions
revel.ActionInvoker, // Invoke the action.
}
// register startup functions with OnAppStart
// Register startup functions with OnAppStart
// revel.DevMode and revel.RunMode only work inside of OnAppStart. See Example Startup Script
// ( order dependent )
// revel.OnAppStart(ExampleStartupScript)
@@ -39,13 +39,13 @@ func init() {
}
// 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
// There is a full implementation of a CSRF filter in
// https://github.com/revel/modules/tree/master/csrf
var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) {
c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN")
c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block")
c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff")
c.Response.Out.Header().Add("Referrer-Policy", "strict-origin-when-cross-origin")
fc[0](c, fc[1:]) // Execute the next filter stage.
}

View File

@@ -1,4 +1,4 @@
{{if eq .RunMode "dev"}}
{{if .DevMode}}
{{template "debug.html" .}}
{{end}}
</body>

View File

@@ -3,7 +3,24 @@
# More info at http://revel.github.io/manual/appconf.html
################################################################################
# Sets the `AppName` variable which can be used in your code as
# Revel build section
# This section contains values that are not reloadable
################################################################################
# Comma delimited list of folders that are included with the package, or build commands
# If you want to not include folders within these ones prefix the folder with a . to make it hidden
package.folders = conf, public, app/views
# Revel reconfigurable section
#
################################################################################
# Sets `revel.AppName` for use in-app.
# Example:
# `if revel.AppName {...}`
app.name = {{ .AppName }}
@@ -13,7 +30,7 @@ app.name = {{ .AppName }}
# into your application
app.secret = {{ .Secret }}
# Revel running behind proxy like nginx, haproxy, etc
# Revel running behind proxy like nginx, haproxy, etc.
app.behind.proxy = false
@@ -57,10 +74,11 @@ cookie.prefix = REVEL
# as false.
# cookie.secure = false
# Limit cookie access to a given domain
# Limit cookie access to a given domain.
#cookie.domain =
# Define when your session cookie expires. Possible values:
# 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.
@@ -82,15 +100,6 @@ format.datetime = 2006-01-02 15:04
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
@@ -102,35 +111,62 @@ i18n.default_language = en
# 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
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 `DevMode` variable to `true` which can be used in your code as
# 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
# Automatically watches your applicaton files and recompiles on-demand
# Watch your applicaton files for changes and automatically rebuild
# Values:
# "true"
# Enables auto rebuilding.
# "false"
# Disables auto rebuilding.
watch = true
# If you set watch.mode = "eager", the server starts to recompile
# your application every time your application's files change.
watch.mode = "normal"
# 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 = eager
# Watch the entire $GOPATH for code changes. Default is false.
# Watch the entire `$GOPATH` for changes.
# Values:
# "true"
# Includes `$GOPATH` in watch path.
# "false"
# Excludes `$GOPATH` from watch path. Default value.
#watch.gopath = true
@@ -141,32 +177,24 @@ module.testrunner = github.com/revel/modules/testrunner
# Where to log the various Revel logs
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,
# 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 = 19
log.info.flags = 19
log.warn.flags = 19
log.error.flags = 19
# Values:
# "off"
# Disable log output.
# "stdout"
# Log to OS's standard output.
# "stderr"
# Log to Os's standard error output. Default value.
# "relative/path/to/log"
# Log to file.
log.all.filter.module.app = stdout # Log all loggers for the application to the stdout
log.error.nfilter.module.app = stderr # Everything else that logs an error to stderr
log.crit.output = stderr # Everything that logs something as critical goes to this
# Revel request access log
# Access log line format:
# 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
# INFO 21:53:55 static server-engine.go:169: Request Stats ip=127.0.0.1 path=/public/vendors/datatables.net-buttons/js/buttons.html5.min.js method=GET start=2017/08/31 21:53:55 status=200 duration_seconds=0.0002583 section=requestlog
log.request.output = stdout
################################################################################
@@ -176,42 +204,20 @@ log.request.output = stderr
# See:
# [dev] section for documentation of the various settings
[prod]
mode.dev = false
mode.dev = false
results.pretty = false
watch = false
module.testrunner =
log.warn.output = log/%(app.name)s-warn.json # Log all warn messages to file
log.error.output = log/%(app.name)s-error.json # Log all errors to file
log.crit.output = log/%(app.name)s-critical.json # Log all critical to file
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 /
# Revel request access log (json format)
# Example:
# log.request.output = %(app.name)s-request.log
log.request.output = off
# log.request.output = %(app.name)s-request.json
log.request.output = log/%(app.name)s-requests.json

View File

@@ -15,5 +15,12 @@ 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
# Catch all, this will route any request into the controller path
#
# **** WARNING ****
# Enabling this exposes any controller and function to the web.
# ** This is a serious security issue if used online **
#
# For rapid development uncomment the following to add new controller.action endpoints
# without having to add them to the routes table.
# * /:controller/:action :controller.:action

View File

@@ -4,4 +4,5 @@
# See also:
# - http://www.rfc-editor.org/rfc/bcp/bcp47.txt
# - http://www.w3.org/International/questions/qa-accept-lang-locales
[DEFAULT]

View File

@@ -16,8 +16,9 @@ import (
"time"
"github.com/revel/cmd/harness"
"github.com/revel/modules/testrunner/app/controllers"
"github.com/revel/revel"
"github.com/revel/cmd/model"
"github.com/revel/cmd/tests"
"github.com/revel/cmd/utils"
)
var cmdTest = &Command{
@@ -47,70 +48,104 @@ or one of UserTest's methods:
}
func init() {
cmdTest.Run = testApp
cmdTest.RunWith = testApp
cmdTest.UpdateConfig = updateTestConfig
}
func testApp(args []string) {
var err error
if len(args) == 0 {
errorf("No import path given.\nRun 'revel help test' for usage.\n")
// Called to update the config command with from the older stype
func updateTestConfig(c *model.CommandConfig, args []string) bool {
c.Index = model.TEST
// The full test runs
// revel test <import path> (run mode) (suite(.function))
if len(args) < 1 {
return false
}
c.Test.ImportPath = args[0]
if len(args) > 1 {
c.Test.Mode = args[1]
}
if len(args) > 2 {
c.Test.Function = args[2]
}
return true
}
// Called to test the application
func testApp(c *model.CommandConfig) {
var err error
mode := DefaultRunMode
if len(args) >= 2 {
mode = args[1]
if c.Test.Mode != "" {
mode = c.Test.Mode
}
// Find and parse app.conf
revel.Init(mode, args[0], "")
revel_path := model.NewRevelPaths(mode, c.ImportPath, "", model.DoNothingRevelCallback)
// Ensure that the testrunner is loaded in this mode.
checkTestRunner()
// todo checkTestRunner()
// Create a directory to hold the test result files.
resultPath := filepath.Join(revel.BasePath, "test-results")
resultPath := filepath.Join(revel_path.BasePath, "test-results")
if err = os.RemoveAll(resultPath); err != nil {
errorf("Failed to remove test result directory %s: %s", resultPath, err)
utils.Logger.Errorf("Failed to remove test result directory %s: %s", resultPath, err)
}
if err = os.Mkdir(resultPath, 0777); err != nil {
errorf("Failed to create test result directory %s: %s", resultPath, err)
utils.Logger.Errorf("Failed to create test result directory %s: %s", resultPath, err)
}
// Direct all the output into a file in the test-results directory.
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)
utils.Logger.Errorf("Failed to create test result log file: %s", err)
}
app, reverr := harness.Build()
app, reverr := harness.Build(c, revel_path)
if reverr != nil {
errorf("Error building: %s", reverr)
utils.Logger.Errorf("Error building: %s", reverr)
}
cmd := app.Cmd()
runMode := fmt.Sprintf(`{"mode":"%s","testModeFlag":true, "specialUseFlag":%v}`, app.Paths.RunMode, c.Verbose)
if c.HistoricMode {
runMode = app.Paths.RunMode
}
cmd := app.Cmd(runMode)
cmd.Stderr = io.MultiWriter(cmd.Stderr, file)
cmd.Stdout = io.MultiWriter(cmd.Stderr, file)
// Start the app...
if err := cmd.Start(); err != nil {
errorf("%s", err)
if err := cmd.Start(c); err != nil {
utils.Logger.Errorf("%s", err)
}
defer cmd.Kill()
revel.INFO.Printf("Testing %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, mode)
var httpAddr = revel_path.HTTPAddr
if httpAddr == "" {
httpAddr = "localhost"
}
var httpProto = "http"
if revel_path.HTTPSsl {
httpProto = "https"
}
// Get a list of tests
var baseURL = fmt.Sprintf("http://127.0.0.1:%d", revel.HTTPPort)
var baseURL = fmt.Sprintf("%s://%s:%d", httpProto, httpAddr, revel_path.HTTPPort)
utils.Logger.Infof("Testing %s (%s) in %s mode URL %s \n", revel_path.AppName, revel_path.ImportPath, mode, baseURL)
testSuites, _ := getTestsList(baseURL)
// If a specific TestSuite[.Method] is specified, only run that suite/test
if len(args) == 3 {
testSuites = filterTestSuites(testSuites, args[2])
if c.Test.Function != "" {
testSuites = filterTestSuites(testSuites, c.Test.Function)
}
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)
failedResults, overallSuccess := runTestSuites(revel_path, baseURL, resultPath, testSuites)
fmt.Println()
if overallSuccess {
@@ -127,16 +162,18 @@ func testApp(args []string) {
}
}
writeResultFile(resultPath, "result.failed", "failed")
errorf("Some tests failed. See file://%s for results.", resultPath)
utils.Logger.Errorf("Some tests failed. See file://%s for results.", resultPath)
}
}
// Outputs the results to a file
func writeResultFile(resultPath, name, content string) {
if err := ioutil.WriteFile(filepath.Join(resultPath, name), []byte(content), 0666); err != nil {
errorf("Failed to write result file %s: %s", filepath.Join(resultPath, name), err)
utils.Logger.Errorf("Failed to write result file %s: %s", filepath.Join(resultPath, name), err)
}
}
// Determines if response should be plural
func pluralize(num int, singular, plural string) string {
if num == 1 {
return singular
@@ -146,7 +183,7 @@ func pluralize(num int, singular, plural string) string {
// Filters test suites and individual tests to match
// the parsed command line parameter
func filterTestSuites(suites *[]controllers.TestSuiteDesc, suiteArgument string) *[]controllers.TestSuiteDesc {
func filterTestSuites(suites *[]tests.TestSuiteDesc, suiteArgument string) *[]tests.TestSuiteDesc {
var suiteName, testName string
argArray := strings.Split(suiteArgument, ".")
suiteName = argArray[0]
@@ -161,54 +198,34 @@ func filterTestSuites(suites *[]controllers.TestSuiteDesc, suiteArgument string)
continue
}
if testName == "" {
return &[]controllers.TestSuiteDesc{suite}
return &[]tests.TestSuiteDesc{suite}
}
// Only run a particular test in a suite
for _, test := range suite.Tests {
if test.Name != testName {
continue
}
return &[]controllers.TestSuiteDesc{
return &[]tests.TestSuiteDesc{
{
Name: suite.Name,
Tests: []controllers.TestDesc{test},
Tests: []tests.TestDesc{test},
},
}
}
errorf("Couldn't find test %s in suite %s", testName, suiteName)
utils.Logger.Errorf("Couldn't find test %s in suite %s", testName, suiteName)
}
errorf("Couldn't find test suite %s", suiteName)
utils.Logger.Errorf("Couldn't find test suite %s", suiteName)
return nil
}
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) {
func getTestsList(baseURL string) (*[]tests.TestSuiteDesc, error) {
var (
err error
resp *http.Response
testSuites []controllers.TestSuiteDesc
testSuites []tests.TestSuiteDesc
)
for i := 0; ; i++ {
if resp, err = http.Get(baseURL + "/@tests.list"); err == nil {
@@ -221,9 +238,9 @@ func getTestsList(baseURL string) (*[]controllers.TestSuiteDesc, error) {
continue
}
if err != nil {
errorf("Failed to request test list: %s", err)
utils.Logger.Fatalf("Failed to request test list: %s %s", baseURL, err)
} else {
errorf("Failed to request test list: non-200 response")
utils.Logger.Fatalf("Failed to request test list: non-200 response %s", baseURL)
}
}
defer func() {
@@ -235,21 +252,15 @@ func getTestsList(baseURL string) (*[]controllers.TestSuiteDesc, error) {
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)
}
// Run the testsuites using the container
func runTestSuites(paths *model.RevelContainer, baseURL, resultPath string, testSuites *[]tests.TestSuiteDesc) (*[]tests.TestSuiteResult, bool) {
// We can determine the testsuite location by finding the test module and extracting the data from it
resultFilePath := filepath.Join(paths.ModulePathMap["testrunner"], "app", "views", "TestRunner/SuiteResult.html")
var (
overallSuccess = true
failedResults []controllers.TestSuiteResult
failedResults []tests.TestSuiteResult
)
for _, suite := range *testSuites {
// Print the name of the suite we're running.
@@ -261,21 +272,25 @@ func runTestSuites(baseURL, resultPath string, testSuites *[]controllers.TestSui
// Run every test.
startTime := time.Now()
suiteResult := controllers.TestSuiteResult{Name: suite.Name, Passed: true}
suiteResult := tests.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)
utils.Logger.Errorf("Failed to fetch test result at url %s: %s", testURL, err)
}
defer func() {
_ = resp.Body.Close()
}()
var testResult controllers.TestResult
var testResult tests.TestResult
err = json.NewDecoder(resp.Body).Decode(&testResult)
if err == nil && !testResult.Passed {
suiteResult.Passed = false
utils.Logger.Error("Test Failed","suite", suite.Name, "test", test.Name)
fmt.Printf(" %s.%s : FAILED\n", suite.Name, test.Name)
} else {
fmt.Printf(" %s.%s : PASSED\n", suite.Name, test.Name)
}
suiteResult.Results = append(suiteResult.Results, testResult)
}
@@ -291,13 +306,7 @@ func runTestSuites(baseURL, resultPath string, testSuites *[]controllers.TestSui
// 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)
}
utils.MustRenderTemplate(suiteResultFilename, resultFilePath, suiteResult)
}
return &failedResults, overallSuccess

View File

@@ -1,176 +0,0 @@
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package main
import (
"archive/tar"
"compress/gzip"
"fmt"
"go/build"
"io"
"os"
"path/filepath"
"strings"
"text/template"
"github.com/revel/revel"
)
// LoggedError is wrapper to differentiate logged panics from unexpected ones.
type LoggedError struct{ error }
func panicOnError(err error, msg string) {
if revErr, ok := err.(*revel.Error); (ok && revErr != nil) || (!ok && err != nil) {
fmt.Fprintf(os.Stderr, "Abort: %s: %s\n", msg, err)
panic(LoggedError{err})
}
}
func mustCopyFile(destFilename, srcFilename string) {
destFile, err := os.Create(destFilename)
panicOnError(err, "Failed to create file "+destFilename)
srcFile, err := os.Open(srcFilename)
panicOnError(err, "Failed to open file "+srcFilename)
_, err = io.Copy(destFile, srcFile)
panicOnError(err,
fmt.Sprintf("Failed to copy data from %s to %s", srcFile.Name(), destFile.Name()))
err = destFile.Close()
panicOnError(err, "Failed to close file "+destFile.Name())
err = srcFile.Close()
panicOnError(err, "Failed to close file "+srcFile.Name())
}
func mustRenderTemplate(destPath, srcPath string, data map[string]interface{}) {
tmpl, err := template.ParseFiles(srcPath)
panicOnError(err, "Failed to parse template "+srcPath)
f, err := os.Create(destPath)
panicOnError(err, "Failed to create "+destPath)
err = tmpl.Execute(f, data)
panicOnError(err, "Failed to render template "+srcPath)
err = f.Close()
panicOnError(err, "Failed to close "+f.Name())
}
func mustChmod(filename string, mode os.FileMode) {
err := os.Chmod(filename, mode)
panicOnError(err, fmt.Sprintf("Failed to chmod %d %q", mode, filename))
}
// copyDir copies a directory tree over to a new directory. Any files ending in
// ".template" are treated as a Go template and rendered using the given data.
// Additionally, the trailing ".template" is stripped from the file name.
// Also, dot files and dot directories are skipped.
func mustCopyDir(destDir, srcDir string, data map[string]interface{}) error {
return revel.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
// Get the relative path from the source base, and the corresponding path in
// the dest directory.
relSrcPath := strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator))
destPath := filepath.Join(destDir, relSrcPath)
// Skip dot files and dot directories.
if strings.HasPrefix(relSrcPath, ".") {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
// Create a subdirectory if necessary.
if info.IsDir() {
err := os.MkdirAll(filepath.Join(destDir, relSrcPath), 0777)
if !os.IsExist(err) {
panicOnError(err, "Failed to create directory")
}
return nil
}
// If this file ends in ".template", render it as a template.
if strings.HasSuffix(relSrcPath, ".template") {
mustRenderTemplate(destPath[:len(destPath)-len(".template")], srcPath, data)
return nil
}
// Else, just copy it over.
mustCopyFile(destPath, srcPath)
return nil
})
}
func mustTarGzDir(destFilename, srcDir string) string {
zipFile, err := os.Create(destFilename)
panicOnError(err, "Failed to create archive")
defer func() {
_ = zipFile.Close()
}()
gzipWriter := gzip.NewWriter(zipFile)
defer func() {
_ = gzipWriter.Close()
}()
tarWriter := tar.NewWriter(gzipWriter)
defer func() {
_ = tarWriter.Close()
}()
_ = revel.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
srcFile, err := os.Open(srcPath)
panicOnError(err, "Failed to read source file")
defer func() {
_ = srcFile.Close()
}()
err = tarWriter.WriteHeader(&tar.Header{
Name: strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator)),
Size: info.Size(),
Mode: int64(info.Mode()),
ModTime: info.ModTime(),
})
panicOnError(err, "Failed to write tar entry header")
_, err = io.Copy(tarWriter, srcFile)
panicOnError(err, "Failed to copy")
return nil
})
return zipFile.Name()
}
func exists(filename string) bool {
_, err := os.Stat(filename)
return err == nil
}
// empty returns true if the given directory is empty.
// the directory must exist.
func empty(dirname string) bool {
dir, err := os.Open(dirname)
if err != nil {
errorf("error opening directory: %s", err)
}
defer func() {
_ = dir.Close()
}()
results, _ := dir.Readdir(1)
return len(results) == 0
}
func importPathFromCurrentDir() string {
pwd, _ := os.Getwd()
importPath, _ := filepath.Rel(filepath.Join(build.Default.GOPATH, "src"), pwd)
return filepath.ToSlash(importPath)
}

View File

@@ -12,7 +12,15 @@ import (
"fmt"
"runtime"
"github.com/revel/revel"
"github.com/revel/cmd/model"
"go/build"
"go/token"
"go/parser"
"go/ast"
"io/ioutil"
"path/filepath"
"github.com/revel/cmd/utils"
"github.com/revel/cmd"
)
var cmdVersion = &Command{
@@ -28,11 +36,65 @@ For example:
}
func init() {
cmdVersion.Run = versionApp
cmdVersion.RunWith = versionApp
}
func versionApp(args []string) {
fmt.Printf("Version(s):")
fmt.Printf("\n Revel v%v (%v)", revel.Version, revel.BuildDate)
// Displays the version of go and Revel
func versionApp(c *model.CommandConfig) {
var (
revelPkg *build.Package
err error
)
if len(c.ImportPath)>0 {
appPkg, err := build.Import(c.ImportPath, "", build.FindOnly)
if err != nil {
utils.Logger.Fatal("Failed to import " + c.ImportPath + " with error:", "error", err)
}
revelPkg, err = build.Import(model.RevelImportPath, appPkg.Dir, build.FindOnly)
} else {
revelPkg, err = build.Import(model.RevelImportPath, "" , build.FindOnly)
}
fmt.Println("\nRevel Framework")
if err != nil {
utils.Logger.Info("Failed to find Revel in GOPATH with error:", "error", err, "gopath", build.Default.GOPATH)
fmt.Println("Information not available (not on GOPATH)")
} else {
utils.Logger.Info("Fullpath to revel", revelPkg.Dir)
fset := token.NewFileSet() // positions are relative to fset
version, err := ioutil.ReadFile(filepath.Join(revelPkg.Dir, "version.go"))
if err != nil {
utils.Logger.Errorf("Failed to find Revel version:", "error", err)
}
// Parse src but stop after processing the imports.
f, err := parser.ParseFile(fset, "", version, parser.ParseComments)
if err != nil {
utils.Logger.Errorf("Failed to parse Revel version error:", "error", err)
}
// Print the imports from the file's AST.
for _, s := range f.Decls {
genDecl, ok := s.(*ast.GenDecl)
if !ok {
continue
}
if genDecl.Tok != token.CONST {
continue
}
for _, a := range genDecl.Specs {
spec := a.(*ast.ValueSpec)
r := spec.Values[0].(*ast.BasicLit)
fmt.Printf("Revel %s = %s\n", spec.Names[0].Name, r.Value)
}
}
}
fmt.Println("\nRevel Command Utility Tool")
fmt.Println("Version", cmd.Version)
fmt.Println("Build Date", cmd.BuildDate)
fmt.Println("Minimum Go Version", cmd.MinimumGoVersion)
fmt.Printf("\n %s %s/%s\n\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
}

158
tests/testrunner.go Normal file
View File

@@ -0,0 +1,158 @@
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package tests
import (
"fmt"
"html/template"
"reflect"
"strings"
"github.com/revel/cmd/utils"
)
// TestSuiteDesc is used for storing information about a single test suite.
// This structure is required by revel test cmd.
type TestSuiteDesc struct {
Name string
Tests []TestDesc
// Elem is reflect.Type which can be used for accessing methods
// of the test suite.
Elem reflect.Type
}
// TestDesc is used for describing a single test of some test suite.
// This structure is required by revel test cmd.
type TestDesc struct {
Name string
}
// TestSuiteResult stores the results the whole test suite.
// This structure is required by revel test cmd.
type TestSuiteResult struct {
Name string
Passed bool
Results []TestResult
}
// TestResult represents the results of running a single test of some test suite.
// This structure is required by revel test cmd.
type TestResult struct {
Name string
Passed bool
ErrorHTML template.HTML
ErrorSummary string
}
var (
testSuites []TestSuiteDesc // A list of all available tests.
none = []reflect.Value{} // It is used as input for reflect call in a few places.
// registeredTests simplifies the search of test suites by their name.
// "TestSuite.TestName" is used as a key. Value represents index in testSuites.
registeredTests map[string]int
)
/*
Below are helper functions.
*/
// describeSuite expects testsuite interface as input parameter
// and returns its description in a form of TestSuiteDesc structure.
func describeSuite(testSuite interface{}) TestSuiteDesc {
t := reflect.TypeOf(testSuite)
// Get a list of methods of the embedded test type.
// It will be used to make sure the same tests are not included in multiple test suites.
super := t.Elem().Field(0).Type
superMethods := map[string]bool{}
for i := 0; i < super.NumMethod(); i++ {
// Save the current method's name.
superMethods[super.Method(i).Name] = true
}
// Get a list of methods on the test suite that take no parameters, return
// no results, and were not part of the embedded type's method set.
var tests []TestDesc
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
mt := m.Type
// Make sure the test method meets the criterias:
// - method of testSuite without input parameters;
// - nothing is returned;
// - has "Test" prefix;
// - doesn't belong to the embedded structure.
methodWithoutParams := (mt.NumIn() == 1 && mt.In(0) == t)
nothingReturned := (mt.NumOut() == 0)
hasTestPrefix := (strings.HasPrefix(m.Name, "Test"))
if methodWithoutParams && nothingReturned && hasTestPrefix && !superMethods[m.Name] {
// Register the test suite's index so we can quickly find it by test's name later.
registeredTests[t.Elem().Name()+"."+m.Name] = len(testSuites)
// Add test to the list of tests.
tests = append(tests, TestDesc{m.Name})
}
}
return TestSuiteDesc{
Name: t.Elem().Name(),
Tests: tests,
Elem: t.Elem(),
}
}
// errorSummary gets an error and returns its summary in human readable format.
func errorSummary(err *utils.Error) (message string) {
expectedPrefix := "(expected)"
actualPrefix := "(actual)"
errDesc := err.Description
//strip the actual/expected stuff to provide more condensed display.
if strings.Index(errDesc, expectedPrefix) == 0 {
errDesc = errDesc[len(expectedPrefix):]
}
if strings.LastIndex(errDesc, actualPrefix) > 0 {
errDesc = errDesc[0 : len(errDesc)-len(actualPrefix)]
}
errFile := err.Path
slashIdx := strings.LastIndex(errFile, "/")
if slashIdx > 0 {
errFile = errFile[slashIdx+1:]
}
message = fmt.Sprintf("%s %s#%d", errDesc, errFile, err.Line)
/*
// If line of error isn't known return the message as is.
if err.Line == 0 {
return
}
// Otherwise, include info about the line number and the relevant
// source code lines.
message += fmt.Sprintf(" (around line %d): ", err.Line)
for _, line := range err.ContextSource() {
if line.IsError {
message += line.Source
}
}
*/
return
}
//sortbySuiteName sorts the testsuites by name.
type sortBySuiteName []interface{}
func (a sortBySuiteName) Len() int { return len(a) }
func (a sortBySuiteName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a sortBySuiteName) Less(i, j int) bool {
return reflect.TypeOf(a[i]).Elem().Name() < reflect.TypeOf(a[j]).Elem().Name()
}

81
utils/error.go Normal file
View File

@@ -0,0 +1,81 @@
package utils
import (
"fmt"
"strconv"
"strings"
)
// The error is a wrapper for the
type Error struct {
SourceType string // The type of source that failed to build.
Title, Path, Description string // Description of the error, as presented to the user.
Line, Column int // Where the error was encountered.
SourceLines []string // The entire source file, split into lines.
Stack string // The raw stack trace string from debug.Stack().
MetaError string // Error that occurred producing the error page.
Link string // A configurable link to wrap the error source in
}
// Creates a link based on the configuration setting "errors.link"
func (e *Error) SetLink(errorLink string) {
errorLink = strings.Replace(errorLink, "{{Path}}", e.Path, -1)
errorLink = strings.Replace(errorLink, "{{Line}}", strconv.Itoa(e.Line), -1)
e.Link = "<a href=" + errorLink + ">" + e.Path + ":" + strconv.Itoa(e.Line) + "</a>"
}
// Error method constructs a plaintext version of the error, taking
// account that fields are optionally set. Returns e.g. Compilation Error
// (in views/header.html:51): expected right delim in end; got "}"
func (e *Error) Error() string {
if e == nil {
panic("opps")
}
loc := ""
if e.Path != "" {
line := ""
if e.Line != 0 {
line = fmt.Sprintf(":%d", e.Line)
}
loc = fmt.Sprintf("(in %s%s)", e.Path, line)
}
header := loc
if e.Title != "" {
if loc != "" {
header = fmt.Sprintf("%s %s: ", e.Title, loc)
} else {
header = fmt.Sprintf("%s: ", e.Title)
}
}
return fmt.Sprintf("%s%s", header, e.Description)
}
// ContextSource method returns a snippet of the source around
// where the error occurred.
func (e *Error) ContextSource() []SourceLine {
if e.SourceLines == nil {
return nil
}
start := (e.Line - 1) - 5
if start < 0 {
start = 0
}
end := (e.Line - 1) + 5
if end > len(e.SourceLines) {
end = len(e.SourceLines)
}
lines := make([]SourceLine, end-start)
for i, src := range e.SourceLines[start:end] {
fileLine := start + i + 1
lines[i] = SourceLine{src, fileLine, fileLine == e.Line}
}
return lines
}
// SourceLine structure to hold the per-source-line details.
type SourceLine struct {
Source string
Line int
IsError bool
}

285
utils/file.go Normal file
View File

@@ -0,0 +1,285 @@
package utils
// DirExists returns true if the given path exists and is a directory.
import (
"os"
"archive/tar"
"strings"
"io"
"path/filepath"
"fmt"
"html/template"
"compress/gzip"
"go/build"
"io/ioutil"
"bytes"
)
func DirExists(filename string) bool {
fileInfo, err := os.Stat(filename)
return err == nil && fileInfo.IsDir()
}
// MustReadLines reads the lines of the given file. Panics in the case of error.
func MustReadLines(filename string) []string {
r, err := ReadLines(filename)
if err != nil {
panic(err)
}
return r
}
// ReadLines reads the lines of the given file. Panics in the case of error.
func ReadLines(filename string) ([]string, error) {
dataBytes, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return strings.Split(string(dataBytes), "\n"), nil
}
func MustCopyFile(destFilename, srcFilename string) {
destFile, err := os.Create(destFilename)
PanicOnError(err, "Failed to create file "+destFilename)
srcFile, err := os.Open(srcFilename)
PanicOnError(err, "Failed to open file "+srcFilename)
_, err = io.Copy(destFile, srcFile)
PanicOnError(err,
fmt.Sprintf("Failed to copy data from %s to %s", srcFile.Name(), destFile.Name()))
err = destFile.Close()
PanicOnError(err, "Failed to close file "+destFile.Name())
err = srcFile.Close()
PanicOnError(err, "Failed to close file "+srcFile.Name())
}
// GenerateTemplate renders the given template to produce source code, which it writes
// to the given file.
func MustGenerateTemplate(filename, templateSource string, args map[string]interface{}) (err error) {
tmpl := template.Must(template.New("").Parse(templateSource))
var b bytes.Buffer
if err = tmpl.Execute(&b, args); err != nil {
Logger.Fatal("ExecuteTemplate: Execute failed", "error", err)
return
}
sourceCode := b.String()
filePath := filepath.Dir(filename)
if !DirExists(filePath) {
err = os.MkdirAll(filePath, 0777)
if err != nil && !os.IsExist(err) {
Logger.Fatal("Failed to make directory","dir", filePath, "error", err)
}
}
// Create the file
file, err := os.Create(filename)
if err != nil {
Logger.Fatal("Failed to create file","error", err)
return
}
defer func() {
_ = file.Close()
}()
if _, err = file.WriteString(sourceCode); err != nil {
Logger.Fatal("Failed to write to file: ", "error", err)
}
return
}
// Given the target path and source path and data. A template
func MustRenderTemplate(destPath, srcPath string, data interface{}) {
tmpl, err := template.ParseFiles(srcPath)
PanicOnError(err, "Failed to parse template "+srcPath)
f, err := os.Create(destPath)
PanicOnError(err, "Failed to create "+destPath)
err = tmpl.Execute(f, data)
PanicOnError(err, "Failed to render template "+srcPath)
err = f.Close()
PanicOnError(err, "Failed to close "+f.Name())
}
// Given the target path and source path and data. A template
func MustRenderTemplateToStream(output io.Writer, srcPath []string, data interface{}) {
tmpl, err := template.ParseFiles(srcPath...)
PanicOnError(err, "Failed to parse template "+srcPath[0])
err = tmpl.Execute(output, data)
PanicOnError(err, "Failed to render template "+srcPath[0])
}
func MustChmod(filename string, mode os.FileMode) {
err := os.Chmod(filename, mode)
PanicOnError(err, fmt.Sprintf("Failed to chmod %d %q", mode, filename))
}
// Called if panic
func PanicOnError(err error, msg string) {
if revErr, ok := err.(*Error); (ok && revErr != nil) || (!ok && err != nil) {
Logger.Fatalf("Abort: %s: %s %s\n", msg, revErr, err)
//panic(NewLoggedError(err))
}
}
// copyDir copies a directory tree over to a new directory. Any files ending in
// ".template" are treated as a Go template and rendered using the given data.
// Additionally, the trailing ".template" is stripped from the file name.
// Also, dot files and dot directories are skipped.
func MustCopyDir(destDir, srcDir string, data map[string]interface{}) error {
return fsWalk(srcDir, srcDir, func(srcPath string, info os.FileInfo, err error) error {
// Get the relative path from the source base, and the corresponding path in
// the dest directory.
relSrcPath := strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator))
destPath := filepath.Join(destDir, relSrcPath)
// Skip dot files and dot directories.
if strings.HasPrefix(relSrcPath, ".") {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
// Create a subdirectory if necessary.
if info.IsDir() {
err := os.MkdirAll(filepath.Join(destDir, relSrcPath), 0777)
if !os.IsExist(err) {
PanicOnError(err, "Failed to create directory")
}
return nil
}
// If this file ends in ".template", render it as a template.
if strings.HasSuffix(relSrcPath, ".template") {
MustRenderTemplate(destPath[:len(destPath)-len(".template")], srcPath, data)
return nil
}
// Else, just copy it over.
MustCopyFile(destPath, srcPath)
return nil
})
}
func Walk(root string, walkFn filepath.WalkFunc) error {
return fsWalk(root,root,walkFn)
}
func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
fsWalkFunc := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
var name string
name, err = filepath.Rel(fname, path)
if err != nil {
return err
}
path = filepath.Join(linkName, name)
if err == nil && info.Mode()&os.ModeSymlink == os.ModeSymlink {
var symlinkPath string
symlinkPath, err = filepath.EvalSymlinks(path)
if err != nil {
return err
}
// https://github.com/golang/go/blob/master/src/path/filepath/path.go#L392
info, err = os.Lstat(symlinkPath)
if err != nil {
return walkFn(path, info, err)
}
if info.IsDir() {
return fsWalk(symlinkPath, path, walkFn)
}
}
return walkFn(path, info, err)
}
err := filepath.Walk(fname, fsWalkFunc)
return err
}
func MustTarGzDir(destFilename, srcDir string) string {
zipFile, err := os.Create(destFilename)
PanicOnError(err, "Failed to create archive")
defer func() {
_ = zipFile.Close()
}()
gzipWriter := gzip.NewWriter(zipFile)
defer func() {
_ = gzipWriter.Close()
}()
tarWriter := tar.NewWriter(gzipWriter)
defer func() {
_ = tarWriter.Close()
}()
_ = fsWalk(srcDir, srcDir, func(srcPath string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
srcFile, err := os.Open(srcPath)
PanicOnError(err, "Failed to read source file")
defer func() {
_ = srcFile.Close()
}()
err = tarWriter.WriteHeader(&tar.Header{
Name: strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator)),
Size: info.Size(),
Mode: int64(info.Mode()),
ModTime: info.ModTime(),
})
PanicOnError(err, "Failed to write tar entry header")
_, err = io.Copy(tarWriter, srcFile)
PanicOnError(err, "Failed to copy")
return nil
})
return zipFile.Name()
}
func Exists(filename string) bool {
_, err := os.Stat(filename)
return err == nil
}
// empty returns true if the given directory is empty.
// the directory must exist.
func Empty(dirname string) bool {
dir, err := os.Open(dirname)
if err != nil {
Logger.Infof("error opening directory: %s", err)
}
defer func() {
_ = dir.Close()
}()
results, _ := dir.Readdir(1)
return len(results) == 0
}
func ImportPathFromCurrentDir() string {
pwd, _ := os.Getwd()
importPath, _ := filepath.Rel(filepath.Join(build.Default.GOPATH, "src"), pwd)
return filepath.ToSlash(importPath)
}

43
utils/log.go Normal file
View File

@@ -0,0 +1,43 @@
package utils
import (
"github.com/revel/cmd/logger"
"github.com/revel/config"
"fmt"
"os"
"strings"
)
var Logger = logger.New()
func InitLogger(basePath string, logLevel logger.LogLevel) {
newContext := config.NewContext()
if logLevel<logger.LvlInfo {
newContext.SetOption("log.info.output", "none")
newContext.SetOption("log.debug.output", "none")
} else {
newContext.SetOption("log.info.output", "stdout")
newContext.SetOption("log.debug.output", "stdout")
}
newContext.SetOption("log.warn.output","stderr")
newContext.SetOption("log.error.output","stderr")
newContext.SetOption("log.crit.output","stderr")
Logger.SetHandler(logger.InitializeFromConfig(basePath, newContext))
}
// This function is to throw a panic that may be caught by the packger so it can perform the needed
// imports
func Retry(format string, args ...interface{}) {
// Ensure the user's command prompt starts on the next line.
if !strings.HasSuffix(format, "\n") {
format += "\n"
}
fmt.Fprintf(os.Stderr, format, args...)
panic(format) // Panic instead of os.Exit so that deferred will run.
}
type LoggedError struct{ error }
func NewLoggedError(err error) *LoggedError {
return &LoggedError{err}
}

11
utils/strings.go Normal file
View File

@@ -0,0 +1,11 @@
package utils
// Return true if the target string is in the list
func ContainsString(list []string, target string) bool {
for _, el := range list {
if el == target {
return true
}
}
return false
}

16
version.go Normal file
View File

@@ -0,0 +1,16 @@
// Copyright (c) 2012-2017 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 cmd
const (
// Version current Revel Command version
Version = "0.20.0-dev"
// BuildDate latest commit/release date
BuildDate = "2018-02-06"
// MinimumGoVersion minimum required Go version for Revel
MinimumGoVersion = ">= go1.8"
)

306
watcher/watcher.go Normal file
View File

@@ -0,0 +1,306 @@
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package watcher
import (
"os"
"path/filepath"
"strings"
"sync"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"gopkg.in/fsnotify/fsnotify.v1"
"time"
)
// Listener is an interface for receivers of filesystem events.
type Listener interface {
// Refresh is invoked by the watcher on relevant filesystem events.
// If the listener returns an error, it is served to the user on the current request.
Refresh() *utils.Error
}
// DiscerningListener allows the receiver to selectively watch files.
type DiscerningListener interface {
Listener
WatchDir(info os.FileInfo) bool
WatchFile(basename string) bool
}
// Watcher allows listeners to register to be notified of changes under a given
// directory.
type Watcher struct {
// Parallel arrays of watcher/listener pairs.
watchers []*fsnotify.Watcher
listeners []Listener
forceRefresh bool
eagerRefresh bool
serial bool
lastError int
notifyMutex sync.Mutex
paths *model.RevelContainer
refreshTimer *time.Timer // The timer to countdown the next refresh
timerMutex *sync.Mutex // A mutex to prevent concurrent updates
refreshChannel chan *utils.Error
refreshChannelCount int
refreshTimerMS time.Duration // The number of milliseconds between refreshing builds
}
// Creates a new watched based on the container
func NewWatcher(paths *model.RevelContainer, eagerRefresh bool) *Watcher {
return &Watcher{
forceRefresh: true,
lastError: -1,
paths: paths,
refreshTimerMS: time.Duration(paths.Config.IntDefault("watch.rebuild.delay", 10)),
eagerRefresh: eagerRefresh ||
paths.DevMode &&
paths.Config.BoolDefault("watch", true) &&
paths.Config.StringDefault("watch.mode", "normal") == "eager",
timerMutex: &sync.Mutex{},
refreshChannel: make(chan *utils.Error, 10),
refreshChannelCount: 0,
}
}
// Listen registers for events within the given root directories (recursively).
func (w *Watcher) Listen(listener Listener, roots ...string) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
utils.Logger.Fatal("Watcher: Failed to create watcher", "error", err)
}
// Replace the unbuffered Event channel with a buffered one.
// Otherwise multiple change events only come out one at a time, across
// multiple page views. (There appears no way to "pump" the events out of
// the watcher)
// This causes a notification when you do a check in go, since you are modifying a buffer in use
watcher.Events = make(chan fsnotify.Event, 100)
watcher.Errors = make(chan error, 10)
// Walk through all files / directories under the root, adding each to watcher.
for _, p := range roots {
// is the directory / file a symlink?
f, err := os.Lstat(p)
if err == nil && f.Mode()&os.ModeSymlink == os.ModeSymlink {
var realPath string
realPath, err = filepath.EvalSymlinks(p)
if err != nil {
panic(err)
}
p = realPath
}
fi, err := os.Stat(p)
if err != nil {
utils.Logger.Fatal("Watcher: Failed to stat watched path", "path", p, "error", err)
continue
}
// If it is a file, watch that specific file.
if !fi.IsDir() {
err = watcher.Add(p)
if err != nil {
utils.Logger.Fatal("Watcher: Failed to watch", "path", p, "error", err)
}
continue
}
var watcherWalker func(path string, info os.FileInfo, err error) error
watcherWalker = func(path string, info os.FileInfo, err error) error {
if err != nil {
utils.Logger.Fatal("Watcher: Error walking path:", "error", err)
return nil
}
if info.IsDir() {
if dl, ok := listener.(DiscerningListener); ok {
if !dl.WatchDir(info) {
return filepath.SkipDir
}
}
err = watcher.Add(path)
if err != nil {
utils.Logger.Fatal("Watcher: Failed to watch", "path", path, "error", err)
}
}
return nil
}
// Else, walk the directory tree.
err = utils.Walk(p, watcherWalker)
if err != nil {
utils.Logger.Fatal("Watcher: Failed to walk directory", "path", p, "error", err)
}
}
if w.eagerRefresh {
// Create goroutine to notify file changes in real time
go w.NotifyWhenUpdated(listener, watcher)
}
w.watchers = append(w.watchers, watcher)
w.listeners = append(w.listeners, listener)
}
// NotifyWhenUpdated notifies the watcher when a file event is received.
func (w *Watcher) NotifyWhenUpdated(listener Listener, watcher *fsnotify.Watcher) {
for {
select {
case ev := <-watcher.Events:
if w.rebuildRequired(ev, listener) {
if w.serial {
// Serialize listener.Refresh() calls.
w.notifyMutex.Lock()
if err := listener.Refresh(); err != nil {
utils.Logger.Error("Watcher: Listener refresh reported error:", "error", err)
}
w.notifyMutex.Unlock()
} else {
// Run refresh in parallel
go func() {
w.notifyInProcess(listener)
}()
}
}
case <-watcher.Errors:
continue
}
}
}
// Notify causes the watcher to forward any change events to listeners.
// It returns the first (if any) error returned.
func (w *Watcher) Notify() *utils.Error {
if w.serial {
// Serialize Notify() calls.
w.notifyMutex.Lock()
defer w.notifyMutex.Unlock()
}
for i, watcher := range w.watchers {
listener := w.listeners[i]
// Pull all pending events / errors from the watcher.
refresh := false
for {
select {
case ev := <-watcher.Events:
if w.rebuildRequired(ev, listener) {
refresh = true
}
continue
case <-watcher.Errors:
continue
default:
// No events left to pull
}
break
}
utils.Logger.Info("Watcher:Notify refresh state", "Current Index", i, " last error index", w.lastError)
if w.forceRefresh || refresh || w.lastError == i {
var err *utils.Error
if w.serial {
err = listener.Refresh()
} else {
err = w.notifyInProcess(listener)
}
if err != nil {
w.lastError = i
w.forceRefresh = true
return err
} else {
w.lastError = -1
w.forceRefresh = false
}
}
}
return nil
}
// Build a queue for refresh notifications
// this will not return until one of the queue completes
func (w *Watcher) notifyInProcess(listener Listener) (err *utils.Error) {
shouldReturn := false
// This code block ensures that either a timer is created
// or that a process would be added the the h.refreshChannel
func() {
w.timerMutex.Lock()
defer w.timerMutex.Unlock()
// If we are in the process of a rebuild, forceRefresh will always be true
w.forceRefresh = true
if w.refreshTimer != nil {
utils.Logger.Info("Found existing timer running, resetting")
w.refreshTimer.Reset(time.Millisecond * w.refreshTimerMS)
shouldReturn = true
w.refreshChannelCount++
} else {
w.refreshTimer = time.NewTimer(time.Millisecond * w.refreshTimerMS)
}
}()
// If another process is already waiting for the timer this one
// only needs to return the output from the channel
if shouldReturn {
return <-w.refreshChannel
}
utils.Logger.Info("Waiting for refresh timer to expire")
<-w.refreshTimer.C
w.timerMutex.Lock()
// Ensure the queue is properly dispatched even if a panic occurs
defer func() {
for x := 0; x < w.refreshChannelCount; x++ {
w.refreshChannel <- err
}
w.refreshChannelCount = 0
w.refreshTimer = nil
w.timerMutex.Unlock()
}()
err = listener.Refresh()
if err != nil {
utils.Logger.Info("Watcher: Recording error last build, setting rebuild on", "error", err)
} else {
w.lastError = -1
w.forceRefresh = false
}
utils.Logger.Info("Rebuilt, result", "error", err)
return
}
func (w *Watcher) rebuildRequired(ev fsnotify.Event, listener Listener) bool {
// Ignore changes to dotfiles.
if strings.HasPrefix(filepath.Base(ev.Name), ".") {
return false
}
if dl, ok := listener.(DiscerningListener); ok {
if !dl.WatchFile(ev.Name) || ev.Op&fsnotify.Chmod == fsnotify.Chmod {
return false
}
}
return true
}
/*
var WatchFilter = func(c *Controller, fc []Filter) {
if MainWatcher != nil {
err := MainWatcher.Notify()
if err != nil {
c.Result = c.RenderError(err)
return
}
}
fc[0](c, fc[1:])
}
*/