mirror of
https://github.com/kevin-DL/revel-cmd.git
synced 2026-01-12 11:05:19 +00:00
Compare commits
181 Commits
server-eng
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2480171e5 | ||
|
|
d83d2d5891 | ||
|
|
55b736e588 | ||
|
|
8162ef3ed6 | ||
|
|
0ec9e69d97 | ||
|
|
1cdd318d9c | ||
|
|
da2a81d035 | ||
|
|
86b4670a2f | ||
|
|
bc376fbee0 | ||
|
|
5c8ac53937 | ||
|
|
c67408482e | ||
|
|
cfe1d9718f | ||
|
|
7f9f658392 | ||
|
|
126d20c873 | ||
|
|
111264cfa0 | ||
|
|
4087c49b9e | ||
|
|
3602eb4ea7 | ||
|
|
192fc6669a | ||
|
|
5689f8679b | ||
|
|
6dba0c3d2d | ||
|
|
bb926f396a | ||
|
|
3cd5ebbde2 | ||
|
|
25dc05b31e | ||
|
|
0a40a2048e | ||
|
|
fcc1319245 | ||
|
|
ea5acb720f | ||
|
|
25d6352bc5 | ||
|
|
ddec572d5d | ||
|
|
7a91d0ca0b | ||
|
|
b562bd2dc5 | ||
|
|
bf17a71166 | ||
|
|
3d924a016b | ||
|
|
3cec19ee62 | ||
|
|
236499f9e5 | ||
|
|
42e0e3bf2b | ||
|
|
b0484c9279 | ||
|
|
c7f4307a5d | ||
|
|
ebc9c73ba0 | ||
|
|
2e2f22ad7d | ||
|
|
357c382d96 | ||
|
|
6d4ae81af9 | ||
|
|
d64c7f164f | ||
|
|
6ecc0a7c0a | ||
|
|
d8117a33d3 | ||
|
|
6371373eb5 | ||
|
|
28ac65f1c1 | ||
|
|
5070fb8be0 | ||
|
|
904cfa2995 | ||
|
|
223bd3b7c0 | ||
|
|
4987ee8319 | ||
|
|
4bab4409b9 | ||
|
|
741f49236a | ||
|
|
60b88a42c9 | ||
|
|
49eef29bb5 | ||
|
|
9d3a554bec | ||
|
|
36bd6b944a | ||
|
|
1d9df256a0 | ||
|
|
ad694c0fb0 | ||
|
|
fb4b56513a | ||
|
|
20d5766eb6 | ||
|
|
0920905a0c | ||
|
|
31cb64e496 | ||
|
|
33abc47c7a | ||
|
|
86736d6e43 | ||
|
|
07d67846c1 | ||
|
|
c1aee24445 | ||
|
|
f2b54f5a69 | ||
|
|
3f54665d4e | ||
|
|
548cbc1764 | ||
|
|
9a9511d28f | ||
|
|
acb8fb631b | ||
|
|
d2014633af | ||
|
|
773f6889b4 | ||
|
|
ca4cfa567e | ||
|
|
436869049c | ||
|
|
cf2e617618 | ||
|
|
424474a035 | ||
|
|
531aa1e209 | ||
|
|
83dfdb8ad2 | ||
|
|
6d8fcd90c1 | ||
|
|
aa459c1b66 | ||
|
|
0b23b3e494 | ||
|
|
3f65e1ef41 | ||
|
|
7dce3d8967 | ||
|
|
149f9f992b | ||
|
|
dfb08d9bd2 | ||
|
|
98e771cd01 | ||
|
|
5c8d5bca7f | ||
|
|
5f558aca4e | ||
|
|
facfe0ecaf | ||
|
|
ee53d2f399 | ||
|
|
3c48e1f83e | ||
|
|
19fb7d6776 | ||
|
|
1e7b5322f5 | ||
|
|
205c652f07 | ||
|
|
3de8b8c03c | ||
|
|
4a877b2a8a | ||
|
|
a0bafdc2a7 | ||
|
|
cdef0b75a8 | ||
|
|
e0d3f83ca8 | ||
|
|
87c9e56322 | ||
|
|
554e62574d | ||
|
|
32a3f08dde | ||
|
|
e6e1cad795 | ||
|
|
5e36cb1025 | ||
|
|
8c21a56302 | ||
|
|
031fde6009 | ||
|
|
09ca80add8 | ||
|
|
2d6c2eefa4 | ||
|
|
644d6e12bd | ||
|
|
be7bebd962 | ||
|
|
43c188c1eb | ||
|
|
f745fb3edf | ||
|
|
1302671687 | ||
|
|
9ad0065f6c | ||
|
|
e5303e84cd | ||
|
|
f4fb2ec091 | ||
|
|
b606ec999c | ||
|
|
01ccd695d4 | ||
|
|
cfe5bf4b0c | ||
|
|
7a4e741d1c | ||
|
|
92943b2121 | ||
|
|
69e59efb14 | ||
|
|
5973b438c1 | ||
|
|
c47f44762a | ||
|
|
b138e35f6d | ||
|
|
17459d14e6 | ||
|
|
c87d53eafa | ||
|
|
4d7a290247 | ||
|
|
7eff69f3cb | ||
|
|
34bc650ea8 | ||
|
|
2c53671706 | ||
|
|
3ad381d45b | ||
|
|
d0baaeb9e9 | ||
|
|
d2ac018544 | ||
|
|
d0e5c797cb | ||
|
|
7e501b8a65 | ||
|
|
fe56bdd8a3 | ||
|
|
97ec142262 | ||
|
|
dfc873bc15 | ||
|
|
cca02dd5ff | ||
|
|
91f43bf94c | ||
|
|
0583fe7d32 | ||
|
|
6ca1d73b61 | ||
|
|
4c87861642 | ||
|
|
a2d7517ca0 | ||
|
|
8efaff19ce | ||
|
|
ac056d17af | ||
|
|
fc904827cd | ||
|
|
c240b05369 | ||
|
|
29e594435c | ||
|
|
a2acbe32bf | ||
|
|
29c6237caf | ||
|
|
2d4ccf289c | ||
|
|
f38fb6a15d | ||
|
|
637ccbd250 | ||
|
|
2da4734499 | ||
|
|
aa9e0f8600 | ||
|
|
db4054233b | ||
|
|
3907c6575e | ||
|
|
27e9fab270 | ||
|
|
17e7d40d31 | ||
|
|
54ce8d3699 | ||
|
|
8ab98db556 | ||
|
|
baf5e9f848 | ||
|
|
9d57681ae6 | ||
|
|
3f136726db | ||
|
|
c0a515facf | ||
|
|
01494f75fb | ||
|
|
e6b34786bb | ||
|
|
79b2afb5e5 | ||
|
|
5fcde12193 | ||
|
|
ad68773b9e | ||
|
|
e5255cd373 | ||
|
|
3cf6d5094e | ||
|
|
efcd02de37 | ||
|
|
7eda33eb71 | ||
|
|
1c5fb4a6f8 | ||
|
|
a699dab33d | ||
|
|
0381636044 | ||
|
|
fb3980ce9d |
13
.codebeatsettings
Normal file
13
.codebeatsettings
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"GOLANG": {
|
||||||
|
"ABC":[33, 38, 50, 70],
|
||||||
|
"ARITY":[5,6,7,8],
|
||||||
|
"BLOCK_NESTING":[9, 10, 12, 13],
|
||||||
|
"CYCLO":[30, 35, 45, 60],
|
||||||
|
"TOO_MANY_IVARS": [28, 30, 40, 45],
|
||||||
|
"TOO_MANY_FUNCTIONS": [20, 30, 40, 50],
|
||||||
|
"TOTAL_COMPLEXITY": [150, 250, 400, 500],
|
||||||
|
"LOC": [100, 175, 250, 320],
|
||||||
|
"TOTAL_LOC": [300, 400, 500, 600]
|
||||||
|
}
|
||||||
|
}
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,3 @@
|
|||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
|
.temp/
|
||||||
|
|||||||
72
.travis.yml
Normal file
72
.travis.yml
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- "1.13.x"
|
||||||
|
- "1.14.x"
|
||||||
|
- "tip"
|
||||||
|
|
||||||
|
os:
|
||||||
|
- osx
|
||||||
|
- linux
|
||||||
|
- windows
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Setting environments variables
|
||||||
|
- GO111MODULE=on
|
||||||
|
|
||||||
|
install:
|
||||||
|
- 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"'
|
||||||
|
# Since travis already checks out go build the commandline tool (revel)
|
||||||
|
- mkdir $HOME/GOPATH_PROTECTED
|
||||||
|
- export GOPATH=$HOME/GOPATH_PROTECTED
|
||||||
|
- go build -o $HOME/gopath/bin/revel github.com/revel/cmd/revel
|
||||||
|
- pwd
|
||||||
|
- env
|
||||||
|
script:
|
||||||
|
- go test -v github.com/revel/cmd/revel/...
|
||||||
|
|
||||||
|
# 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 --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 --package revelframework.com -v
|
||||||
|
- revel test --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 -v
|
||||||
|
- revel clean --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 -v
|
||||||
|
- revel build --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 -v -t build/testapp2
|
||||||
|
- revel build --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 -v -t build/testapp2 -m prod
|
||||||
|
- revel package --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 -v
|
||||||
|
- revel package --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 -v -m prod
|
||||||
|
|
||||||
|
- export INITIALWD=$PWD
|
||||||
|
# Check build works with no-vendor flag
|
||||||
|
- cd $GOPATH
|
||||||
|
- export GO111MODULE=auto
|
||||||
|
- revel new -a my/testapp2 --no-vendor -v
|
||||||
|
- revel test -a my/testapp2 -v
|
||||||
|
|
||||||
|
# Check non verbose build, outside of GO path
|
||||||
|
- cd $INITIALWD
|
||||||
|
- revel new --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp3 --package revelframework.com
|
||||||
|
- revel test --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp3
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
- os: windows
|
||||||
50
.vscode/launch.json
vendored
Normal file
50
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Help",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"program": "${workspaceRoot}/revel",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Create new",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "Clean-Test-Project",
|
||||||
|
"mode": "auto",
|
||||||
|
"program": "${workspaceRoot}/revel",
|
||||||
|
"args": ["new", "-v","-a", "${workspaceRoot}/.temp/revel/reveltest", "-p","revel.com/testproject"],
|
||||||
|
"env": {
|
||||||
|
"GOPATH": "${workspaceRoot}/.temp/revel/GOPATH"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Run program",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"program": "${workspaceRoot}/revel",
|
||||||
|
"args": ["run","-v", "-v","-a", "${workspaceRoot}/.temp/revel/reveltest"],
|
||||||
|
"env": {
|
||||||
|
"GOPATH": "${workspaceRoot}/.temp/revel/GOPATH"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Run program Directly",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"program": "${workspaceRoot}/.temp/revel/reveltest/app/tmp/main.go",
|
||||||
|
"args": ["-port=9000","-importPath=revel.com/testproject/reveltest", "-runMode={\"mode\":\"dev\", \"specialUseFlag\":true,\"packagePathMap\":{\"github.com/revel/modules/static\":\"/home/notzippy/go/pkg/mod/github.com/revel/modules@v1.0.0/static\",\"github.com/revel/modules/testrunner\":\"/home/notzippy/go/pkg/mod/github.com/revel/modules@v1.0.0/testrunner\",\"github.com/revel/revel\":\"/home/notzippy/go/pkg/mod/github.com/revel/revel@v1.0.0\",\"revel.com/testproject/reveltest\":\"/mnt/DevSystem/Work/Workareas/revel/revel3/cmd/.temp/revel/reveltest\"}}"],
|
||||||
|
"env": {
|
||||||
|
"GOPATH": "${workspaceRoot}/.temp/revel/GOPATH"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1
.vscode/load.sh
vendored
Executable file
1
.vscode/load.sh
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
http_load -rate 5 -seconds 10 load.web
|
||||||
2
.vscode/load.web
vendored
Normal file
2
.vscode/load.web
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
http://localhost:9000/
|
||||||
|
http://localhost:9000/miss
|
||||||
20
.vscode/tasks.json
vendored
Normal file
20
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Clean-Test-Project",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "rm -rf ${workspaceRoot}/.temp/revel/testproject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Update Go Mod",
|
||||||
|
"type": "shell",
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceRoot}/.temp/revel/testproject"
|
||||||
|
},
|
||||||
|
"command": "go mod tidy && go mod edit -replace github.com/revel/revel => ../../../revel"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
20
LICENSE
Normal file
20
LICENSE
Normal 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.
|
||||||
42
README.md
42
README.md
@@ -1,5 +1,9 @@
|
|||||||
# Revel command line tools
|
# Revel command line tools
|
||||||
|
|
||||||
|
[](http://travis-ci.org/revel/cmd)
|
||||||
|
[](LICENSE)
|
||||||
|
[](https://goreportcard.com/report/github.com/revel/cmd)
|
||||||
|
|
||||||
Provides the `revel` command, used to create and run Revel apps.
|
Provides the `revel` command, used to create and run Revel apps.
|
||||||
|
|
||||||
- More info at http://revel.github.io/manual/tool.html
|
- More info at http://revel.github.io/manual/tool.html
|
||||||
@@ -7,5 +11,41 @@ Provides the `revel` command, used to create and run Revel apps.
|
|||||||
Install
|
Install
|
||||||
------------
|
------------
|
||||||
```bash
|
```bash
|
||||||
go get github.com/revel/cmd/revel
|
go install github.com/revel/cmd/revel@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
New Application
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Create a new application
|
||||||
|
```commandline
|
||||||
|
revel new -a my/app
|
||||||
|
```
|
||||||
|
|
||||||
|
## Community
|
||||||
|
|
||||||
|
* [Gitter](https://gitter.im/revel/community)
|
||||||
|
* [StackOverflow](http://stackoverflow.com/questions/tagged/revel)
|
||||||
|
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
* [Manual, Samples, Godocs, etc](http://revel.github.io)
|
||||||
|
* [Apps using Revel](https://github.com/revel/revel/wiki/Apps-in-the-Wild)
|
||||||
|
* [Articles Featuring Revel](https://github.com/revel/revel/wiki/Articles)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
* [Contributing Code Guidelines](https://github.com/revel/revel/blob/master/CONTRIBUTING.md)
|
||||||
|
* [Revel Contributors](https://github.com/revel/revel/graphs/contributors)
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
[](https://sourcerer.io/fame/notzippy/revel/cmd/links/0)
|
||||||
|
[](https://sourcerer.io/fame/notzippy/revel/cmd/links/1)
|
||||||
|
[](https://sourcerer.io/fame/notzippy/revel/cmd/links/2)
|
||||||
|
[](https://sourcerer.io/fame/notzippy/revel/cmd/links/3)
|
||||||
|
[](https://sourcerer.io/fame/notzippy/revel/cmd/links/4)
|
||||||
|
[](https://sourcerer.io/fame/notzippy/revel/cmd/links/5)
|
||||||
|
[](https://sourcerer.io/fame/notzippy/revel/cmd/links/6)
|
||||||
|
[](https://sourcerer.io/fame/notzippy/revel/cmd/links/7)
|
||||||
|
|||||||
35
go.mod
Normal file
35
go.mod
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
module github.com/revel/cmd
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/BurntSushi/toml v1.0.0 // indirect
|
||||||
|
github.com/agtorre/gocolorize v1.0.0
|
||||||
|
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
|
github.com/go-stack/stack v1.8.0 // indirect
|
||||||
|
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac // indirect
|
||||||
|
github.com/jessevdk/go-flags v1.4.0
|
||||||
|
github.com/mattn/go-colorable v0.1.8
|
||||||
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
|
github.com/myesui/uuid v1.0.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/revel/config v1.0.0
|
||||||
|
github.com/revel/log15 v2.11.20+incompatible
|
||||||
|
github.com/revel/pathtree v0.0.0-20140121041023-41257a1839e9 // indirect
|
||||||
|
github.com/revel/revel v1.0.0
|
||||||
|
github.com/stretchr/testify v1.7.0
|
||||||
|
github.com/twinj/uuid v1.0.0 // indirect
|
||||||
|
github.com/xeonx/timeago v1.0.0-rc4 // indirect
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
|
||||||
|
golang.org/x/tools v0.1.10
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
|
gopkg.in/stack.v0 v0.0.0-20141108040640-9b43fcefddd0
|
||||||
|
gopkg.in/stretchr/testify.v1 v1.2.2 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
|
)
|
||||||
86
go.sum
Normal file
86
go.sum
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
|
||||||
|
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
github.com/agtorre/gocolorize v1.0.0 h1:TvGQd+fAqWQlDjQxSKe//Y6RaxK+RHpEU9X/zPmHW50=
|
||||||
|
github.com/agtorre/gocolorize v1.0.0/go.mod h1:cH6imfTkHVBRJhSOeSeEZhB4zqEYSq0sXuIyehgZMIY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac h1:n1DqxAo4oWPMvH1+v+DLYlMCecgumhhgnxAPdqDIFHI=
|
||||||
|
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||||
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/myesui/uuid v1.0.0 h1:xCBmH4l5KuvLYc5L7AS7SZg9/jKdIFubM7OVoLqaQUI=
|
||||||
|
github.com/myesui/uuid v1.0.0/go.mod h1:2CDfNgU0LR8mIdO8vdWd8i9gWWxLlcoIGGpSNgafq84=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/revel/config v1.0.0 h1:UAzLPQ+x9nJeP6a+H93G+AKEosg3OO2oVLBXK9oSN2U=
|
||||||
|
github.com/revel/config v1.0.0/go.mod h1:GT4a9px5kDGRqLizcw/md0QFErrhen76toz4qS3oIoI=
|
||||||
|
github.com/revel/log15 v2.11.20+incompatible h1:JkA4tbwIo/UGEMumY50zndKq816RQW3LQ0wIpRc+32U=
|
||||||
|
github.com/revel/log15 v2.11.20+incompatible/go.mod h1:l0WmLRs+IM1hBl4noJiBc2tZQiOgZyXzS1mdmFt+5Gc=
|
||||||
|
github.com/revel/pathtree v0.0.0-20140121041023-41257a1839e9 h1:/d6kfjzjyx19ieWqMOXHSTLFuRxLOH15ZubtcAXExKw=
|
||||||
|
github.com/revel/pathtree v0.0.0-20140121041023-41257a1839e9/go.mod h1:TmlwoRLDvgRjoTe6rbsxIaka/CulzYrgfef7iNJcEWY=
|
||||||
|
github.com/revel/revel v1.0.0 h1:BsPFnKuuzXEkPtrjdjZHiDcvDmbBiBQvh7Z5c6kLb/Y=
|
||||||
|
github.com/revel/revel v1.0.0/go.mod h1:VZWJnHjpDEtuGUuZJ2NO42XryitrtwsdVaJxfDeo5yc=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk=
|
||||||
|
github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY=
|
||||||
|
github.com/xeonx/timeago v1.0.0-rc4 h1:9rRzv48GlJC0vm+iBpLcWAr8YbETyN9Vij+7h2ammz4=
|
||||||
|
github.com/xeonx/timeago v1.0.0-rc4/go.mod h1:qDLrYEFynLO7y5Ho7w3GwgtYgpy5UfhcXIIQvMKVDkA=
|
||||||
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
|
||||||
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
|
||||||
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||||
|
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
|
gopkg.in/stack.v0 v0.0.0-20141108040640-9b43fcefddd0 h1:lMH45EKqD8Nf6LwoF+43YOKjOAEEHQRVgDyG8RCV4MU=
|
||||||
|
gopkg.in/stack.v0 v0.0.0-20141108040640-9b43fcefddd0/go.mod h1:kl/bNzW/jgTgUOCGDj3XPn9/Hbfhw6pjfBRUnaTioFQ=
|
||||||
|
gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M=
|
||||||
|
gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
167
harness/app.go
167
harness/app.go
@@ -6,32 +6,46 @@ package harness
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Error is used for constant errors.
|
||||||
|
type Error string
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ErrTimedOut Error = "app timed out"
|
||||||
|
|
||||||
// App contains the configuration for running a Revel app. (Not for the app itself)
|
// App contains the configuration for running a Revel app. (Not for the app itself)
|
||||||
// Its only purpose is constructing the command to execute.
|
// Its only purpose is constructing the command to execute.
|
||||||
type App struct {
|
type App struct {
|
||||||
BinaryPath string // Path to the app executable
|
BinaryPath string // Path to the app executable
|
||||||
Port int // Port to pass as a command line argument.
|
Port int // Port to pass as a command line argument.
|
||||||
cmd AppCmd // The last cmd returned.
|
cmd AppCmd // The last cmd returned.
|
||||||
|
PackagePathMap map[string]string // Package to directory path map
|
||||||
|
Paths *model.RevelContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApp returns app instance with binary path in it
|
// NewApp returns app instance with binary path in it.
|
||||||
func NewApp(binPath string) *App {
|
func NewApp(binPath string, paths *model.RevelContainer, packagePathMap map[string]string) *App {
|
||||||
return &App{BinaryPath: binPath}
|
return &App{BinaryPath: binPath, Paths: paths, Port: paths.HTTPPort, PackagePathMap: packagePathMap}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cmd returns a command to run the app server using the current configuration.
|
// Cmd returns a command to run the app server using the current configuration.
|
||||||
func (a *App) Cmd() AppCmd {
|
func (a *App) Cmd(runMode string) AppCmd {
|
||||||
a.cmd = NewAppCmd(a.BinaryPath, a.Port)
|
a.cmd = NewAppCmd(a.BinaryPath, a.Port, runMode, a.Paths)
|
||||||
return a.cmd
|
return a.cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,82 +60,159 @@ type AppCmd struct {
|
|||||||
*exec.Cmd
|
*exec.Cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAppCmd returns the AppCmd with parameters initialized for running app
|
// 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,
|
cmd := exec.Command(binPath,
|
||||||
fmt.Sprintf("-port=%d", port),
|
fmt.Sprintf("-port=%d", port),
|
||||||
fmt.Sprintf("-importPath=%s", revel.ImportPath),
|
fmt.Sprintf("-importPath=%s", paths.ImportPath),
|
||||||
fmt.Sprintf("-runMode=%s", revel.RunMode))
|
fmt.Sprintf("-runMode=%s", runMode))
|
||||||
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||||
return AppCmd{cmd}
|
return AppCmd{cmd}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the app server, and wait until it is ready to serve requests.
|
// Start the app server, and wait until it is ready to serve requests.
|
||||||
func (cmd AppCmd) Start() error {
|
func (cmd AppCmd) Start(c *model.CommandConfig) error {
|
||||||
listeningWriter := startupListeningWriter{os.Stdout, make(chan bool)}
|
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool), c, &bytes.Buffer{}}
|
||||||
cmd.Stdout = listeningWriter
|
cmd.Stdout = listeningWriter
|
||||||
revel.TRACE.Println("Exec app:", cmd.Path, cmd.Args)
|
cmd.Stderr = listeningWriter
|
||||||
|
utils.CmdInit(cmd.Cmd, !c.Vendored, c.AppPath)
|
||||||
|
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args, "dir", cmd.Dir, "env", cmd.Env)
|
||||||
if err := cmd.Cmd.Start(); err != nil {
|
if err := cmd.Cmd.Start(); err != nil {
|
||||||
revel.ERROR.Fatalln("Error running:", err)
|
utils.Logger.Fatal("Error running:", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-cmd.waitChan():
|
case exitState := <-cmd.waitChan():
|
||||||
return errors.New("revel/harness: app died")
|
fmt.Println("Startup failure view previous messages, \n Proxy is listening :", c.Run.Port)
|
||||||
|
err := utils.NewError("", "Revel Run Error", "starting your application there was an exception. See terminal output, "+exitState, "")
|
||||||
|
atomic.SwapInt32(&startupError, 1)
|
||||||
|
// TODO pretiffy command line output
|
||||||
|
err.Stack = listeningWriter.buffer.String()
|
||||||
|
return err
|
||||||
|
|
||||||
case <-time.After(30 * time.Second):
|
case <-time.After(60 * time.Second):
|
||||||
|
println("Revel proxy is listening, point your browser to :", c.Run.Port)
|
||||||
|
utils.Logger.Error("Killing revel server process did not respond after wait timeout.", "processid", cmd.Process.Pid)
|
||||||
cmd.Kill()
|
cmd.Kill()
|
||||||
return errors.New("revel/harness: app timed out")
|
|
||||||
|
return fmt.Errorf("revel/harness: %w", ErrTimedOut)
|
||||||
|
|
||||||
case <-listeningWriter.notifyReady:
|
case <-listeningWriter.notifyReady:
|
||||||
|
println("Revel proxy is listening, point your browser to :", c.Run.Port)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove this unreachable code and document it
|
|
||||||
panic("Impossible")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the app server inline. Never returns.
|
// Run the app server inline. Never returns.
|
||||||
func (cmd AppCmd) Run() {
|
func (cmd AppCmd) Run(c *model.CommandConfig) {
|
||||||
revel.TRACE.Println("Exec app:", cmd.Path, cmd.Args)
|
utils.CmdInit(cmd.Cmd, !c.Vendored, c.AppPath)
|
||||||
|
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
||||||
if err := cmd.Cmd.Run(); err != nil {
|
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.
|
// Kill terminates the app server if it's running.
|
||||||
func (cmd AppCmd) Kill() {
|
func (cmd AppCmd) Kill() {
|
||||||
if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) {
|
if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) {
|
||||||
revel.TRACE.Println("Killing revel server pid", cmd.Process.Pid)
|
// Windows appears to send the kill to all threads, shutting down the
|
||||||
err := cmd.Process.Kill()
|
// server before this can, this check will ensure the process is still running
|
||||||
if err != nil {
|
if _, err := os.FindProcess(cmd.Process.Pid); err != nil {
|
||||||
revel.ERROR.Fatalln("Failed to kill revel server:", err)
|
// Server has already exited
|
||||||
|
utils.Logger.Info("Server not running revel server pid", "pid", cmd.Process.Pid)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for the shutdown channel
|
||||||
|
waitMutex := &sync.WaitGroup{}
|
||||||
|
waitMutex.Add(1)
|
||||||
|
ch := make(chan bool, 1)
|
||||||
|
go func() {
|
||||||
|
waitMutex.Done()
|
||||||
|
s, err := cmd.Process.Wait()
|
||||||
|
defer func() {
|
||||||
|
ch <- true
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Info("Wait failed for process ", "error", err)
|
||||||
|
}
|
||||||
|
if s != nil {
|
||||||
|
utils.Logger.Info("Revel App exited", "state", s.String())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Wait for the channel to begin waiting
|
||||||
|
waitMutex.Wait()
|
||||||
|
|
||||||
|
// Send an interrupt signal to allow for a graceful shutdown
|
||||||
|
utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid)
|
||||||
|
|
||||||
|
err := cmd.Process.Signal(os.Interrupt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Info(
|
||||||
|
"Revel app already exited.",
|
||||||
|
"processid", cmd.Process.Pid, "error", err,
|
||||||
|
"killerror", cmd.Process.Kill())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a timer to ensure that the process exits
|
||||||
|
utils.Logger.Info("Waiting to exit")
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
return
|
||||||
|
case <-time.After(60 * time.Second):
|
||||||
|
// Kill the process
|
||||||
|
utils.Logger.Error(
|
||||||
|
"Revel app failed to exit in 60 seconds - killing.",
|
||||||
|
"processid", cmd.Process.Pid,
|
||||||
|
"killerror", cmd.Process.Kill())
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Logger.Info("Done Waiting to exit")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a channel that is notified when Wait() returns.
|
// Return a channel that is notified when Wait() returns.
|
||||||
func (cmd AppCmd) waitChan() <-chan struct{} {
|
func (cmd AppCmd) waitChan() <-chan string {
|
||||||
ch := make(chan struct{}, 1)
|
ch := make(chan string, 1)
|
||||||
go func() {
|
go func() {
|
||||||
_ = cmd.Wait()
|
_ = cmd.Wait()
|
||||||
ch <- struct{}{}
|
state := cmd.ProcessState
|
||||||
|
exitStatus := " unknown "
|
||||||
|
if state != nil {
|
||||||
|
exitStatus = state.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
ch <- exitStatus
|
||||||
}()
|
}()
|
||||||
return ch
|
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)
|
// 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.
|
// This is super ghetto, but by far the simplest thing that should work.
|
||||||
type startupListeningWriter struct {
|
type startupListeningWriter struct {
|
||||||
dest io.Writer
|
dest io.Writer
|
||||||
notifyReady chan bool
|
notifyReady chan bool
|
||||||
|
c *model.CommandConfig
|
||||||
|
buffer *bytes.Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w startupListeningWriter) Write(p []byte) (n int, err error) {
|
// Writes to this output stream.
|
||||||
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 <- true
|
||||||
w.notifyReady = nil
|
w.notifyReady = nil
|
||||||
}
|
}
|
||||||
|
if w.c.HistoricMode {
|
||||||
|
if w.notifyReady != nil && bytes.Contains(p, []byte("Listening on")) {
|
||||||
|
w.notifyReady <- true
|
||||||
|
w.notifyReady = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if w.notifyReady != nil {
|
||||||
|
w.buffer.Write(p)
|
||||||
|
}
|
||||||
return w.dest.Write(p)
|
return w.dest.Write(p)
|
||||||
}
|
}
|
||||||
|
|||||||
401
harness/build.go
Executable file → Normal file
401
harness/build.go
Executable file → Normal file
@@ -13,62 +13,100 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/parser"
|
||||||
|
"github.com/revel/cmd/parser2"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var importErrorPattern = regexp.MustCompile("cannot find package \"([^\"]+)\"")
|
var importErrorPattern = regexp.MustCompile("cannot find package \"([^\"]+)\"")
|
||||||
|
var importErrorPattern2 = regexp.MustCompile("no required module provides package ([^;]+)+")
|
||||||
|
var addPackagePattern = regexp.MustCompile(`to add:\n\tgo get (.*)\n`)
|
||||||
|
|
||||||
|
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:
|
// Build the app:
|
||||||
// 1. Generate the the main.go file.
|
// 1. Generate the the main.go file.
|
||||||
// 2. Run the appropriate "go build" command.
|
// 2. Run the appropriate "go build" command.
|
||||||
// Requires that revel.Init has been called previously.
|
// 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.
|
// 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, err error) {
|
||||||
// First, clear the generated files (to avoid them messing with ProcessSource).
|
// First, clear the generated files (to avoid them messing with ProcessSource).
|
||||||
cleanSource("tmp", "routes")
|
cleanSource(paths, "tmp", "routes")
|
||||||
|
|
||||||
sourceInfo, compileError := ProcessSource(revel.CodePaths)
|
var sourceInfo *model.SourceInfo
|
||||||
if compileError != nil {
|
|
||||||
return nil, compileError
|
if c.HistoricBuildMode {
|
||||||
|
sourceInfo, err = parser.ProcessSource(paths)
|
||||||
|
} else {
|
||||||
|
sourceInfo, err = parser2.ProcessSource(paths)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the db.import to the import paths.
|
// Add the db.import to the import paths.
|
||||||
if dbImportPath, found := revel.Config.String("db.import"); found {
|
if dbImportPath, found := paths.Config.String("db.import"); found {
|
||||||
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, dbImportPath)
|
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.
|
// Generate two source files.
|
||||||
templateArgs := map[string]interface{}{
|
templateArgs := map[string]interface{}{
|
||||||
"Controllers": sourceInfo.ControllerSpecs(),
|
"ImportPath": paths.ImportPath,
|
||||||
|
"Controllers": controllers,
|
||||||
"ValidationKeys": sourceInfo.ValidationKeys,
|
"ValidationKeys": sourceInfo.ValidationKeys,
|
||||||
"ImportPaths": calcImportAliases(sourceInfo),
|
"ImportPaths": calcImportAliases(sourceInfo),
|
||||||
"TestSuites": sourceInfo.TestSuites(),
|
"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")
|
||||||
|
|
||||||
|
if err = genSource(paths, "tmp", "main.go", RevelMainTemplate, templateArgs); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = genSource(paths, filepath.Join("tmp", "run"), "run.go", RevelRunTemplate, templateArgs); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = genSource(paths, "routes", "routes.go", RevelRoutesTemplate, templateArgs); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Read build config.
|
// Read build config.
|
||||||
buildTags := revel.Config.StringDefault("build.tags", "")
|
buildTags := paths.Config.StringDefault("build.tags", "")
|
||||||
|
|
||||||
// Build the user program (all code under app).
|
// Build the user program (all code under app).
|
||||||
// It relies on the user having "go" installed.
|
// It relies on the user having "go" installed.
|
||||||
goPath, err := exec.LookPath("go")
|
goPath, err := exec.LookPath("go")
|
||||||
if err != nil {
|
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)
|
// Binary path is a combination of target/app directory, app's import path and its name.
|
||||||
if err != nil {
|
binName := filepath.Join("target", "app", paths.ImportPath, filepath.Base(paths.BasePath))
|
||||||
revel.ERROR.Fatalln("Failure importing", revel.ImportPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Binary path is a combination of $GOBIN/revel.d directory, app's import path and its name.
|
|
||||||
binName := filepath.Join(pkg.BinDir, "revel.d", revel.ImportPath, filepath.Base(revel.BasePath))
|
|
||||||
|
|
||||||
// Change binary path for Windows build
|
// Change binary path for Windows build
|
||||||
goos := runtime.GOOS
|
goos := runtime.GOOS
|
||||||
@@ -80,72 +118,133 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gotten := make(map[string]struct{})
|
gotten := make(map[string]struct{})
|
||||||
|
contains := func(s []string, e string) bool {
|
||||||
|
for _, a := range s {
|
||||||
|
if a == e {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.GoModFlags) > 0 {
|
||||||
|
for _, gomod := range c.GoModFlags {
|
||||||
|
goModCmd := exec.Command(goPath, append([]string{"mod"}, strings.Split(gomod, " ")...)...)
|
||||||
|
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
|
||||||
|
output, err := goModCmd.CombinedOutput()
|
||||||
|
utils.Logger.Info("Gomod applied ", "output", string(output))
|
||||||
|
|
||||||
|
// If the build succeeded, we're done.
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Error("Gomod Failed continuing ", "error", err, "output", string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
appVersion := getAppVersion()
|
appVersion := getAppVersion(paths)
|
||||||
|
if appVersion == "" {
|
||||||
|
appVersion = "noVersionProvided"
|
||||||
|
}
|
||||||
|
|
||||||
buildTime := time.Now().UTC().Format(time.RFC3339)
|
buildTime := time.Now().UTC().Format(time.RFC3339)
|
||||||
versionLinkerFlags := fmt.Sprintf("-X %s/app.AppVersion=%s -X %s/app.BuildTime=%s",
|
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
|
// Append any build flags specified, they will override existing flags
|
||||||
goVersion, err := strconv.ParseFloat(runtime.Version()[2:5], 64)
|
flags := []string{}
|
||||||
// runtime.Version() may return commit hash, we assume it is above 1.5
|
if len(c.BuildFlags) == 0 {
|
||||||
if goVersion < 1.5 && err == nil {
|
flags = []string{
|
||||||
versionLinkerFlags = fmt.Sprintf("-X %s/app.AppVersion \"%s\" -X %s/app.BuildTime \"%s\"",
|
"build",
|
||||||
revel.ImportPath, appVersion, revel.ImportPath, buildTime)
|
"-ldflags", versionLinkerFlags,
|
||||||
|
"-tags", buildTags,
|
||||||
|
"-o", binName,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !contains(c.BuildFlags, "build") {
|
||||||
|
flags = []string{"build"}
|
||||||
|
}
|
||||||
|
if !contains(flags, "-ldflags") {
|
||||||
|
ldflags := "-ldflags= " + versionLinkerFlags
|
||||||
|
// Add user defined build flags
|
||||||
|
for i := range c.BuildFlags {
|
||||||
|
ldflags += " -X '" + c.BuildFlags[i] + "'"
|
||||||
|
}
|
||||||
|
flags = append(flags, ldflags)
|
||||||
|
}
|
||||||
|
if !contains(flags, "-tags") && buildTags != "" {
|
||||||
|
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...)
|
|
||||||
|
|
||||||
// This is Go main path
|
|
||||||
// Note: It's not applicable for filepath.* usage
|
// 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...)
|
buildCmd := exec.Command(goPath, flags...)
|
||||||
revel.TRACE.Println("Exec:", buildCmd.Args)
|
if !c.Vendored {
|
||||||
|
// This is Go main path
|
||||||
|
gopath := c.GoPath
|
||||||
|
for _, o := range paths.ModulePathMap {
|
||||||
|
gopath += string(filepath.ListSeparator) + o.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
buildCmd.Env = append(os.Environ(),
|
||||||
|
"GOPATH="+gopath,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
utils.CmdInit(buildCmd, !c.Vendored, c.AppPath)
|
||||||
|
|
||||||
|
utils.Logger.Info("Exec:", "args", buildCmd.Args, "working dir", buildCmd.Dir)
|
||||||
output, err := buildCmd.CombinedOutput()
|
output, err := buildCmd.CombinedOutput()
|
||||||
|
|
||||||
// If the build succeeded, we're done.
|
// If the build succeeded, we're done.
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return NewApp(binName), nil
|
utils.Logger.Info("Build successful continuing")
|
||||||
|
return NewApp(binName, paths, sourceInfo.PackageMap), nil
|
||||||
}
|
}
|
||||||
revel.ERROR.Println(string(output))
|
|
||||||
|
// Since there was an error, capture the output in case we need to report it
|
||||||
|
stOutput := string(output)
|
||||||
|
utils.Logger.Infof("Got error on build of app %s", stOutput)
|
||||||
|
|
||||||
// See if it was an import error that we can go get.
|
// See if it was an import error that we can go get.
|
||||||
matches := importErrorPattern.FindStringSubmatch(string(output))
|
matches := importErrorPattern.FindAllStringSubmatch(stOutput, -1)
|
||||||
if matches == nil {
|
if matches == nil {
|
||||||
return nil, newCompileError(output)
|
matches = importErrorPattern2.FindAllStringSubmatch(stOutput, -1)
|
||||||
}
|
}
|
||||||
|
if matches == nil {
|
||||||
|
matches = addPackagePattern.FindAllStringSubmatch(stOutput, -1)
|
||||||
|
|
||||||
// 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{}{}
|
utils.Logger.Info("Build failed checking for missing imports", "message", stOutput, "missing_imports", len(matches))
|
||||||
|
if matches == nil {
|
||||||
// Execute "go get <pkg>"
|
utils.Logger.Info("Build failed no missing imports", "message", stOutput)
|
||||||
getCmd := exec.Command(goPath, "get", pkgName)
|
return nil, newCompileError(paths, output)
|
||||||
revel.TRACE.Println("Exec:", getCmd.Args)
|
}
|
||||||
getOutput, err := getCmd.CombinedOutput()
|
utils.Logger.Warn("Detected missing packages, importing them", "packages", len(matches))
|
||||||
if err != nil {
|
for _, match := range matches {
|
||||||
revel.ERROR.Println(string(getOutput))
|
// Ensure we haven't already tried to go get it.
|
||||||
return nil, newCompileError(output)
|
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{}{}
|
||||||
|
if err := c.PackageResolver(pkgName); err != nil {
|
||||||
|
panic("failed to resolve")
|
||||||
|
utils.Logger.Error("Unable to resolve package", "package", pkgName, "error", err)
|
||||||
|
return nil, newCompileError(paths, []byte(err.Error()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success getting the import, attempt to build again.
|
// Success getting the import, attempt to build again.
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove this unreachable code and document it
|
// unreachable
|
||||||
revel.ERROR.Fatalf("Not reachable")
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to define a version string for the compiled app
|
// Try to define a version string for the compiled app
|
||||||
@@ -154,7 +253,7 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
|||||||
// variable
|
// variable
|
||||||
// - Read the output of "git describe" if the source is in a git repository
|
// - 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.
|
// 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 != "" {
|
if version := os.Getenv("APP_VERSION"); version != "" {
|
||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
@@ -162,17 +261,16 @@ func getAppVersion() string {
|
|||||||
// Check for the git binary
|
// Check for the git binary
|
||||||
if gitPath, err := exec.LookPath("git"); err == nil {
|
if gitPath, err := exec.LookPath("git"); err == nil {
|
||||||
// Check for the .git directory
|
// Check for the .git directory
|
||||||
gitDir := filepath.Join(revel.BasePath, ".git")
|
gitDir := filepath.Join(paths.BasePath, ".git")
|
||||||
info, err := os.Stat(gitDir)
|
info, err := os.Stat(gitDir)
|
||||||
if (err != nil && os.IsNotExist(err)) || !info.IsDir() {
|
if (err != nil && os.IsNotExist(err)) || !info.IsDir() {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
gitCmd := exec.Command(gitPath, "--git-dir="+gitDir, "describe", "--always", "--dirty")
|
gitCmd := exec.Command(gitPath, "--git-dir="+gitDir, "--work-tree="+paths.BasePath, "describe", "--always", "--dirty")
|
||||||
revel.TRACE.Println("Exec:", gitCmd.Args)
|
utils.Logger.Info("Exec:", "args", gitCmd.Args)
|
||||||
output, err := gitCmd.Output()
|
output, err := gitCmd.Output()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.WARN.Println("Cannot determine git repository version:", err)
|
utils.Logger.Error("Cannot determine git repository version:", "error", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,19 +280,19 @@ func getAppVersion() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanSource(dirs ...string) {
|
func cleanSource(paths *model.RevelContainer, dirs ...string) {
|
||||||
for _, dir := range dirs {
|
for _, dir := range dirs {
|
||||||
cleanDir(dir)
|
cleanDir(paths, dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanDir(dir string) {
|
func cleanDir(paths *model.RevelContainer, dir string) {
|
||||||
revel.INFO.Println("Cleaning dir " + dir)
|
utils.Logger.Info("Cleaning dir ", "dir", dir)
|
||||||
tmpPath := filepath.Join(revel.AppPath, dir)
|
tmpPath := filepath.Join(paths.AppPath, dir)
|
||||||
f, err := os.Open(tmpPath)
|
f, err := os.Open(tmpPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
revel.ERROR.Println("Failed to clean dir:", err)
|
utils.Logger.Error("Failed to clean dir:", "error", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -204,20 +302,20 @@ func cleanDir(dir string) {
|
|||||||
infos, err := f.Readdir(0)
|
infos, err := f.Readdir(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
revel.ERROR.Println("Failed to clean dir:", err)
|
utils.Logger.Fatal("Failed to clean dir:", "error", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, info := range infos {
|
for _, info := range infos {
|
||||||
path := filepath.Join(tmpPath, info.Name())
|
pathName := filepath.Join(tmpPath, info.Name())
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
err := os.RemoveAll(path)
|
err := os.RemoveAll(pathName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.ERROR.Println("Failed to remove dir:", err)
|
utils.Logger.Fatal("Failed to remove dir:", "error", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err := os.Remove(path)
|
err := os.Remove(pathName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.ERROR.Println("Failed to remove file:", err)
|
utils.Logger.Fatal("Failed to remove file:", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,39 +325,16 @@ func cleanDir(dir string) {
|
|||||||
|
|
||||||
// genSource renders the given template to produce source code, which it writes
|
// genSource renders the given template to produce source code, which it writes
|
||||||
// to the given directory and file.
|
// to the given directory and file.
|
||||||
func genSource(dir, filename, templateSource string, args map[string]interface{}) {
|
func genSource(paths *model.RevelContainer, dir, filename, templateSource string, args map[string]interface{}) error {
|
||||||
sourceCode := revel.ExecuteTemplate(
|
return utils.GenerateTemplate(filepath.Join(paths.AppPath, dir, filename), templateSource, args)
|
||||||
template.Must(template.New("").Parse(templateSource)),
|
|
||||||
args)
|
|
||||||
|
|
||||||
// Create a fresh dir.
|
|
||||||
cleanSource(dir)
|
|
||||||
tmpPath := filepath.Join(revel.AppPath, dir)
|
|
||||||
err := os.Mkdir(tmpPath, 0777)
|
|
||||||
if err != nil && !os.IsExist(err) {
|
|
||||||
revel.ERROR.Fatalf("Failed to make '%v' directory: %v", dir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the file
|
|
||||||
file, err := os.Create(filepath.Join(tmpPath, filename))
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Looks through all the method args and returns a set of unique import paths
|
// Looks through all the method args and returns a set of unique import paths
|
||||||
// that cover all the method arg types.
|
// that cover all the method arg types.
|
||||||
// Additionally, assign package aliases when necessary to resolve ambiguity.
|
// 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)
|
aliases := make(map[string]string)
|
||||||
typeArrays := [][]*TypeInfo{src.ControllerSpecs(), src.TestSuites()}
|
typeArrays := [][]*model.TypeInfo{src.ControllerSpecs(), src.TestSuites()}
|
||||||
for _, specs := range typeArrays {
|
for _, specs := range typeArrays {
|
||||||
for _, spec := range specs {
|
for _, spec := range specs {
|
||||||
addAlias(aliases, spec.ImportPath, spec.PackageName)
|
addAlias(aliases, spec.ImportPath, spec.PackageName)
|
||||||
@@ -286,25 +361,27 @@ func calcImportAliases(src *SourceInfo) map[string]string {
|
|||||||
return aliases
|
return aliases
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds an alias to the map of alias names.
|
||||||
func addAlias(aliases map[string]string, importPath, pkgName string) {
|
func addAlias(aliases map[string]string, importPath, pkgName string) {
|
||||||
alias, ok := aliases[importPath]
|
_, ok := aliases[importPath]
|
||||||
if ok {
|
if ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
alias = makePackageAlias(aliases, pkgName)
|
aliases[importPath] = makePackageAlias(aliases, pkgName)
|
||||||
aliases[importPath] = alias
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generates a package alias.
|
||||||
func makePackageAlias(aliases map[string]string, pkgName string) string {
|
func makePackageAlias(aliases map[string]string, pkgName string) string {
|
||||||
i := 0
|
i := 0
|
||||||
alias := pkgName
|
alias := pkgName
|
||||||
for containsValue(aliases, alias) || alias=="revel" {
|
for containsValue(aliases, alias) || alias == "revel" {
|
||||||
alias = fmt.Sprintf("%s%d", pkgName, i)
|
alias = fmt.Sprintf("%s%d", pkgName, i)
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
return alias
|
return alias
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if this value is in the map.
|
||||||
func containsValue(m map[string]string, val string) bool {
|
func containsValue(m map[string]string, val string) bool {
|
||||||
for _, v := range m {
|
for _, v := range m {
|
||||||
if v == val {
|
if v == val {
|
||||||
@@ -316,15 +393,15 @@ func containsValue(m map[string]string, val string) bool {
|
|||||||
|
|
||||||
// Parse the output of the "go build" command.
|
// Parse the output of the "go build" command.
|
||||||
// Return a detailed Error.
|
// Return a detailed Error.
|
||||||
func newCompileError(output []byte) *revel.Error {
|
func newCompileError(paths *model.RevelContainer, output []byte) *utils.SourceError {
|
||||||
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
|
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
|
||||||
FindSubmatch(output)
|
FindSubmatch(output)
|
||||||
if errorMatch == nil {
|
if errorMatch == nil {
|
||||||
errorMatch = regexp.MustCompile(`(?m)^(.*?)\:(\d+)\:\s(.*?)$`).FindSubmatch(output)
|
errorMatch = regexp.MustCompile(`(?m)^(.*?):(\d+):\s(.*?)$`).FindSubmatch(output)
|
||||||
|
|
||||||
if errorMatch == nil {
|
if errorMatch == nil {
|
||||||
revel.ERROR.Println("Failed to parse build errors:\n", string(output))
|
utils.Logger.Error("Failed to parse build errors", "error", string(output))
|
||||||
return &revel.Error{
|
return &utils.SourceError{
|
||||||
SourceType: "Go code",
|
SourceType: "Go code",
|
||||||
Title: "Go Compilation Error",
|
Title: "Go Compilation Error",
|
||||||
Description: "See console for build error.",
|
Description: "See console for build error.",
|
||||||
@@ -333,16 +410,31 @@ func newCompileError(output []byte) *revel.Error {
|
|||||||
|
|
||||||
errorMatch = append(errorMatch, errorMatch[3])
|
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, "src", paths.ImportPath, relFilename)
|
||||||
|
println(newPath)
|
||||||
|
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.
|
// Read the source for the offending file.
|
||||||
var (
|
var (
|
||||||
relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go"
|
relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go"
|
||||||
absFilename, _ = filepath.Abs(relFilename)
|
absFilename = findInPaths(relFilename)
|
||||||
line, _ = strconv.Atoi(string(errorMatch[2]))
|
line, _ = strconv.Atoi(string(errorMatch[2]))
|
||||||
description = string(errorMatch[4])
|
description = string(errorMatch[4])
|
||||||
compileError = &revel.Error{
|
compileError = &utils.SourceError{
|
||||||
SourceType: "Go code",
|
SourceType: "Go code",
|
||||||
Title: "Go Compilation Error",
|
Title: "Go Compilation Error",
|
||||||
Path: relFilename,
|
Path: relFilename,
|
||||||
@@ -351,16 +443,16 @@ func newCompileError(output []byte) *revel.Error {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
errorLink := revel.Config.StringDefault("error.link", "")
|
errorLink := paths.Config.StringDefault("error.link", "")
|
||||||
|
|
||||||
if errorLink != "" {
|
if errorLink != "" {
|
||||||
compileError.SetLink(errorLink)
|
compileError.SetLink(errorLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileStr, err := revel.ReadLines(absFilename)
|
fileStr, err := utils.ReadLines(absFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
compileError.MetaError = absFilename + ": " + err.Error()
|
compileError.MetaError = absFilename + ": " + err.Error()
|
||||||
revel.ERROR.Println(compileError.MetaError)
|
utils.Logger.Info("Unable to readlines "+compileError.MetaError, "error", err)
|
||||||
return compileError
|
return compileError
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,12 +460,14 @@ func newCompileError(output []byte) *revel.Error {
|
|||||||
return compileError
|
return compileError
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevelMainTemplate template for app/tmp/main.go
|
// RevelMainTemplate template for app/tmp/run/run.go.
|
||||||
const RevelMainTemplate = `// GENERATED CODE - DO NOT EDIT
|
const RevelRunTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||||
package main
|
// 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 (
|
import (
|
||||||
"flag"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"github.com/revel/revel"{{range $k, $v := $.ImportPaths}}
|
"github.com/revel/revel"{{range $k, $v := $.ImportPaths}}
|
||||||
{{$v}} "{{$k}}"{{end}}
|
{{$v}} "{{$k}}"{{end}}
|
||||||
@@ -381,19 +475,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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...
|
// So compiler won't complain if the generated code doesn't reference reflect package...
|
||||||
_ = reflect.Invalid
|
_ = reflect.Invalid
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
// Register and run the application
|
||||||
flag.Parse()
|
func Run(port int) {
|
||||||
revel.Init(*runMode, *importPath, *srcPath)
|
Register()
|
||||||
revel.INFO.Println("Running revel server")
|
revel.Run(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register all the controllers
|
||||||
|
func Register() {
|
||||||
|
revel.AppLog.Info("Running revel server")
|
||||||
{{range $i, $c := .Controllers}}
|
{{range $i, $c := .Controllers}}
|
||||||
revel.RegisterController((*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),
|
revel.RegisterController((*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),
|
||||||
[]*revel.MethodType{
|
[]*revel.MethodType{
|
||||||
@@ -419,13 +513,40 @@ func main() {
|
|||||||
testing.TestSuites = []interface{}{ {{range .TestSuites}}
|
testing.TestSuites = []interface{}{ {{range .TestSuites}}
|
||||||
(*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),{{end}}
|
(*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),{{end}}
|
||||||
}
|
}
|
||||||
|
|
||||||
revel.Run(*port)
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
// RevelRoutesTemplate template for app/conf/routes
|
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
|
||||||
|
|
||||||
|
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
|
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
|
package routes
|
||||||
|
|
||||||
import "github.com/revel/revel"
|
import "github.com/revel/revel"
|
||||||
|
|||||||
@@ -15,9 +15,13 @@ package harness
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
@@ -26,33 +30,97 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"github.com/revel/cmd/watcher"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
watcher *revel.Watcher
|
|
||||||
doNotWatch = []string{"tmp", "views", "routes"}
|
doNotWatch = []string{"tmp", "views", "routes"}
|
||||||
|
|
||||||
lastRequestHadError int32
|
lastRequestHadError int32
|
||||||
|
startupError int32
|
||||||
|
startupErrorText error
|
||||||
)
|
)
|
||||||
|
|
||||||
// Harness reverse proxies requests to the application server.
|
// Harness reverse proxies requests to the application server.
|
||||||
// It builds / runs / rebuilds / restarts the server when code is changed.
|
// It builds / runs / rebuilds / restarts the server when code is changed.
|
||||||
type Harness struct {
|
type Harness struct {
|
||||||
app *App
|
app *App // The application
|
||||||
serverHost string
|
useProxy bool // True if proxy is in use
|
||||||
port int
|
serverHost string // The proxy server host
|
||||||
proxy *httputil.ReverseProxy
|
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
|
||||||
|
ranOnce bool // True app compiled once
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderError(iw http.ResponseWriter, ir *http.Request, err error) {
|
func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err error) {
|
||||||
context := revel.NewGOContext(nil)
|
// Render error here
|
||||||
context.Request.SetRequest(ir)
|
// Grab the template from three places
|
||||||
context.Response.SetResponse(iw)
|
// 1) Application/views/errors
|
||||||
c := revel.NewController(context)
|
// 2) revel_home/views/errors
|
||||||
c.RenderError(err).Apply(c.Request, c.Response)
|
// 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 occurred %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var revelError *utils.SourceError
|
||||||
|
|
||||||
|
if !errors.As(err, &revelError) {
|
||||||
|
revelError = &utils.SourceError{
|
||||||
|
Title: "Server Error",
|
||||||
|
Description: err.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP handles all requests.
|
// ServeHTTP handles all requests.
|
||||||
@@ -65,18 +133,23 @@ func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Flush any change events and rebuild app if necessary.
|
// Flush any change events and rebuild app if necessary.
|
||||||
// Render an error page if the rebuild / restart failed.
|
// Render an error page if the rebuild / restart failed.
|
||||||
err := watcher.Notify()
|
err := h.watcher.Notify()
|
||||||
if err != nil {
|
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)
|
atomic.CompareAndSwapInt32(&lastRequestHadError, 0, 1)
|
||||||
renderError(w, r, err)
|
h.renderError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In a thread safe manner update the flag so that a request for
|
||||||
|
// /favicon.ico is allowed
|
||||||
atomic.CompareAndSwapInt32(&lastRequestHadError, 1, 0)
|
atomic.CompareAndSwapInt32(&lastRequestHadError, 1, 0)
|
||||||
|
|
||||||
// Reverse proxy the request.
|
// Reverse proxy the request.
|
||||||
// (Need special code for websockets, courtesy of bradfitz)
|
// (Need special code for websockets, courtesy of bradfitz)
|
||||||
if strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
|
if strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
|
||||||
proxyWebsocket(w, r, h.serverHost)
|
h.proxyWebsocket(w, r, h.serverHost)
|
||||||
} else {
|
} else {
|
||||||
h.proxy.ServeHTTP(w, r)
|
h.proxy.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
@@ -84,24 +157,27 @@ func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// NewHarness method returns a reverse proxy that forwards requests
|
// NewHarness method returns a reverse proxy that forwards requests
|
||||||
// to the given port.
|
// 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.
|
// Get a template loader to render errors.
|
||||||
// Prefer the app's views/errors directory, and fall back to the stock error pages.
|
// Prefer the app's views/errors directory, and fall back to the stock error pages.
|
||||||
revel.MainTemplateLoader = revel.NewTemplateLoader(
|
// revel.MainTemplateLoader = revel.NewTemplateLoader(
|
||||||
[]string{filepath.Join(revel.RevelPath, "templates")})
|
// []string{filepath.Join(revel.RevelPath, "templates")})
|
||||||
if err := revel.MainTemplateLoader.Refresh(); err != nil {
|
// if err := revel.MainTemplateLoader.Refresh(); err != nil {
|
||||||
revel.ERROR.Println(err)
|
// revel.RevelLog.Error("Template loader error", "error", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
addr := revel.HTTPAddr
|
addr := paths.HTTPAddr
|
||||||
port := revel.Config.IntDefault("harness.port", 0)
|
port := paths.Config.IntDefault("harness.port", 0)
|
||||||
scheme := "http"
|
scheme := "http"
|
||||||
if revel.HTTPSsl {
|
|
||||||
|
if paths.HTTPSsl {
|
||||||
scheme = "https"
|
scheme = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the server is running on the wildcard address, use "localhost"
|
// If the server is running on the wildcard address, use "localhost"
|
||||||
if addr == "" {
|
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"
|
addr = "localhost"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,51 +187,116 @@ func NewHarness() *Harness {
|
|||||||
|
|
||||||
serverURL, _ := url.ParseRequestURI(fmt.Sprintf(scheme+"://%s:%d", addr, port))
|
serverURL, _ := url.ParseRequestURI(fmt.Sprintf(scheme+"://%s:%d", addr, port))
|
||||||
|
|
||||||
harness := &Harness{
|
serverHarness := &Harness{
|
||||||
port: port,
|
port: port,
|
||||||
serverHost: serverURL.String()[len(scheme+"://"):],
|
serverHost: serverURL.String()[len(scheme+"://"):],
|
||||||
proxy: httputil.NewSingleHostReverseProxy(serverURL),
|
proxy: httputil.NewSingleHostReverseProxy(serverURL),
|
||||||
|
mutex: &sync.Mutex{},
|
||||||
|
paths: paths,
|
||||||
|
useProxy: !noProxy,
|
||||||
|
config: c,
|
||||||
|
runMode: runMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
if revel.HTTPSsl {
|
if paths.HTTPSsl {
|
||||||
harness.proxy.Transport = &http.Transport{
|
serverHarness.proxy.Transport = &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return harness
|
return serverHarness
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh method rebuilds the Revel application and run it on the given port.
|
// Refresh method rebuilds the Revel application and run it on the given port.
|
||||||
func (h *Harness) Refresh() (err *revel.Error) {
|
// called by the watcher.
|
||||||
|
func (h *Harness) Refresh() (err *utils.SourceError) {
|
||||||
|
t := time.Now()
|
||||||
|
fmt.Println("Change detected, recompiling")
|
||||||
|
err = h.refresh()
|
||||||
|
if err != nil && !h.ranOnce && h.useProxy {
|
||||||
|
addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort)
|
||||||
|
|
||||||
|
fmt.Printf("\nError compiling code, to view error details see proxy running on http://%s\n\n", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.ranOnce = true
|
||||||
|
fmt.Printf("\nTime to recompile %s\n", time.Since(t).String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Harness) refresh() (err *utils.SourceError) {
|
||||||
|
// 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 {
|
if h.app != nil {
|
||||||
h.app.Kill()
|
h.app.Kill()
|
||||||
}
|
}
|
||||||
|
|
||||||
revel.TRACE.Println("Rebuild")
|
utils.Logger.Info("Rebuild Called")
|
||||||
h.app, err = Build()
|
var newErr error
|
||||||
if err != nil {
|
h.app, newErr = Build(h.config, h.paths)
|
||||||
|
if newErr != nil {
|
||||||
|
utils.Logger.Error("Build detected an error", "error", newErr)
|
||||||
|
|
||||||
|
var castErr *utils.SourceError
|
||||||
|
if errors.As(newErr, &castErr) {
|
||||||
|
return castErr
|
||||||
|
}
|
||||||
|
|
||||||
|
err = &utils.SourceError{
|
||||||
|
Title: "App failed to start up",
|
||||||
|
Description: newErr.Error(),
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.app.Port = h.port
|
if h.useProxy {
|
||||||
if err2 := h.app.Cmd().Start(); err2 != nil {
|
h.app.Port = h.port
|
||||||
return &revel.Error{
|
runMode := h.runMode
|
||||||
Title: "App failed to start up",
|
|
||||||
Description: err2.Error(),
|
if !h.config.HistoricMode {
|
||||||
|
// Recalulate run mode based on the config
|
||||||
|
var paths []byte
|
||||||
|
if len(h.app.PackagePathMap) > 0 {
|
||||||
|
paths, _ = json.Marshal(h.app.PackagePathMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
runMode = fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, h.app.Paths.RunMode, h.config.GetVerbose(), string(paths))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err2 := h.app.Cmd(runMode).Start(h.config); err2 != nil {
|
||||||
|
utils.Logger.Error("Could not start application", "error", err2)
|
||||||
|
|
||||||
|
var serr *utils.SourceError
|
||||||
|
if errors.As(err2, &serr) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &utils.SourceError{
|
||||||
|
Title: "App failed to start up",
|
||||||
|
Description: err2.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h.app = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// WatchDir method returns false to file matches with doNotWatch
|
// WatchDir method returns false to file matches with doNotWatch
|
||||||
// otheriwse true
|
// otheriwse true.
|
||||||
func (h *Harness) WatchDir(info os.FileInfo) bool {
|
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"
|
// WatchFile method returns true given filename HasSuffix of ".go"
|
||||||
// otheriwse false
|
// otheriwse false - implements revel.DiscerningListener.
|
||||||
func (h *Harness) WatchFile(filename string) bool {
|
func (h *Harness) WatchFile(filename string) bool {
|
||||||
return strings.HasSuffix(filename, ".go")
|
return strings.HasSuffix(filename, ".go")
|
||||||
}
|
}
|
||||||
@@ -164,66 +305,79 @@ func (h *Harness) WatchFile(filename string) bool {
|
|||||||
// server, which it runs and rebuilds as necessary.
|
// server, which it runs and rebuilds as necessary.
|
||||||
func (h *Harness) Run() {
|
func (h *Harness) Run() {
|
||||||
var paths []string
|
var paths []string
|
||||||
if revel.Config.BoolDefault("watch.gopath", false) {
|
if h.paths.Config.BoolDefault("watch.gopath", false) {
|
||||||
gopaths := filepath.SplitList(build.Default.GOPATH)
|
gopaths := filepath.SplitList(build.Default.GOPATH)
|
||||||
paths = append(paths, gopaths...)
|
paths = append(paths, gopaths...)
|
||||||
}
|
}
|
||||||
paths = append(paths, revel.CodePaths...)
|
paths = append(paths, h.paths.CodePaths...)
|
||||||
watcher = revel.NewWatcher()
|
h.watcher = watcher.NewWatcher(h.paths, false)
|
||||||
watcher.Listen(h, paths...)
|
h.watcher.Listen(h, paths...)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
addr := fmt.Sprintf("%s:%d", revel.HTTPAddr, revel.HTTPPort)
|
if err := h.Refresh(); err != nil {
|
||||||
revel.INFO.Printf("Listening on %s", addr)
|
utils.Logger.Error("Failed to refresh", "error", err)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Kill the app on signal.
|
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 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)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a new channel to listen for the interrupt event
|
||||||
ch := make(chan os.Signal)
|
ch := make(chan os.Signal)
|
||||||
|
//nolint:staticcheck // os.Kill ineffective on Unix, useful on Windows?
|
||||||
signal.Notify(ch, os.Interrupt, os.Kill)
|
signal.Notify(ch, os.Interrupt, os.Kill)
|
||||||
<-ch
|
<-ch
|
||||||
|
// Kill the app and exit
|
||||||
if h.app != nil {
|
if h.app != nil {
|
||||||
h.app.Kill()
|
h.app.Kill()
|
||||||
}
|
}
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find an unused port
|
// Find an unused port.
|
||||||
func getFreePort() (port int) {
|
func getFreePort() (port int) {
|
||||||
conn, err := net.Listen("tcp", ":0")
|
conn, err := net.Listen("tcp", ":0")
|
||||||
if err != nil {
|
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
|
port = conn.Addr().(*net.TCPAddr).Port
|
||||||
err = conn.Close()
|
err = conn.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.ERROR.Fatal(err)
|
utils.Logger.Fatal("Unable to close port", "error", err)
|
||||||
}
|
}
|
||||||
return port
|
return port
|
||||||
}
|
}
|
||||||
|
|
||||||
// proxyWebsocket copies data between websocket client and server until one side
|
// proxyWebsocket copies data between websocket client and server until one side
|
||||||
// closes the connection. (ReverseProxy doesn't work with websocket requests.)
|
// 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 (
|
var (
|
||||||
d net.Conn
|
d net.Conn
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if revel.HTTPSsl {
|
if h.paths.HTTPSsl {
|
||||||
// since this proxy isn't used in production,
|
// since this proxy isn't used in production,
|
||||||
// it's OK to set InsecureSkipVerify to true
|
// it's OK to set InsecureSkipVerify to true
|
||||||
// no need to add another configuration option.
|
// no need to add another configuration option.
|
||||||
@@ -233,7 +387,7 @@ func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error contacting backend server.", 500)
|
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
|
return
|
||||||
}
|
}
|
||||||
hj, ok := w.(http.Hijacker)
|
hj, ok := w.(http.Hijacker)
|
||||||
@@ -243,21 +397,21 @@ func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
|
|||||||
}
|
}
|
||||||
nc, _, err := hj.Hijack()
|
nc, _, err := hj.Hijack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.ERROR.Printf("Hijack error: %v", err)
|
utils.Logger.Error("Hijack error", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err = nc.Close(); err != nil {
|
if err = nc.Close(); err != nil {
|
||||||
revel.ERROR.Println(err)
|
utils.Logger.Error("Connection close error", "error", err)
|
||||||
}
|
}
|
||||||
if err = d.Close(); err != nil {
|
if err = d.Close(); err != nil {
|
||||||
revel.ERROR.Println(err)
|
utils.Logger.Error("Dial close error", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = r.Write(d)
|
err = r.Write(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
revel.ERROR.Printf("Error copying request to target: %v", err)
|
utils.Logger.Error("Error copying request to target", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,808 +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 harness
|
|
||||||
|
|
||||||
// This file handles the app code introspection.
|
|
||||||
// It catalogs the controllers, their methods, and their arguments.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
|
||||||
"go/parser"
|
|
||||||
"go/scanner"
|
|
||||||
"go/token"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/revel/revel"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SourceInfo is the top-level struct containing all extracted information
|
|
||||||
// about the app source code, used to generate main.go.
|
|
||||||
type SourceInfo struct {
|
|
||||||
// StructSpecs lists type info for all structs found under the code paths.
|
|
||||||
// They may be queried to determine which ones (transitively) embed certain types.
|
|
||||||
StructSpecs []*TypeInfo
|
|
||||||
// ValidationKeys provides a two-level lookup. The keys are:
|
|
||||||
// 1. The fully-qualified function name,
|
|
||||||
// e.g. "github.com/revel/examples/chat/app/controllers.(*Application).Action"
|
|
||||||
// 2. Within that func's file, the line number of the (overall) expression statement.
|
|
||||||
// e.g. the line returned from runtime.Caller()
|
|
||||||
// The result of the lookup the name of variable being validated.
|
|
||||||
ValidationKeys map[string]map[int]string
|
|
||||||
// A list of import paths.
|
|
||||||
// Revel notices files with an init() function and imports that package.
|
|
||||||
InitImportPaths []string
|
|
||||||
|
|
||||||
// controllerSpecs lists type info for all structs found under
|
|
||||||
// app/controllers/... that embed (directly or indirectly) revel.Controller
|
|
||||||
controllerSpecs []*TypeInfo
|
|
||||||
// testSuites list the types that constitute the set of application tests.
|
|
||||||
testSuites []*TypeInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeInfo summarizes information about a struct type in the app source code.
|
|
||||||
type TypeInfo struct {
|
|
||||||
StructName string // e.g. "Application"
|
|
||||||
ImportPath string // e.g. "github.com/revel/examples/chat/app/controllers"
|
|
||||||
PackageName string // e.g. "controllers"
|
|
||||||
MethodSpecs []*MethodSpec
|
|
||||||
|
|
||||||
// Used internally to identify controllers that indirectly embed *revel.Controller.
|
|
||||||
embeddedTypes []*embeddedTypeName
|
|
||||||
}
|
|
||||||
|
|
||||||
// methodCall describes a call to c.Render(..)
|
|
||||||
// It documents the argument names used, in order to propagate them to RenderArgs.
|
|
||||||
type methodCall struct {
|
|
||||||
Path string // e.g. "myapp/app/controllers.(*Application).Action"
|
|
||||||
Line int
|
|
||||||
Names []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// MethodSpec holds the information of one Method
|
|
||||||
type MethodSpec struct {
|
|
||||||
Name string // Name of the method, e.g. "Index"
|
|
||||||
Args []*MethodArg // Argument descriptors
|
|
||||||
RenderCalls []*methodCall // Descriptions of Render() invocations from this Method.
|
|
||||||
}
|
|
||||||
|
|
||||||
// MethodArg holds the information of one argument
|
|
||||||
type MethodArg struct {
|
|
||||||
Name string // Name of the argument.
|
|
||||||
TypeExpr TypeExpr // The name of the type, e.g. "int", "*pkg.UserType"
|
|
||||||
ImportPath string // If the arg is of an imported type, this is the import path.
|
|
||||||
}
|
|
||||||
|
|
||||||
type embeddedTypeName struct {
|
|
||||||
ImportPath, StructName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maps a controller simple name (e.g. "Login") to the methods for which it is a
|
|
||||||
// receiver.
|
|
||||||
type methodMap map[string][]*MethodSpec
|
|
||||||
|
|
||||||
// ProcessSource parses the app controllers directory and
|
|
||||||
// returns a list of the controller types found.
|
|
||||||
// Otherwise CompileError if the parsing fails.
|
|
||||||
func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
|
|
||||||
var (
|
|
||||||
srcInfo *SourceInfo
|
|
||||||
compileError *revel.Error
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, root := range roots {
|
|
||||||
rootImportPath := importPathFromPath(root)
|
|
||||||
if rootImportPath == "" {
|
|
||||||
revel.WARN.Println("Skipping code path", root)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start walking the directory tree.
|
|
||||||
_ = revel.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error scanning app source:", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !info.IsDir() || info.Name() == "tmp" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the import path of the package.
|
|
||||||
pkgImportPath := rootImportPath
|
|
||||||
if root != path {
|
|
||||||
pkgImportPath = rootImportPath + "/" + filepath.ToSlash(path[len(root)+1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse files within the path.
|
|
||||||
var pkgs map[string]*ast.Package
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
pkgs, err = parser.ParseDir(fset, path, func(f os.FileInfo) bool {
|
|
||||||
return !f.IsDir() && !strings.HasPrefix(f.Name(), ".") && strings.HasSuffix(f.Name(), ".go")
|
|
||||||
}, 0)
|
|
||||||
if err != nil {
|
|
||||||
if errList, ok := err.(scanner.ErrorList); ok {
|
|
||||||
var pos = errList[0].Pos
|
|
||||||
compileError = &revel.Error{
|
|
||||||
SourceType: ".go source",
|
|
||||||
Title: "Go Compilation Error",
|
|
||||||
Path: pos.Filename,
|
|
||||||
Description: errList[0].Msg,
|
|
||||||
Line: pos.Line,
|
|
||||||
Column: pos.Column,
|
|
||||||
SourceLines: revel.MustReadLines(pos.Filename),
|
|
||||||
}
|
|
||||||
|
|
||||||
errorLink := revel.Config.StringDefault("error.link", "")
|
|
||||||
|
|
||||||
if errorLink != "" {
|
|
||||||
compileError.SetLink(errorLink)
|
|
||||||
}
|
|
||||||
|
|
||||||
return compileError
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is exception, err alredy checked above. Here just a print
|
|
||||||
ast.Print(nil, err)
|
|
||||||
log.Fatalf("Failed to parse dir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip "main" packages.
|
|
||||||
delete(pkgs, "main")
|
|
||||||
|
|
||||||
// If there is no code in this directory, skip it.
|
|
||||||
if len(pkgs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore packages that end with _test
|
|
||||||
for i := range pkgs {
|
|
||||||
if len(i) > 6 {
|
|
||||||
if string(i[len(i)-5:]) == "_test" {
|
|
||||||
delete(pkgs, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// There should be only one package in this directory.
|
|
||||||
if len(pkgs) > 1 {
|
|
||||||
for i := range pkgs {
|
|
||||||
println("Found package ", i)
|
|
||||||
}
|
|
||||||
log.Println("Most unexpected! Multiple packages in a single directory:", pkgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pkg *ast.Package
|
|
||||||
for _, v := range pkgs {
|
|
||||||
pkg = v
|
|
||||||
}
|
|
||||||
|
|
||||||
srcInfo = appendSourceInfo(srcInfo, processPackage(fset, pkgImportPath, path, pkg))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return srcInfo, compileError
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendSourceInfo(srcInfo1, srcInfo2 *SourceInfo) *SourceInfo {
|
|
||||||
if srcInfo1 == nil {
|
|
||||||
return srcInfo2
|
|
||||||
}
|
|
||||||
|
|
||||||
srcInfo1.StructSpecs = append(srcInfo1.StructSpecs, srcInfo2.StructSpecs...)
|
|
||||||
srcInfo1.InitImportPaths = append(srcInfo1.InitImportPaths, srcInfo2.InitImportPaths...)
|
|
||||||
for k, v := range srcInfo2.ValidationKeys {
|
|
||||||
if _, ok := srcInfo1.ValidationKeys[k]; ok {
|
|
||||||
log.Println("Key conflict when scanning validation calls:", k)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
srcInfo1.ValidationKeys[k] = v
|
|
||||||
}
|
|
||||||
return srcInfo1
|
|
||||||
}
|
|
||||||
|
|
||||||
func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast.Package) *SourceInfo {
|
|
||||||
var (
|
|
||||||
structSpecs []*TypeInfo
|
|
||||||
initImportPaths []string
|
|
||||||
|
|
||||||
methodSpecs = make(methodMap)
|
|
||||||
validationKeys = make(map[string]map[int]string)
|
|
||||||
scanControllers = strings.HasSuffix(pkgImportPath, "/controllers") ||
|
|
||||||
strings.Contains(pkgImportPath, "/controllers/")
|
|
||||||
scanTests = strings.HasSuffix(pkgImportPath, "/tests") ||
|
|
||||||
strings.Contains(pkgImportPath, "/tests/")
|
|
||||||
)
|
|
||||||
|
|
||||||
// For each source file in the package...
|
|
||||||
for _, file := range pkg.Files {
|
|
||||||
|
|
||||||
// Imports maps the package key to the full import path.
|
|
||||||
// e.g. import "sample/app/models" => "models": "sample/app/models"
|
|
||||||
imports := map[string]string{}
|
|
||||||
|
|
||||||
// For each declaration in the source file...
|
|
||||||
for _, decl := range file.Decls {
|
|
||||||
addImports(imports, decl, pkgPath)
|
|
||||||
|
|
||||||
if scanControllers {
|
|
||||||
// Match and add both structs and methods
|
|
||||||
structSpecs = appendStruct(structSpecs, pkgImportPath, pkg, decl, imports, fset)
|
|
||||||
appendAction(fset, methodSpecs, decl, pkgImportPath, pkg.Name, imports)
|
|
||||||
} else if scanTests {
|
|
||||||
structSpecs = appendStruct(structSpecs, pkgImportPath, pkg, decl, imports, fset)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a func...
|
|
||||||
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
|
||||||
// Scan it for validation calls
|
|
||||||
lineKeys := getValidationKeys(fset, funcDecl, imports)
|
|
||||||
if len(lineKeys) > 0 {
|
|
||||||
validationKeys[pkgImportPath+"."+getFuncName(funcDecl)] = lineKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it's an init function.
|
|
||||||
if funcDecl.Name.Name == "init" {
|
|
||||||
initImportPaths = []string{pkgImportPath}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the method specs to the struct specs.
|
|
||||||
for _, spec := range structSpecs {
|
|
||||||
spec.MethodSpecs = methodSpecs[spec.StructName]
|
|
||||||
}
|
|
||||||
|
|
||||||
return &SourceInfo{
|
|
||||||
StructSpecs: structSpecs,
|
|
||||||
ValidationKeys: validationKeys,
|
|
||||||
InitImportPaths: initImportPaths,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFuncName returns a name for this func or method declaration.
|
|
||||||
// e.g. "(*Application).SayHello" for a method, "SayHello" for a func.
|
|
||||||
func getFuncName(funcDecl *ast.FuncDecl) string {
|
|
||||||
prefix := ""
|
|
||||||
if funcDecl.Recv != nil {
|
|
||||||
recvType := funcDecl.Recv.List[0].Type
|
|
||||||
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
|
||||||
prefix = "(*" + recvStarType.X.(*ast.Ident).Name + ")"
|
|
||||||
} else {
|
|
||||||
prefix = recvType.(*ast.Ident).Name
|
|
||||||
}
|
|
||||||
prefix += "."
|
|
||||||
}
|
|
||||||
return prefix + funcDecl.Name.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
|
|
||||||
genDecl, ok := decl.(*ast.GenDecl)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if genDecl.Tok != token.IMPORT {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, spec := range genDecl.Specs {
|
|
||||||
importSpec := spec.(*ast.ImportSpec)
|
|
||||||
var pkgAlias string
|
|
||||||
if importSpec.Name != nil {
|
|
||||||
pkgAlias = importSpec.Name.Name
|
|
||||||
if pkgAlias == "_" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\""
|
|
||||||
fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes
|
|
||||||
|
|
||||||
// If the package was not aliased (common case), we have to import it
|
|
||||||
// to see what the package name is.
|
|
||||||
// TODO: Can improve performance here a lot:
|
|
||||||
// 1. Do not import everything over and over again. Keep a cache.
|
|
||||||
// 2. Exempt the standard library; their directories always match the package name.
|
|
||||||
// 3. Can use build.FindOnly and then use parser.ParseDir with mode PackageClauseOnly
|
|
||||||
if pkgAlias == "" {
|
|
||||||
pkg, err := build.Import(fullPath, srcDir, 0)
|
|
||||||
if err != nil {
|
|
||||||
// We expect this to happen for apps using reverse routing (since we
|
|
||||||
// have not yet generated the routes). Don't log that.
|
|
||||||
if !strings.HasSuffix(fullPath, "/app/routes") {
|
|
||||||
revel.TRACE.Println("Could not find import:", fullPath)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pkgAlias = pkg.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
imports[pkgAlias] = fullPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this Decl is a struct type definition, it is summarized and added to specs.
|
|
||||||
// Else, specs is returned unchanged.
|
|
||||||
func appendStruct(specs []*TypeInfo, pkgImportPath string, pkg *ast.Package, decl ast.Decl, imports map[string]string, fset *token.FileSet) []*TypeInfo {
|
|
||||||
// Filter out non-Struct type declarations.
|
|
||||||
spec, found := getStructTypeDecl(decl, fset)
|
|
||||||
if !found {
|
|
||||||
return specs
|
|
||||||
}
|
|
||||||
structType := spec.Type.(*ast.StructType)
|
|
||||||
|
|
||||||
// At this point we know it's a type declaration for a struct.
|
|
||||||
// Fill in the rest of the info by diving into the fields.
|
|
||||||
// Add it provisionally to the Controller list -- it's later filtered using field info.
|
|
||||||
controllerSpec := &TypeInfo{
|
|
||||||
StructName: spec.Name.Name,
|
|
||||||
ImportPath: pkgImportPath,
|
|
||||||
PackageName: pkg.Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, field := range structType.Fields.List {
|
|
||||||
// If field.Names is set, it's not an embedded type.
|
|
||||||
if field.Names != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// A direct "sub-type" has an ast.Field as either:
|
|
||||||
// Ident { "AppController" }
|
|
||||||
// SelectorExpr { "rev", "Controller" }
|
|
||||||
// Additionally, that can be wrapped by StarExprs.
|
|
||||||
fieldType := field.Type
|
|
||||||
pkgName, typeName := func() (string, string) {
|
|
||||||
// Drill through any StarExprs.
|
|
||||||
for {
|
|
||||||
if starExpr, ok := fieldType.(*ast.StarExpr); ok {
|
|
||||||
fieldType = starExpr.X
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the embedded type is in the same package, it's an Ident.
|
|
||||||
if ident, ok := fieldType.(*ast.Ident); ok {
|
|
||||||
return "", ident.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
|
|
||||||
if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
|
|
||||||
return pkgIdent.Name, selectorExpr.Sel.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", ""
|
|
||||||
}()
|
|
||||||
|
|
||||||
// If a typename wasn't found, skip it.
|
|
||||||
if typeName == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the import path for this type.
|
|
||||||
// If it was referenced without a package name, use the current package import path.
|
|
||||||
// Else, look up the package's import path by name.
|
|
||||||
var importPath string
|
|
||||||
if pkgName == "" {
|
|
||||||
importPath = pkgImportPath
|
|
||||||
} else {
|
|
||||||
var ok bool
|
|
||||||
if importPath, ok = imports[pkgName]; !ok {
|
|
||||||
log.Print("Failed to find import path for ", pkgName, ".", typeName)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
controllerSpec.embeddedTypes = append(controllerSpec.embeddedTypes, &embeddedTypeName{
|
|
||||||
ImportPath: importPath,
|
|
||||||
StructName: typeName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(specs, controllerSpec)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If decl is a Method declaration, it is summarized and added to the array
|
|
||||||
// underneath its receiver type.
|
|
||||||
// e.g. "Login" => {MethodSpec, MethodSpec, ..}
|
|
||||||
func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPath, pkgName string, imports map[string]string) {
|
|
||||||
// Func declaration?
|
|
||||||
funcDecl, ok := decl.(*ast.FuncDecl)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Have a receiver?
|
|
||||||
if funcDecl.Recv == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is it public?
|
|
||||||
if !funcDecl.Name.IsExported() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Does it return a Result?
|
|
||||||
if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if selExpr.Sel.Name != "Result" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != revel.RevelImportPath {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
method := &MethodSpec{
|
|
||||||
Name: funcDecl.Name.Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a description of the arguments to the method.
|
|
||||||
for _, field := range funcDecl.Type.Params.List {
|
|
||||||
for _, name := range field.Names {
|
|
||||||
var importPath string
|
|
||||||
typeExpr := NewTypeExpr(pkgName, field.Type)
|
|
||||||
if !typeExpr.Valid {
|
|
||||||
log.Printf("Didn't understand argument '%s' of action %s. Ignoring.\n", name, getFuncName(funcDecl))
|
|
||||||
return // We didn't understand one of the args. Ignore this action.
|
|
||||||
}
|
|
||||||
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(""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
method.Args = append(method.Args, &MethodArg{
|
|
||||||
Name: name.Name,
|
|
||||||
TypeExpr: typeExpr,
|
|
||||||
ImportPath: importPath,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a description of the calls to Render from the method.
|
|
||||||
// Inspect every node (e.g. always return true).
|
|
||||||
method.RenderCalls = []*methodCall{}
|
|
||||||
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
|
||||||
// Is it a function call?
|
|
||||||
callExpr, ok := node.(*ast.CallExpr)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is it calling (*Controller).Render?
|
|
||||||
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// The type of the receiver is not easily available, so just store every
|
|
||||||
// call to any method called Render.
|
|
||||||
if selExpr.Sel.Name != "Render" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this call's args to the renderArgs.
|
|
||||||
pos := fset.Position(callExpr.Rparen)
|
|
||||||
methodCall := &methodCall{
|
|
||||||
Line: pos.Line,
|
|
||||||
Names: []string{},
|
|
||||||
}
|
|
||||||
for _, arg := range callExpr.Args {
|
|
||||||
argIdent, ok := arg.(*ast.Ident)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
methodCall.Names = append(methodCall.Names, argIdent.Name)
|
|
||||||
}
|
|
||||||
method.RenderCalls = append(method.RenderCalls, methodCall)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
var recvTypeName string
|
|
||||||
var recvType = funcDecl.Recv.List[0].Type
|
|
||||||
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
|
||||||
recvTypeName = recvStarType.X.(*ast.Ident).Name
|
|
||||||
} else {
|
|
||||||
recvTypeName = recvType.(*ast.Ident).Name
|
|
||||||
}
|
|
||||||
|
|
||||||
mm[recvTypeName] = append(mm[recvTypeName], method)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan app source code for calls to X.Y(), where X is of type *Validation.
|
|
||||||
//
|
|
||||||
// Recognize these scenarios:
|
|
||||||
// - "Y" = "Validation" and is a member of the receiver.
|
|
||||||
// (The common case for inline validation)
|
|
||||||
// - "X" is passed in to the func as a parameter.
|
|
||||||
// (For structs implementing Validated)
|
|
||||||
//
|
|
||||||
// The line number to which a validation call is attributed is that of the
|
|
||||||
// surrounding ExprStmt. This is so that it matches what runtime.Callers()
|
|
||||||
// reports.
|
|
||||||
//
|
|
||||||
// The end result is that we can set the default validation key for each call to
|
|
||||||
// be the same as the local variable.
|
|
||||||
func getValidationKeys(fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[string]string) map[int]string {
|
|
||||||
var (
|
|
||||||
lineKeys = make(map[int]string)
|
|
||||||
|
|
||||||
// Check the func parameters and the receiver's members for the *revel.Validation type.
|
|
||||||
validationParam = getValidationParameter(funcDecl, imports)
|
|
||||||
)
|
|
||||||
|
|
||||||
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
|
||||||
// e.g. c.Validation.Required(arg) or v.Required(arg)
|
|
||||||
callExpr, ok := node.(*ast.CallExpr)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// e.g. c.Validation.Required or v.Required
|
|
||||||
funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch x := funcSelector.X.(type) {
|
|
||||||
case *ast.SelectorExpr: // e.g. c.Validation
|
|
||||||
if x.Sel.Name != "Validation" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.Ident: // e.g. v
|
|
||||||
if validationParam == nil || x.Obj != validationParam {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(callExpr.Args) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given the validation expression, extract the key.
|
|
||||||
key := callExpr.Args[0]
|
|
||||||
switch expr := key.(type) {
|
|
||||||
case *ast.BinaryExpr:
|
|
||||||
// If the argument is a binary expression, take the first expression.
|
|
||||||
// (e.g. c.Validation.Required(myName != ""))
|
|
||||||
key = expr.X
|
|
||||||
case *ast.UnaryExpr:
|
|
||||||
// If the argument is a unary expression, drill in.
|
|
||||||
// (e.g. c.Validation.Required(!myBool)
|
|
||||||
key = expr.X
|
|
||||||
case *ast.BasicLit:
|
|
||||||
// If it's a literal, skip it.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if typeExpr := NewTypeExpr("", key); typeExpr.Valid {
|
|
||||||
lineKeys[fset.Position(callExpr.End()).Line] = typeExpr.TypeName("")
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
return lineKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if there is a *revel.Validation as an argument.
|
|
||||||
func getValidationParameter(funcDecl *ast.FuncDecl, imports map[string]string) *ast.Object {
|
|
||||||
for _, field := range funcDecl.Type.Params.List {
|
|
||||||
starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == revel.RevelImportPath {
|
|
||||||
return field.Names[0].Obj
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TypeInfo) String() string {
|
|
||||||
return s.ImportPath + "." + s.StructName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *embeddedTypeName) String() string {
|
|
||||||
return s.ImportPath + "." + s.StructName
|
|
||||||
}
|
|
||||||
|
|
||||||
// getStructTypeDecl checks if the given decl is a type declaration for a
|
|
||||||
// struct. If so, the TypeSpec is returned.
|
|
||||||
func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) {
|
|
||||||
genDecl, ok := decl.(*ast.GenDecl)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if genDecl.Tok != token.TYPE {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(genDecl.Specs) == 0 {
|
|
||||||
revel.WARN.Printf("Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
spec = genDecl.Specs[0].(*ast.TypeSpec)
|
|
||||||
_, found = spec.Type.(*ast.StructType)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypesThatEmbed returns all types that (directly or indirectly) embed the
|
|
||||||
// target type, which must be a fully qualified type name,
|
|
||||||
// e.g. "github.com/revel/revel.Controller"
|
|
||||||
func (s *SourceInfo) TypesThatEmbed(targetType 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):])
|
|
||||||
}
|
|
||||||
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
|
|
||||||
srcPath := filepath.Join(gopath, "src")
|
|
||||||
if strings.HasPrefix(root, srcPath) {
|
|
||||||
return filepath.ToSlash(root[len(srcPath)+1:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
srcPath := filepath.Join(build.Default.GOROOT, "src", "pkg")
|
|
||||||
if strings.HasPrefix(root, srcPath) {
|
|
||||||
revel.WARN.Println("Code path should be in GOPATH, but is in GOROOT:", root)
|
|
||||||
return filepath.ToSlash(root[len(srcPath)+1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
revel.ERROR.Println("Unexpected! Code path is not in GOPATH:", root)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
178
logger/composite_multihandler.go
Normal file
178
logger/composite_multihandler.go
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/mattn/go-colorable"
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 *Record) (err error) {
|
||||||
|
var handler LogHandler
|
||||||
|
|
||||||
|
switch r.Level {
|
||||||
|
case LvlInfo:
|
||||||
|
handler = h.InfoHandler
|
||||||
|
case LvlDebug:
|
||||||
|
handler = h.DebugHandler
|
||||||
|
case LvlWarn:
|
||||||
|
handler = h.WarnHandler
|
||||||
|
case LvlError:
|
||||||
|
handler = h.ErrorHandler
|
||||||
|
case LvlCrit:
|
||||||
|
handler = h.CriticalHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Embed the caller function in the context
|
||||||
|
if handler != nil {
|
||||||
|
if err := handler.Log(r); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 we are not replacing the source make sure that the level handler is applied first
|
||||||
|
if _, isLevel := (*source).(*LevelFilterHandler); !isLevel {
|
||||||
|
*source = LevelHandler(level, *source)
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the multi handler set the handler, using the LogOptions defined.
|
||||||
|
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, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
logger/doc.go
Normal file
12
logger/doc.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
Defining handlers happens as follows
|
||||||
|
1) ALL handlers (log.all.output) replace any existing handlers
|
||||||
|
2) Output handlers (log.error.output) replace any existing handlers
|
||||||
|
3) Filter handlers (log.xxx.filter, log.xxx.nfilter) append to existing handlers,
|
||||||
|
note log.all.filter is treated as a filter handler, so it will NOT replace existing ones
|
||||||
|
*/
|
||||||
|
package logger
|
||||||
214
logger/handlers.go
Normal file
214
logger/handlers.go
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LevelFilterHandler struct {
|
||||||
|
Level LogLevel
|
||||||
|
h LogHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filters out records which do not match the level
|
||||||
|
// Uses the `log15.FilterHandler` to perform this task.
|
||||||
|
func LevelHandler(lvl LogLevel, h LogHandler) LogHandler {
|
||||||
|
return &LevelFilterHandler{lvl, h}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The implementation of the Log.
|
||||||
|
func (h LevelFilterHandler) Log(r *Record) error {
|
||||||
|
if r.Level == h.Level {
|
||||||
|
return h.h.Log(r)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filters out records which do not match the level
|
||||||
|
// Uses the `log15.FilterHandler` to perform this task.
|
||||||
|
func MinLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
|
||||||
|
return FilterHandler(func(r *Record) (pass bool) {
|
||||||
|
return r.Level <= lvl
|
||||||
|
}, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filters out records which match the level
|
||||||
|
// Uses the `log15.FilterHandler` to perform this task.
|
||||||
|
func NotLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
|
||||||
|
return FilterHandler(func(r *Record) (pass bool) {
|
||||||
|
return r.Level != lvl
|
||||||
|
}, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallerFileHandler(h LogHandler) LogHandler {
|
||||||
|
return FuncHandler(func(r *Record) error {
|
||||||
|
r.Context.Add("caller", fmt.Sprint(r.Call))
|
||||||
|
return h.Log(r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// TODO: infinite recursion
|
||||||
|
return 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 MatchFilterHandler(key, value, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchFilterHandler returns a Handler that only writes records
|
||||||
|
// to the wrapped Handler if the given key in the logged
|
||||||
|
// context matches the value. For example, to only log records
|
||||||
|
// from your ui package:
|
||||||
|
//
|
||||||
|
// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler)
|
||||||
|
//
|
||||||
|
func MatchFilterHandler(key string, value interface{}, h LogHandler) LogHandler {
|
||||||
|
return FilterHandler(func(r *Record) (pass bool) {
|
||||||
|
return r.Context[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 FuncHandler(func(r *Record) error {
|
||||||
|
if r.Context[key] == value {
|
||||||
|
return a.Log(r)
|
||||||
|
} else 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 FuncHandler(func(r *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 FuncHandler(func(r *Record) error {
|
||||||
|
matchCount := 0
|
||||||
|
for k, v := range matchMap {
|
||||||
|
value, found := r.Context[k]
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Test for two failure cases
|
||||||
|
if value == v && inverse || value != v && !inverse {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
matchCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
if matchCount != len(matchMap) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return a.Log(r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 FilterHandler(func(r *Record) (pass bool) {
|
||||||
|
return r.Context[key] != value
|
||||||
|
}, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MultiHandler(hs ...LogHandler) LogHandler {
|
||||||
|
return FuncHandler(func(r *Record) error {
|
||||||
|
for _, h := range hs {
|
||||||
|
if err := h.Log(r); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamHandler writes log records to an io.Writer
|
||||||
|
// with the given format. StreamHandler can be used
|
||||||
|
// to easily begin writing log records to other
|
||||||
|
// outputs.
|
||||||
|
//
|
||||||
|
// StreamHandler wraps itself with LazyHandler and SyncHandler
|
||||||
|
// to evaluate Lazy objects and perform safe concurrent writes.
|
||||||
|
func StreamHandler(wr io.Writer, fmtr LogFormat) LogHandler {
|
||||||
|
h := FuncHandler(func(r *Record) error {
|
||||||
|
_, err := wr.Write(fmtr.Format(r))
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return LazyHandler(SyncHandler(h))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter handler.
|
||||||
|
func FilterHandler(fn func(r *Record) bool, h LogHandler) LogHandler {
|
||||||
|
return FuncHandler(func(r *Record) error {
|
||||||
|
if fn(r) {
|
||||||
|
return h.Log(r)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// List log handler handles a list of LogHandlers.
|
||||||
|
type ListLogHandler struct {
|
||||||
|
handlers []LogHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new list of log handlers.
|
||||||
|
func NewListLogHandler(h1, h2 LogHandler) *ListLogHandler {
|
||||||
|
ll := &ListLogHandler{handlers: []LogHandler{h1, h2}}
|
||||||
|
return ll
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the record.
|
||||||
|
func (ll *ListLogHandler) Log(r *Record) (err error) {
|
||||||
|
for _, handler := range ll.handlers {
|
||||||
|
if err == nil {
|
||||||
|
err = handler.Log(r)
|
||||||
|
} else {
|
||||||
|
handler.Log(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add another log handler.
|
||||||
|
func (ll *ListLogHandler) Add(h LogHandler) {
|
||||||
|
if h != nil {
|
||||||
|
ll.handlers = append(ll.handlers, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a log handler.
|
||||||
|
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:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
190
logger/init.go
Normal file
190
logger/init.go
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
// Get all handlers based on the Config (if available).
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/revel/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// Preconfigure all the options
|
||||||
|
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
|
||||||
|
if config != nil && !config.BoolDefault(TestModeFlag, false) {
|
||||||
|
initAllLog(c, basePath, config)
|
||||||
|
}
|
||||||
|
initLogLevels(c, basePath, config)
|
||||||
|
if c.CriticalHandler == nil && c.ErrorHandler != nil {
|
||||||
|
c.CriticalHandler = c.ErrorHandler
|
||||||
|
}
|
||||||
|
if config != nil && !config.BoolDefault(TestModeFlag, false) {
|
||||||
|
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)
|
||||||
|
|
||||||
|
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, ""))
|
||||||
|
fmt.Printf("Adding key map handler %s %s output %s matching %#v\n", option, name, config.StringDefault(option, ""), keyMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
346
logger/init_test.go
Normal file
346
logger/init_test.go
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
// Copyright (c) 2012-2018 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 logger_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/logger"
|
||||||
|
"github.com/revel/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// A counter for the tester.
|
||||||
|
testCounter struct {
|
||||||
|
debug, info, warn, error, critical int
|
||||||
|
}
|
||||||
|
// The data to tes.
|
||||||
|
testData struct {
|
||||||
|
config []string
|
||||||
|
result testResult
|
||||||
|
tc *testCounter
|
||||||
|
}
|
||||||
|
// The test result.
|
||||||
|
testResult struct {
|
||||||
|
debug, info, warn, error, critical int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Single test cases.
|
||||||
|
var singleCases = []testData{
|
||||||
|
{
|
||||||
|
config: []string{"log.crit.output"},
|
||||||
|
result: testResult{0, 0, 0, 0, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.error.output"},
|
||||||
|
result: testResult{0, 0, 0, 1, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.warn.output"},
|
||||||
|
result: testResult{0, 0, 1, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.info.output"},
|
||||||
|
result: testResult{0, 1, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.debug.output"},
|
||||||
|
result: testResult{1, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test singles.
|
||||||
|
func TestSingleCases(t *testing.T) {
|
||||||
|
rootLog := logger.New()
|
||||||
|
for _, testCase := range singleCases {
|
||||||
|
testCase.logTest(t, rootLog)
|
||||||
|
testCase.validate(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter test cases.
|
||||||
|
var filterCases = []testData{
|
||||||
|
{
|
||||||
|
config: []string{"log.crit.filter.module.app"},
|
||||||
|
result: testResult{0, 0, 0, 0, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.crit.filter.module.appa"},
|
||||||
|
result: testResult{0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.error.filter.module.app"},
|
||||||
|
result: testResult{0, 0, 0, 1, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.error.filter.module.appa"},
|
||||||
|
result: testResult{0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.warn.filter.module.app"},
|
||||||
|
result: testResult{0, 0, 1, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.warn.filter.module.appa"},
|
||||||
|
result: testResult{0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.info.filter.module.app"},
|
||||||
|
result: testResult{0, 1, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.info.filter.module.appa"},
|
||||||
|
result: testResult{0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.debug.filter.module.app"},
|
||||||
|
result: testResult{1, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.debug.filter.module.appa"},
|
||||||
|
result: testResult{0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter test.
|
||||||
|
func TestFilterCases(t *testing.T) {
|
||||||
|
rootLog := logger.New("module", "app")
|
||||||
|
for _, testCase := range filterCases {
|
||||||
|
testCase.logTest(t, rootLog)
|
||||||
|
testCase.validate(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inverse test cases.
|
||||||
|
var nfilterCases = []testData{
|
||||||
|
{
|
||||||
|
config: []string{"log.crit.nfilter.module.appa"},
|
||||||
|
result: testResult{0, 0, 0, 0, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.crit.nfilter.modules.appa"},
|
||||||
|
result: testResult{0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.crit.nfilter.module.app"},
|
||||||
|
result: testResult{0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.error.nfilter.module.appa"}, // Special case, when error is not nill critical inherits from error
|
||||||
|
result: testResult{0, 0, 0, 1, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.error.nfilter.module.app"},
|
||||||
|
result: testResult{0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.warn.nfilter.module.appa"},
|
||||||
|
result: testResult{0, 0, 1, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.warn.nfilter.module.app"},
|
||||||
|
result: testResult{0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.info.nfilter.module.appa"},
|
||||||
|
result: testResult{0, 1, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.info.nfilter.module.app"},
|
||||||
|
result: testResult{0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.debug.nfilter.module.appa"},
|
||||||
|
result: testResult{1, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.debug.nfilter.module.app"},
|
||||||
|
result: testResult{0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inverse test.
|
||||||
|
func TestNotFilterCases(t *testing.T) {
|
||||||
|
rootLog := logger.New("module", "app")
|
||||||
|
for _, testCase := range nfilterCases {
|
||||||
|
testCase.logTest(t, rootLog)
|
||||||
|
testCase.validate(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// off test cases.
|
||||||
|
var offCases = []testData{
|
||||||
|
{
|
||||||
|
config: []string{"log.all.output", "log.error.output=off"},
|
||||||
|
result: testResult{1, 1, 1, 0, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Off test.
|
||||||
|
func TestOffCases(t *testing.T) {
|
||||||
|
rootLog := logger.New("module", "app")
|
||||||
|
for _, testCase := range offCases {
|
||||||
|
testCase.logTest(t, rootLog)
|
||||||
|
testCase.validate(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplicate test cases.
|
||||||
|
var duplicateCases = []testData{
|
||||||
|
{
|
||||||
|
config: []string{"log.all.output", "log.error.output", "log.error.filter.module.app"},
|
||||||
|
result: testResult{1, 1, 1, 2, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// test duplicate cases.
|
||||||
|
func TestDuplicateCases(t *testing.T) {
|
||||||
|
rootLog := logger.New("module", "app")
|
||||||
|
for _, testCase := range duplicateCases {
|
||||||
|
testCase.logTest(t, rootLog)
|
||||||
|
testCase.validate(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contradicting cases.
|
||||||
|
var contradictCases = []testData{
|
||||||
|
{
|
||||||
|
config: []string{"log.all.output", "log.error.output=off", "log.all.output"},
|
||||||
|
result: testResult{1, 1, 1, 0, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.all.output", "log.error.output=off", "log.debug.filter.module.app"},
|
||||||
|
result: testResult{2, 1, 1, 0, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.all.filter.module.app", "log.info.output=off", "log.info.filter.module.app"},
|
||||||
|
result: testResult{1, 2, 1, 1, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.all.output", "log.info.output=off", "log.info.filter.module.app"},
|
||||||
|
result: testResult{1, 1, 1, 1, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contradiction test.
|
||||||
|
func TestContradictCases(t *testing.T) {
|
||||||
|
rootLog := logger.New("module", "app")
|
||||||
|
for _, testCase := range contradictCases {
|
||||||
|
testCase.logTest(t, rootLog)
|
||||||
|
testCase.validate(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All test cases.
|
||||||
|
var allCases = []testData{
|
||||||
|
{
|
||||||
|
config: []string{"log.all.filter.module.app"},
|
||||||
|
result: testResult{1, 1, 1, 1, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: []string{"log.all.output"},
|
||||||
|
result: testResult{2, 2, 2, 2, 2},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// All tests.
|
||||||
|
func TestAllCases(t *testing.T) {
|
||||||
|
rootLog := logger.New("module", "app")
|
||||||
|
for i, testCase := range allCases {
|
||||||
|
testCase.logTest(t, rootLog)
|
||||||
|
allCases[i] = testCase
|
||||||
|
}
|
||||||
|
rootLog = logger.New()
|
||||||
|
for i, testCase := range allCases {
|
||||||
|
testCase.logTest(t, rootLog)
|
||||||
|
allCases[i] = testCase
|
||||||
|
}
|
||||||
|
for _, testCase := range allCases {
|
||||||
|
testCase.validate(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testCounter) Log(r *logger.Record) error {
|
||||||
|
switch r.Level {
|
||||||
|
case logger.LvlDebug:
|
||||||
|
c.debug++
|
||||||
|
case logger.LvlInfo:
|
||||||
|
c.info++
|
||||||
|
case logger.LvlWarn:
|
||||||
|
c.warn++
|
||||||
|
case logger.LvlError:
|
||||||
|
c.error++
|
||||||
|
case logger.LvlCrit:
|
||||||
|
c.critical++
|
||||||
|
default:
|
||||||
|
panic("Unknown log level")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (td *testData) logTest(t *testing.T, rootLog logger.MultiLogger) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if td.tc == nil {
|
||||||
|
td.tc = &testCounter{}
|
||||||
|
counterInit(td.tc)
|
||||||
|
}
|
||||||
|
newContext := config.NewContext()
|
||||||
|
for _, i := range td.config {
|
||||||
|
iout := strings.Split(i, "=")
|
||||||
|
if len(iout) > 1 {
|
||||||
|
newContext.SetOption(iout[0], iout[1])
|
||||||
|
} else {
|
||||||
|
newContext.SetOption(i, "test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newContext.SetOption("specialUseFlag", "true")
|
||||||
|
|
||||||
|
handler := logger.InitializeFromConfig("test", newContext)
|
||||||
|
|
||||||
|
rootLog.SetHandler(handler)
|
||||||
|
|
||||||
|
td.runLogTest(rootLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (td *testData) runLogTest(log logger.MultiLogger) {
|
||||||
|
log.Debug("test")
|
||||||
|
log.Info("test")
|
||||||
|
log.Warn("test")
|
||||||
|
log.Error("test")
|
||||||
|
log.Crit("test")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (td *testData) validate(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
t.Logf("Test %#v expected %#v", td.tc, td.result)
|
||||||
|
assert.Equal(t, td.result.debug, td.tc.debug, "Debug failed "+strings.Join(td.config, " "))
|
||||||
|
assert.Equal(t, td.result.info, td.tc.info, "Info failed "+strings.Join(td.config, " "))
|
||||||
|
assert.Equal(t, td.result.warn, td.tc.warn, "Warn failed "+strings.Join(td.config, " "))
|
||||||
|
assert.Equal(t, td.result.error, td.tc.error, "Error failed "+strings.Join(td.config, " "))
|
||||||
|
assert.Equal(t, td.result.critical, td.tc.critical, "Critical failed "+strings.Join(td.config, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add test to the function map.
|
||||||
|
func counterInit(tc *testCounter) {
|
||||||
|
logger.LogFunctionMap["test"] = func(c *logger.CompositeMultiHandler, logOptions *logger.LogOptions) {
|
||||||
|
// Output to the test log and the stdout
|
||||||
|
outHandler := logger.LogHandler(
|
||||||
|
logger.NewListLogHandler(tc,
|
||||||
|
logger.StreamHandler(os.Stdout, logger.TerminalFormatHandler(false, true))),
|
||||||
|
)
|
||||||
|
if logOptions.HandlerWrap != nil {
|
||||||
|
outHandler = logOptions.HandlerWrap.SetChild(outHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SetHandlers(outHandler, logOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
37
logger/log_function_map.go
Normal file
37
logger/log_function_map.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogFunctionMap can be added to, so that you can specify your own logging mechanism
|
||||||
|
// it has defaults for off, stdout, stderr.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Clear existing handler
|
||||||
|
c.SetHandlers(NilHandler(), logOptions)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 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)
|
||||||
|
},
|
||||||
|
}
|
||||||
205
logger/logger.go
Normal file
205
logger/logger.go
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/revel/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// Set the stack depth for the logger
|
||||||
|
SetStackDepth(int) MultiLogger
|
||||||
|
|
||||||
|
// Log a message at the given level with context key/value pairs
|
||||||
|
Debug(msg string, ctx ...interface{})
|
||||||
|
|
||||||
|
// Log a message at the given level formatting message with the parameters
|
||||||
|
Debugf(msg string, params ...interface{})
|
||||||
|
|
||||||
|
// Log a message at the given level with context key/value pairs
|
||||||
|
Info(msg string, ctx ...interface{})
|
||||||
|
|
||||||
|
// Log a message at the given level formatting message with the parameters
|
||||||
|
Infof(msg string, params ...interface{})
|
||||||
|
|
||||||
|
// Log a message at the given level with context key/value pairs
|
||||||
|
Warn(msg string, ctx ...interface{})
|
||||||
|
|
||||||
|
// Log a message at the given level formatting message with the parameters
|
||||||
|
Warnf(msg string, params ...interface{})
|
||||||
|
|
||||||
|
// Log a message at the given level with context key/value pairs
|
||||||
|
Error(msg string, ctx ...interface{})
|
||||||
|
|
||||||
|
// Log a message at the given level formatting message with the parameters
|
||||||
|
Errorf(msg string, params ...interface{})
|
||||||
|
|
||||||
|
// Log a message at the given level with context key/value pairs
|
||||||
|
Crit(msg string, ctx ...interface{})
|
||||||
|
|
||||||
|
// Log a message at the given level formatting message with the parameters
|
||||||
|
Critf(msg string, params ...interface{})
|
||||||
|
|
||||||
|
// Log a message at the given level with context key/value pairs and exits
|
||||||
|
Fatal(msg string, ctx ...interface{})
|
||||||
|
|
||||||
|
// Log a message at the given level formatting message with the parameters and exits
|
||||||
|
Fatalf(msg string, params ...interface{})
|
||||||
|
|
||||||
|
// Log a message at the given level with context key/value pairs and panics
|
||||||
|
Panic(msg string, ctx ...interface{})
|
||||||
|
|
||||||
|
// Log a message at the given level formatting message with the parameters and panics
|
||||||
|
Panicf(msg string, params ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// The log handler interface.
|
||||||
|
LogHandler interface {
|
||||||
|
Log(*Record) error
|
||||||
|
// log15.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// The log stack handler interface.
|
||||||
|
LogStackHandler interface {
|
||||||
|
LogHandler
|
||||||
|
GetStack() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// The log handler interface which has child logs.
|
||||||
|
ParentLogHandler interface {
|
||||||
|
SetChild(handler LogHandler) LogHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// The log format interface.
|
||||||
|
LogFormat interface {
|
||||||
|
Format(r *Record) []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// The log level type.
|
||||||
|
LogLevel int
|
||||||
|
|
||||||
|
// Used for the callback to LogFunctionMap.
|
||||||
|
LogOptions struct {
|
||||||
|
Ctx *config.Context
|
||||||
|
ReplaceExistingHandler bool
|
||||||
|
HandlerWrap ParentLogHandler
|
||||||
|
Levels []LogLevel
|
||||||
|
ExtendedOptions map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The log record.
|
||||||
|
Record struct {
|
||||||
|
Message string // The message
|
||||||
|
Time time.Time // The time
|
||||||
|
Level LogLevel // The level
|
||||||
|
Call CallStack // The call stack if built
|
||||||
|
Context ContextMap // The context
|
||||||
|
}
|
||||||
|
|
||||||
|
// The lazy structure to implement a function to be invoked only if needed.
|
||||||
|
Lazy struct {
|
||||||
|
Fn interface{} // the function
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently the only requirement for the callstack is to support the Formatter method
|
||||||
|
// which stack.Call does so we use that.
|
||||||
|
CallStack interface {
|
||||||
|
fmt.Formatter // Requirement
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// FormatFunc returns a new Format object which uses
|
||||||
|
// the given function to perform record formatting.
|
||||||
|
func FormatFunc(f func(*Record) []byte) LogFormat {
|
||||||
|
return formatFunc(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
type formatFunc func(*Record) []byte
|
||||||
|
|
||||||
|
func (f formatFunc) Format(r *Record) []byte {
|
||||||
|
return f(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecord(message string, level LogLevel) *Record {
|
||||||
|
return &Record{Message: message, Context: ContextMap{}, Level: level}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
LvlCrit LogLevel = iota // Critical
|
||||||
|
LvlError // Error
|
||||||
|
LvlWarn // Warning
|
||||||
|
LvlInfo // Information
|
||||||
|
LvlDebug // Debug
|
||||||
|
)
|
||||||
|
|
||||||
|
// LvlAllList is a list of all the log levels.
|
||||||
|
var LvlAllList = []LogLevel{LvlDebug, LvlInfo, LvlWarn, LvlError, LvlCrit}
|
||||||
|
|
||||||
|
// Implements the ParentLogHandler.
|
||||||
|
type parentLogHandler struct {
|
||||||
|
setChild func(handler LogHandler) LogHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new parent log handler.
|
||||||
|
func NewParentLogHandler(callBack func(child LogHandler) LogHandler) ParentLogHandler {
|
||||||
|
return &parentLogHandler{callBack}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the child of the log handler.
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a string option with default.
|
||||||
|
func (l *LogOptions) GetStringDefault(option, value string) string {
|
||||||
|
if v, found := l.ExtendedOptions[option]; found {
|
||||||
|
return v.(string)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets an int option with default.
|
||||||
|
func (l *LogOptions) GetIntDefault(option string, value int) int {
|
||||||
|
if v, found := l.ExtendedOptions[option]; found {
|
||||||
|
return v.(int)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a boolean option with default.
|
||||||
|
func (l *LogOptions) GetBoolDefault(option string, value bool) bool {
|
||||||
|
if v, found := l.ExtendedOptions[option]; found {
|
||||||
|
return v.(bool)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
144
logger/revel_logger.go
Normal file
144
logger/revel_logger.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/revel/log15"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This type implements the MultiLogger.
|
||||||
|
type RevelLogger struct {
|
||||||
|
log15.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *RevelLogger) Debugf(msg string, param ...interface{}) {
|
||||||
|
rl.Debug(fmt.Sprintf(msg, param...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print a formatted info message.
|
||||||
|
func (rl *RevelLogger) Infof(msg string, param ...interface{}) {
|
||||||
|
rl.Info(fmt.Sprintf(msg, param...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print a formatted warn message.
|
||||||
|
func (rl *RevelLogger) Warnf(msg string, param ...interface{}) {
|
||||||
|
rl.Warn(fmt.Sprintf(msg, param...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print a formatted error message.
|
||||||
|
func (rl *RevelLogger) Errorf(msg string, param ...interface{}) {
|
||||||
|
rl.Error(fmt.Sprintf(msg, param...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print a formatted critical message.
|
||||||
|
func (rl *RevelLogger) Critf(msg string, param ...interface{}) {
|
||||||
|
rl.Crit(fmt.Sprintf(msg, param...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print a formatted fatal message.
|
||||||
|
func (rl *RevelLogger) Fatalf(msg string, param ...interface{}) {
|
||||||
|
rl.Fatal(fmt.Sprintf(msg, param...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print a formatted panic message.
|
||||||
|
func (rl *RevelLogger) Panicf(msg string, param ...interface{}) {
|
||||||
|
rl.Panic(fmt.Sprintf(msg, param...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print a critical message and call os.Exit(1).
|
||||||
|
func (rl *RevelLogger) Fatal(msg string, ctx ...interface{}) {
|
||||||
|
rl.Crit(msg, ctx...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print a critical message and panic.
|
||||||
|
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(0)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the handler in the Logger.
|
||||||
|
func (rl *RevelLogger) SetHandler(h LogHandler) {
|
||||||
|
rl.Logger.SetHandler(callHandler(h.Log))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The function wrapper to implement the callback.
|
||||||
|
type callHandler func(r *Record) error
|
||||||
|
|
||||||
|
// Log implementation, reads the record and extracts the details from the log record
|
||||||
|
// Hiding the implementation.
|
||||||
|
func (c callHandler) Log(log *log15.Record) error {
|
||||||
|
ctx := log.Ctx
|
||||||
|
var ctxMap ContextMap
|
||||||
|
if len(ctx) > 0 {
|
||||||
|
ctxMap = make(ContextMap, len(ctx)/2)
|
||||||
|
|
||||||
|
for i := 0; i < len(ctx); i += 2 {
|
||||||
|
v := ctx[i]
|
||||||
|
key, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
key = fmt.Sprintf("LOGGER_INVALID_KEY %v", v)
|
||||||
|
}
|
||||||
|
var value interface{}
|
||||||
|
if len(ctx) > i+1 {
|
||||||
|
value = ctx[i+1]
|
||||||
|
} else {
|
||||||
|
value = "LOGGER_VALUE_MISSING"
|
||||||
|
}
|
||||||
|
ctxMap[key] = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctxMap = make(ContextMap)
|
||||||
|
}
|
||||||
|
r := &Record{Message: log.Msg, Context: ctxMap, Time: log.Time, Level: LogLevel(log.Lvl), Call: CallStack(log.Call)}
|
||||||
|
return c(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internally used contextMap, allows conversion of map to map[string]string.
|
||||||
|
type ContextMap map[string]interface{}
|
||||||
|
|
||||||
|
// Convert the context map to be string only values, any non string values are ignored.
|
||||||
|
func (m ContextMap) StringMap() (newMap map[string]string) {
|
||||||
|
if m != nil {
|
||||||
|
newMap = map[string]string{}
|
||||||
|
for key, value := range m {
|
||||||
|
if svalue, isstring := value.(string); isstring {
|
||||||
|
newMap[key] = svalue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m ContextMap) Add(key string, value interface{}) {
|
||||||
|
m[key] = value
|
||||||
|
}
|
||||||
245
logger/terminal_format.go
Normal file
245
logger/terminal_format.go
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"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 levelString = map[LogLevel]string{
|
||||||
|
LvlDebug: "DEBUG",
|
||||||
|
LvlInfo: "INFO", LvlWarn: "WARN", LvlError: "ERROR", 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 FormatFunc(func(r *Record) []byte {
|
||||||
|
// Bash coloring http://misc.flogisoft.com/bash/tip_colors_and_formatting
|
||||||
|
color := 0
|
||||||
|
switch r.Level {
|
||||||
|
case LvlCrit:
|
||||||
|
// Magenta
|
||||||
|
color = 35
|
||||||
|
case LvlError:
|
||||||
|
// Red
|
||||||
|
color = 31
|
||||||
|
case LvlWarn:
|
||||||
|
// Yellow
|
||||||
|
color = 33
|
||||||
|
case LvlInfo:
|
||||||
|
// Green
|
||||||
|
color = 32
|
||||||
|
case LvlDebug:
|
||||||
|
// Cyan
|
||||||
|
color = 36
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
caller, _ := r.Context["caller"].(string)
|
||||||
|
module, _ := r.Context["module"].(string)
|
||||||
|
if !noColor && color > 0 {
|
||||||
|
if len(module) > 0 {
|
||||||
|
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %6s %13s: %-40s ", color, levelString[r.Level], r.Time.Format(dateFormat), module, caller, r.Message)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %13s: %-40s ", color, levelString[r.Level], r.Time.Format(dateFormat), caller, r.Message)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(b, "%-5s %s %6s %13s: %-40s", levelString[r.Level], r.Time.Format(dateFormat), module, caller, r.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for k, v := range r.Context {
|
||||||
|
if i != 0 {
|
||||||
|
b.WriteByte(' ')
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
if k == "module" || k == "caller" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v := formatLogfmtValue(v)
|
||||||
|
|
||||||
|
// TODO: we should probably check that all of your key bytes aren't invalid
|
||||||
|
if !noColor && 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()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the value in json format.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A reusuable buffer for outputting data.
|
||||||
|
var stringBufPool = sync.Pool{
|
||||||
|
New: func() interface{} { return new(bytes.Buffer) },
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape the string when needed.
|
||||||
|
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 && !needsQuotes {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONFormatEx formats log records as JSON objects. If pretty is true,
|
||||||
|
// records will be pretty-printed. If lineSeparated is true, records
|
||||||
|
// will be logged with a new line between each record.
|
||||||
|
func JSONFormatEx(pretty, lineSeparated bool) LogFormat {
|
||||||
|
jsonMarshal := json.Marshal
|
||||||
|
if pretty {
|
||||||
|
jsonMarshal = func(v interface{}) ([]byte, error) {
|
||||||
|
return json.MarshalIndent(v, "", " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormatFunc(func(r *Record) []byte {
|
||||||
|
props := make(map[string]interface{})
|
||||||
|
|
||||||
|
props["t"] = r.Time
|
||||||
|
props["lvl"] = levelString[r.Level]
|
||||||
|
props["msg"] = r.Message
|
||||||
|
for k, v := range r.Context {
|
||||||
|
props[k] = formatJSONValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := jsonMarshal(props)
|
||||||
|
if err != nil {
|
||||||
|
b, _ = jsonMarshal(map[string]string{
|
||||||
|
errorKey: err.Error(),
|
||||||
|
})
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
if lineSeparated {
|
||||||
|
b = append(b, '\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatJSONValue(value interface{}) interface{} {
|
||||||
|
value = formatShared(value)
|
||||||
|
switch value.(type) {
|
||||||
|
case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string:
|
||||||
|
return value
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%+v", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
110
logger/utils.go
Normal file
110
logger/utils.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/revel/log15"
|
||||||
|
"gopkg.in/stack.v0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The test mode flag overrides the default log level and shows only errors.
|
||||||
|
TestModeFlag = "testModeFlag"
|
||||||
|
// The special use flag enables showing messages when the logger is setup.
|
||||||
|
SpecialUseFlag = "specialUseFlag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Returns the logger for the name.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used by the initFilterLog to handle the filters.
|
||||||
|
var 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)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// The message indicating that a logger is using a deprecated log mechanism.
|
||||||
|
var logDeprecated = []byte("* LOG DEPRECATED * ")
|
||||||
|
|
||||||
|
// Implements the Write of the logger.
|
||||||
|
func (lr loggerRewrite) Write(p []byte) (n int, err error) {
|
||||||
|
if !lr.hideDeprecated {
|
||||||
|
p = append(logDeprecated, 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.Crit("This should not occur","stack",revel.NewCallStack())`.
|
||||||
|
func NewCallStack() interface{} {
|
||||||
|
return stack.Trace()
|
||||||
|
}
|
||||||
111
logger/wrap_handlers.go
Normal file
111
logger/wrap_handlers.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
// FuncHandler returns a Handler that logs records with the given
|
||||||
|
// function.
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error is used for constant errors.
|
||||||
|
type Error string
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrNotFunc Error = "not a function"
|
||||||
|
ErrTakesArgs Error = "takes arguments"
|
||||||
|
ErrNoReturn Error = "no return value"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Function handler wraps the declared function and returns the handler for it.
|
||||||
|
func FuncHandler(fn func(r *Record) error) LogHandler {
|
||||||
|
return funcHandler(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type declaration for the function.
|
||||||
|
type funcHandler func(r *Record) error
|
||||||
|
|
||||||
|
// The implementation of the Log.
|
||||||
|
func (h funcHandler) Log(r *Record) error {
|
||||||
|
return h(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function allows you to do a full declaration for the log,
|
||||||
|
// it is recommended you use FuncHandler instead.
|
||||||
|
func HandlerFunc(log func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error) LogHandler {
|
||||||
|
return remoteHandler(log)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type used for the HandlerFunc.
|
||||||
|
type remoteHandler func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error
|
||||||
|
|
||||||
|
// The Log implementation.
|
||||||
|
func (c remoteHandler) Log(record *Record) error {
|
||||||
|
return c(record.Message, record.Time, record.Level, record.Call, record.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncHandler can be wrapped around a handler to guarantee that
|
||||||
|
// only a single Log operation can proceed at a time. It's necessary
|
||||||
|
// for thread-safe concurrent writes.
|
||||||
|
func SyncHandler(h LogHandler) LogHandler {
|
||||||
|
var mu sync.Mutex
|
||||||
|
return FuncHandler(func(r *Record) error {
|
||||||
|
defer mu.Unlock()
|
||||||
|
mu.Lock()
|
||||||
|
return h.Log(r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LazyHandler writes all values to the wrapped handler after evaluating
|
||||||
|
// any lazy functions in the record's context. It is already wrapped
|
||||||
|
// around StreamHandler and SyslogHandler in this library, you'll only need
|
||||||
|
// it if you write your own Handler.
|
||||||
|
func LazyHandler(h LogHandler) LogHandler {
|
||||||
|
return FuncHandler(func(r *Record) error {
|
||||||
|
for k, v := range r.Context {
|
||||||
|
if lz, ok := v.(Lazy); ok {
|
||||||
|
_, err := evaluateLazy(lz)
|
||||||
|
if err != nil {
|
||||||
|
r.Context[errorKey] = "bad lazy " + k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.Log(r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluateLazy(lz Lazy) (interface{}, error) {
|
||||||
|
t := reflect.TypeOf(lz.Fn)
|
||||||
|
|
||||||
|
if t.Kind() != reflect.Func {
|
||||||
|
return nil, fmt.Errorf("%w %+v", ErrNotFunc, lz.Fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.NumIn() > 0 {
|
||||||
|
return nil, fmt.Errorf("%w %+v", ErrTakesArgs, lz.Fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.NumOut() == 0 {
|
||||||
|
return nil, fmt.Errorf("%w %+v", ErrNoReturn, lz.Fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
value := reflect.ValueOf(lz.Fn)
|
||||||
|
results := value.Call([]reflect.Value{})
|
||||||
|
if len(results) == 1 {
|
||||||
|
return results[0].Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
values := make([]interface{}, len(results))
|
||||||
|
for i, v := range results {
|
||||||
|
values[i] = v.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
10
model/command/build.go
Normal file
10
model/command/build.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
type (
|
||||||
|
Build struct {
|
||||||
|
ImportCommand
|
||||||
|
TargetPath string `short:"t" long:"target-path" description:"Path to target folder. Folder will be completely deleted if it exists" required:"false"`
|
||||||
|
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
|
||||||
|
CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"`
|
||||||
|
}
|
||||||
|
)
|
||||||
7
model/command/clean.go
Normal file
7
model/command/clean.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
type (
|
||||||
|
Clean struct {
|
||||||
|
ImportCommand
|
||||||
|
}
|
||||||
|
)
|
||||||
7
model/command/import_command.go
Normal file
7
model/command/import_command.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
type (
|
||||||
|
ImportCommand struct {
|
||||||
|
ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"`
|
||||||
|
}
|
||||||
|
)
|
||||||
12
model/command/new.go
Normal file
12
model/command/new.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
type (
|
||||||
|
New struct {
|
||||||
|
ImportCommand
|
||||||
|
SkeletonPath string `short:"s" long:"skeleton" description:"Path to skeleton folder (Must exist on GO PATH)" required:"false"`
|
||||||
|
Package string `short:"p" long:"package" description:"The package name, this becomes the repfix to the app name, if defined vendored is set to true" required:"false"`
|
||||||
|
NotVendored bool `long:"no-vendor" description:"True if project should not be configured with a go.mod, this requires you to have the project on the GOPATH, this is only compatible with go versions v1.12 or older"`
|
||||||
|
Run bool `short:"r" long:"run" description:"True if you want to run the application right away"`
|
||||||
|
Callback func() error
|
||||||
|
}
|
||||||
|
)
|
||||||
10
model/command/package.go
Normal file
10
model/command/package.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
type (
|
||||||
|
Package struct {
|
||||||
|
ImportCommand
|
||||||
|
TargetPath string `short:"t" long:"target-path" description:"Full path and filename of target package to deploy" required:"false"`
|
||||||
|
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
|
||||||
|
CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"`
|
||||||
|
}
|
||||||
|
)
|
||||||
10
model/command/run.go
Normal file
10
model/command/run.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
type (
|
||||||
|
Run struct {
|
||||||
|
ImportCommand
|
||||||
|
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
|
||||||
|
Port int `short:"p" long:"port" default:"-1" description:"The port to listen" `
|
||||||
|
NoProxy bool `short:"n" long:"no-proxy" description:"True if proxy server should not be started. This will only update the main and routes files on change"`
|
||||||
|
}
|
||||||
|
)
|
||||||
9
model/command/test_command.go
Normal file
9
model/command/test_command.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
type (
|
||||||
|
Test struct {
|
||||||
|
ImportCommand
|
||||||
|
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
|
||||||
|
Function string `short:"f" long:"suite-function" description:"The suite.function"`
|
||||||
|
}
|
||||||
|
)
|
||||||
7
model/command/version.go
Normal file
7
model/command/version.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
type (
|
||||||
|
Version struct {
|
||||||
|
ImportCommand
|
||||||
|
}
|
||||||
|
)
|
||||||
363
model/command_config.go
Normal file
363
model/command_config.go
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/build"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/revel/cmd"
|
||||||
|
"github.com/revel/cmd/model/command"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The constants.
|
||||||
|
const (
|
||||||
|
NEW COMMAND = iota + 1
|
||||||
|
RUN
|
||||||
|
BUILD
|
||||||
|
PACKAGE
|
||||||
|
CLEAN
|
||||||
|
TEST
|
||||||
|
VERSION
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrImportInvalid Error = "invalid import path, working dir is in GOPATH root"
|
||||||
|
ErrUnableToImport Error = "unable to determine import path from"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
FrameworkVersion *Version // The framework version
|
||||||
|
CommandVersion *Version // The command version
|
||||||
|
HistoricMode bool `long:"historic-run-mode" description:"If set the runmode is passed a string not json"` // True if debug is active
|
||||||
|
ImportPath string // The import path (relative to a GOPATH)
|
||||||
|
GoPath string // The GoPath
|
||||||
|
GoCmd string // The full path to the go executable
|
||||||
|
// SrcRoot string // The source root
|
||||||
|
AppPath string // The application path (absolute)
|
||||||
|
AppName string // The application name
|
||||||
|
HistoricBuildMode bool `long:"historic-build-mode" description:"If set the code is scanned using the original parsers, not the go.1.11+"` // True if debug is active
|
||||||
|
Vendored bool // True if the application is vendored
|
||||||
|
PackageResolver func(pkgName string) error // a package resolver for the config
|
||||||
|
BuildFlags []string `short:"X" long:"build-flags" description:"These flags will be used when building the application. May be specified multiple times, only applicable for Build, Run, Package, Test commands"`
|
||||||
|
GoModFlags []string `long:"gomod-flags" description:"These flags will execute go mod commands for each flag, this happens during the build process"`
|
||||||
|
New command.New `command:"new"`
|
||||||
|
Build command.Build `command:"build"`
|
||||||
|
Run command.Run `command:"run"`
|
||||||
|
Package command.Package `command:"package"`
|
||||||
|
Clean command.Clean `command:"clean"`
|
||||||
|
Test command.Test `command:"test"`
|
||||||
|
Version command.Version `command:"version"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Updates the import path depending on the command.
|
||||||
|
func (c *CommandConfig) UpdateImportPath() error {
|
||||||
|
var importPath string
|
||||||
|
required := true
|
||||||
|
switch c.Index {
|
||||||
|
case NEW:
|
||||||
|
importPath = c.New.ImportPath
|
||||||
|
case RUN:
|
||||||
|
importPath = c.Run.ImportPath
|
||||||
|
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
|
||||||
|
case BUILD:
|
||||||
|
importPath = c.Build.ImportPath
|
||||||
|
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
|
||||||
|
case PACKAGE:
|
||||||
|
importPath = c.Package.ImportPath
|
||||||
|
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
|
||||||
|
case CLEAN:
|
||||||
|
importPath = c.Clean.ImportPath
|
||||||
|
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
|
||||||
|
case TEST:
|
||||||
|
importPath = c.Test.ImportPath
|
||||||
|
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
|
||||||
|
case VERSION:
|
||||||
|
importPath = c.Version.ImportPath
|
||||||
|
required = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(importPath) == 0 || filepath.IsAbs(importPath) || importPath[0] == '.' {
|
||||||
|
utils.Logger.Info("Import path is absolute or not specified", "path", importPath)
|
||||||
|
// 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) && len(currentPath) > len(path)+1 {
|
||||||
|
importPath = currentPath[len(path)+1:]
|
||||||
|
// Remove the source from the path if it is there
|
||||||
|
if len(importPath) > 4 && (strings.ToLower(importPath[0:4]) == "src/" || strings.ToLower(importPath[0:4]) == "src\\") {
|
||||||
|
importPath = importPath[4:]
|
||||||
|
} else if importPath == "src" {
|
||||||
|
if c.Index != VERSION {
|
||||||
|
return ErrImportInvalid
|
||||||
|
}
|
||||||
|
importPath = ""
|
||||||
|
}
|
||||||
|
utils.Logger.Info("Updated import path", "path", importPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ImportPath = importPath
|
||||||
|
// We need the source root determined at this point to check the setversions
|
||||||
|
if err := c.initAppFolder(); err != nil {
|
||||||
|
utils.Logger.Error("Error initing app folder", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Logger.Info("Returned import path", "path", importPath)
|
||||||
|
if required && c.Index != NEW {
|
||||||
|
if err := c.SetVersions(); err != nil {
|
||||||
|
utils.Logger.Panic("Failed to fetch revel versions", "error", err)
|
||||||
|
}
|
||||||
|
if err := c.FrameworkVersion.CompatibleFramework(c); err != nil {
|
||||||
|
utils.Logger.Fatal("Compatibility Error", "message", err,
|
||||||
|
"Revel framework version", c.FrameworkVersion.String(), "Revel tool version", c.CommandVersion.String())
|
||||||
|
}
|
||||||
|
utils.Logger.Info("Revel versions", "revel-tool", c.CommandVersion.String(), "Revel Framework", c.FrameworkVersion.String())
|
||||||
|
}
|
||||||
|
if !required {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(importPath) == 0 {
|
||||||
|
return fmt.Errorf("%w: %s", ErrUnableToImport, importPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandConfig) initAppFolder() (err error) {
|
||||||
|
utils.Logger.Info("initAppFolder", "vendored", c.Vendored, "build-gopath", build.Default.GOPATH, "gopath-env", os.Getenv("GOPATH"))
|
||||||
|
|
||||||
|
// check for go executable
|
||||||
|
c.GoCmd, err = exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Go executable not found in PATH.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// First try to determine where the application is located - this should be the import value
|
||||||
|
appFolder := c.ImportPath
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
if len(appFolder) == 0 {
|
||||||
|
// We will assume the working directory is the appFolder
|
||||||
|
appFolder = wd
|
||||||
|
} else if strings.LastIndex(wd, appFolder) == len(wd)-len(appFolder) {
|
||||||
|
// Check for existence of an /app folder
|
||||||
|
if utils.Exists(filepath.Join(wd, "app")) {
|
||||||
|
appFolder = wd
|
||||||
|
} else {
|
||||||
|
appFolder = filepath.Join(wd, appFolder)
|
||||||
|
}
|
||||||
|
} else if !filepath.IsAbs(appFolder) {
|
||||||
|
appFolder = filepath.Join(wd, appFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Logger.Info("Determined app folder to be", "appfolder", appFolder, "working", wd, "importPath", c.ImportPath)
|
||||||
|
|
||||||
|
// Use app folder to read the go.mod if it exists and extract the package information
|
||||||
|
goModFile := filepath.Join(appFolder, "go.mod")
|
||||||
|
utils.Logger.Info("Checking gomod, extracting from file", "path", goModFile, "exists", utils.Exists(goModFile))
|
||||||
|
if utils.Exists(goModFile) {
|
||||||
|
c.Vendored = true
|
||||||
|
utils.Logger.Info("Found go mod, extracting from file", "path", goModFile)
|
||||||
|
file, err := ioutil.ReadFile(goModFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, line := range strings.Split(string(file), "\n") {
|
||||||
|
if strings.Index(line, "module ") == 0 {
|
||||||
|
c.ImportPath = strings.TrimSpace(strings.Split(line, "module")[1])
|
||||||
|
c.AppPath = appFolder
|
||||||
|
// c.SrcRoot = appFolder
|
||||||
|
utils.Logger.Info("Set application path and package based on go mod", "path", c.AppPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// c.SrcRoot = appFolder
|
||||||
|
c.AppPath = appFolder
|
||||||
|
} else if c.Index != NEW || (c.Index == NEW && c.New.NotVendored) {
|
||||||
|
workingDir, _ := os.Getwd()
|
||||||
|
goPathList := filepath.SplitList(c.GoPath)
|
||||||
|
bestpath := ""
|
||||||
|
for _, path := range goPathList {
|
||||||
|
if c.Index == NEW {
|
||||||
|
// If the GOPATH is part of the working dir this is the most likely target
|
||||||
|
if strings.HasPrefix(workingDir, path) {
|
||||||
|
bestpath = path
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if utils.Exists(filepath.Join(path, "src", c.ImportPath)) {
|
||||||
|
bestpath = path
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Logger.Info("Source root", "cwd", workingDir, "gopath", c.GoPath, "c.ImportPath", c.ImportPath, "bestpath", bestpath)
|
||||||
|
if len(bestpath) > 0 {
|
||||||
|
c.AppPath = filepath.Join(bestpath, "src", c.ImportPath)
|
||||||
|
}
|
||||||
|
// Recalculate the appFolder because we are using a GOPATH
|
||||||
|
} else {
|
||||||
|
// This is new and not vendored, so the app path is the appFolder
|
||||||
|
c.AppPath = appFolder
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Logger.Info("Set application path", "path", c.AppPath, "vendored", c.Vendored, "importpath", c.ImportPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to initialize the package resolver.
|
||||||
|
func (c *CommandConfig) InitPackageResolver() {
|
||||||
|
c.initGoPaths()
|
||||||
|
utils.Logger.Info("InitPackageResolver", "useVendor", c.Vendored, "path", c.AppPath)
|
||||||
|
|
||||||
|
// This should get called when needed
|
||||||
|
c.PackageResolver = func(pkgName string) error {
|
||||||
|
utils.Logger.Info("Request for package ", "package", pkgName, "use vendor", c.Vendored)
|
||||||
|
var getCmd *exec.Cmd
|
||||||
|
print("Downloading related packages ...")
|
||||||
|
if c.Vendored {
|
||||||
|
getCmd = exec.Command(c.GoCmd, "mod", "tidy", "-v")
|
||||||
|
} else {
|
||||||
|
utils.Logger.Info("No vendor folder detected, not using dependency manager to import package", "package", pkgName)
|
||||||
|
getCmd = exec.Command(c.GoCmd, "get", "-u", pkgName)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.CmdInit(getCmd, !c.Vendored, c.AppPath)
|
||||||
|
utils.Logger.Info("Go get command ", "exec", getCmd.Path, "dir", getCmd.Dir, "args", getCmd.Args, "env", getCmd.Env, "package", pkgName)
|
||||||
|
output, err := getCmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Error("Failed to import package", "error", err, "gopath", build.Default.GOPATH, "GO-ROOT", build.Default.GOROOT, "output", string(output))
|
||||||
|
}
|
||||||
|
println(" completed.")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup and set Go related variables.
|
||||||
|
func (c *CommandConfig) initGoPaths() {
|
||||||
|
utils.Logger.Info("InitGoPaths", "vendored", c.Vendored)
|
||||||
|
// 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.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Vendored {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.")
|
||||||
|
}
|
||||||
|
// todo determine if the rest needs to happen
|
||||||
|
// revel/revel#1004 choose go path relative to current working directory
|
||||||
|
|
||||||
|
// What we want to do is to add the import to the end of the
|
||||||
|
// gopath, and discover which import exists - If none exist this is an error except in the case
|
||||||
|
// where we are dealing with new which is a special case where we will attempt to target the working directory first
|
||||||
|
/*
|
||||||
|
// If source root is empty and this isn't a version then skip it
|
||||||
|
if len(c.SrcRoot) == 0 {
|
||||||
|
if c.Index == NEW {
|
||||||
|
c.SrcRoot = c.New.ImportPath
|
||||||
|
} else {
|
||||||
|
if c.Index != VERSION {
|
||||||
|
utils.Logger.Fatal("Abort: could not create a Revel application outside of GOPATH.")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set go src path
|
||||||
|
c.SrcRoot = filepath.Join(c.SrcRoot, "src")
|
||||||
|
|
||||||
|
c.AppPath = filepath.Join(c.SrcRoot, filepath.FromSlash(c.ImportPath))
|
||||||
|
utils.Logger.Info("Set application path", "path", c.AppPath)
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the versions on the command config.
|
||||||
|
func (c *CommandConfig) GetVerbose() (verbose bool) {
|
||||||
|
if len(c.Verbose) > 0 {
|
||||||
|
verbose = c.Verbose[0]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the versions on the command config.
|
||||||
|
func (c *CommandConfig) SetVersions() (err error) {
|
||||||
|
c.CommandVersion, _ = ParseVersion(cmd.Version)
|
||||||
|
pathMap, err := utils.FindSrcPaths(c.AppPath, []string{RevelImportPath}, c.PackageResolver)
|
||||||
|
if err == nil {
|
||||||
|
utils.Logger.Info("Fullpath to revel", "dir", pathMap[RevelImportPath])
|
||||||
|
fset := token.NewFileSet() // positions are relative to fset
|
||||||
|
|
||||||
|
versionData, err := ioutil.ReadFile(filepath.Join(pathMap[RevelImportPath], "version.go"))
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Error("Failed to find Revel version:", "error", err, "path", pathMap[RevelImportPath])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse src but stop after processing the imports.
|
||||||
|
f, err := parser.ParseFile(fset, "", versionData, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
return utils.NewBuildError("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)
|
||||||
|
if spec.Names[0].Name == "Version" {
|
||||||
|
c.FrameworkVersion, err = ParseVersion(strings.ReplaceAll(r.Value, `"`, ``))
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Errorf("Failed to parse version")
|
||||||
|
} else {
|
||||||
|
utils.Logger.Info("Parsed revel version", "version", c.FrameworkVersion.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
11
model/embedded_type_name.go
Normal file
11
model/embedded_type_name.go
Normal 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
|
||||||
|
}
|
||||||
61
model/event.go
Normal file
61
model/event.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type (
|
||||||
|
// The event type.
|
||||||
|
Event int
|
||||||
|
// The event response.
|
||||||
|
EventResponse int
|
||||||
|
// The handler signature.
|
||||||
|
EventHandler func(typeOf Event, value interface{}) (responseOf EventResponse)
|
||||||
|
RevelCallback interface {
|
||||||
|
FireEvent(key Event, value interface{}) (response EventResponse)
|
||||||
|
PackageResolver(pkgName string) error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Event type when templates are going to be refreshed (receivers are registered template engines added to the template.engine conf option).
|
||||||
|
TEMPLATE_REFRESH_REQUESTED Event = iota
|
||||||
|
// Event type when templates are refreshed (receivers are registered template engines added to the template.engine conf option).
|
||||||
|
TEMPLATE_REFRESH_COMPLETED
|
||||||
|
// Event type before all module loads, events thrown to handlers added to AddInitEventHandler.
|
||||||
|
|
||||||
|
// Event type before all module loads, events thrown to handlers added to AddInitEventHandler.
|
||||||
|
REVEL_BEFORE_MODULES_LOADED
|
||||||
|
// Event type before module loads, events thrown to handlers added to AddInitEventHandler.
|
||||||
|
REVEL_BEFORE_MODULE_LOADED
|
||||||
|
// Event type after module loads, events thrown to handlers added to AddInitEventHandler.
|
||||||
|
REVEL_AFTER_MODULE_LOADED
|
||||||
|
// Event type after all module loads, events thrown to handlers added to AddInitEventHandler.
|
||||||
|
REVEL_AFTER_MODULES_LOADED
|
||||||
|
|
||||||
|
// Event type before server engine is initialized, receivers are active server engine and handlers added to AddInitEventHandler.
|
||||||
|
ENGINE_BEFORE_INITIALIZED
|
||||||
|
// Event type before server engine is started, receivers are active server engine and handlers added to AddInitEventHandler.
|
||||||
|
ENGINE_STARTED
|
||||||
|
// Event type after server engine is stopped, receivers are active server engine and handlers added to AddInitEventHandler.
|
||||||
|
ENGINE_SHUTDOWN
|
||||||
|
|
||||||
|
// Called before routes are refreshed.
|
||||||
|
ROUTE_REFRESH_REQUESTED
|
||||||
|
// Called after routes have been refreshed.
|
||||||
|
ROUTE_REFRESH_COMPLETED
|
||||||
|
|
||||||
|
// Fired when a panic is caught during the startup process.
|
||||||
|
REVEL_FAILURE
|
||||||
|
)
|
||||||
|
|
||||||
|
var initEventList = []EventHandler{} // Event handler list for receiving events
|
||||||
|
|
||||||
|
// Fires system events from revel.
|
||||||
|
func RaiseEvent(key Event, value interface{}) (response EventResponse) {
|
||||||
|
for _, handler := range initEventList {
|
||||||
|
response |= handler(key, value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event handler to listen for all system events.
|
||||||
|
func AddInitEventHandler(handler EventHandler) {
|
||||||
|
initEventList = append(initEventList, handler)
|
||||||
|
}
|
||||||
24
model/event_test.go
Normal file
24
model/event_test.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package model_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/revel/revel"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test that the event handler can be attached and it dispatches the event received.
|
||||||
|
func TestEventHandler(t *testing.T) {
|
||||||
|
counter := 0
|
||||||
|
newListener := func(typeOf revel.Event, value interface{}) (responseOf revel.EventResponse) {
|
||||||
|
if typeOf == revel.ENGINE_SHUTDOWN_REQUEST {
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Attach the same handler twice so we expect to see the response twice as well
|
||||||
|
revel.AddInitEventHandler(newListener)
|
||||||
|
revel.AddInitEventHandler(newListener)
|
||||||
|
revel.StopServer(1)
|
||||||
|
assert.Equal(t, counter, 2, "Expected event handler to have been called")
|
||||||
|
}
|
||||||
23
model/method.go
Normal file
23
model/method.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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.
|
||||||
|
}
|
||||||
315
model/revel_container.go
Normal file
315
model/revel_container.go
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
// This package will be shared between Revel and Revel CLI eventually
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"github.com/revel/config"
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error is used for constant errors.
|
||||||
|
type Error string
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrNoApp Error = "no app found at path"
|
||||||
|
ErrNoConfig Error = "no config found at path"
|
||||||
|
ErrNotFound Error = "not found"
|
||||||
|
ErrMissingCert Error = "no http.sslcert provided"
|
||||||
|
ErrMissingKey Error = "no http.sslkey provided"
|
||||||
|
ErrNoFiles Error = "no files found in import path"
|
||||||
|
ErrNoPackages Error = "no packages found for import"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// The container object for describing all Revels variables.
|
||||||
|
RevelContainer struct {
|
||||||
|
BuildPaths struct {
|
||||||
|
Revel string
|
||||||
|
}
|
||||||
|
Paths struct {
|
||||||
|
Import string
|
||||||
|
Source string
|
||||||
|
Base string
|
||||||
|
App string
|
||||||
|
Views string
|
||||||
|
Code []string
|
||||||
|
Template []string
|
||||||
|
Config []string
|
||||||
|
}
|
||||||
|
PackageInfo struct {
|
||||||
|
Config config.Context
|
||||||
|
Packaged bool
|
||||||
|
DevMode bool
|
||||||
|
Vendor bool
|
||||||
|
}
|
||||||
|
Application struct {
|
||||||
|
Name string
|
||||||
|
Root string
|
||||||
|
}
|
||||||
|
|
||||||
|
ImportPath string // The import path
|
||||||
|
SourcePath string // The full source path
|
||||||
|
RunMode string // The current run mode
|
||||||
|
RevelPath string // The path to the Revel source code
|
||||||
|
BasePath string // The base path to the application
|
||||||
|
AppPath string // The application path (BasePath + "/app")
|
||||||
|
ViewsPath string // The application views path
|
||||||
|
CodePaths []string // All the code paths
|
||||||
|
TemplatePaths []string // All the template paths
|
||||||
|
ConfPaths []string // All the configuration paths
|
||||||
|
Config *config.Context // The global config object
|
||||||
|
Packaged bool // True if packaged
|
||||||
|
DevMode bool // True if running in dev mode
|
||||||
|
HTTPPort int // The http port
|
||||||
|
HTTPAddr string // The http address
|
||||||
|
HTTPSsl bool // True if running https
|
||||||
|
HTTPSslCert string // The SSL certificate
|
||||||
|
HTTPSslKey string // The SSL key
|
||||||
|
AppName string // The application name
|
||||||
|
AppRoot string // The application root from the config `app.root`
|
||||||
|
CookiePrefix string // The cookie prefix
|
||||||
|
CookieDomain string // The cookie domain
|
||||||
|
CookieSecure bool // True if cookie is secure
|
||||||
|
SecretStr string // The secret string
|
||||||
|
MimeConfig *config.Context // The mime configuration
|
||||||
|
ModulePathMap map[string]*ModuleInfo // The module path map
|
||||||
|
}
|
||||||
|
ModuleInfo struct {
|
||||||
|
ImportPath string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
WrappedRevelCallback struct {
|
||||||
|
FireEventFunction func(key Event, value interface{}) (response EventResponse)
|
||||||
|
ImportFunction func(pkgName string) error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Simple Wrapped RevelCallback.
|
||||||
|
func NewWrappedRevelCallback(fe func(key Event, value interface{}) (response EventResponse), ie func(pkgName string) error) RevelCallback {
|
||||||
|
return &WrappedRevelCallback{fe, ie}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to implement the FireEvent.
|
||||||
|
func (w *WrappedRevelCallback) FireEvent(key Event, value interface{}) (response EventResponse) {
|
||||||
|
if w.FireEventFunction != nil {
|
||||||
|
response = w.FireEventFunction(key, value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WrappedRevelCallback) PackageResolver(pkgName string) error {
|
||||||
|
return w.ImportFunction(pkgName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevelImportPath Revel framework import path.
|
||||||
|
var (
|
||||||
|
RevelImportPath = "github.com/revel/revel"
|
||||||
|
RevelModulesImportPath = "github.com/revel/modules"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This function returns a container object describing the revel application
|
||||||
|
// eventually this type of function will replace the global variables.
|
||||||
|
func NewRevelPaths(mode, importPath, appSrcPath string, callback RevelCallback) (rp *RevelContainer, err error) {
|
||||||
|
rp = &RevelContainer{ModulePathMap: map[string]*ModuleInfo{}}
|
||||||
|
// Ignore trailing slashes.
|
||||||
|
rp.ImportPath = strings.TrimRight(importPath, "/")
|
||||||
|
rp.SourcePath = appSrcPath
|
||||||
|
rp.RunMode = mode
|
||||||
|
|
||||||
|
// We always need to determine the paths for files
|
||||||
|
pathMap, err := utils.FindSrcPaths(appSrcPath, []string{importPath + "/app", RevelImportPath}, callback.PackageResolver)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rp.AppPath, rp.RevelPath = pathMap[importPath], pathMap[RevelImportPath]
|
||||||
|
// Setup paths for application
|
||||||
|
rp.BasePath = rp.SourcePath
|
||||||
|
rp.PackageInfo.Vendor = utils.Exists(filepath.Join(rp.BasePath, "go.mod"))
|
||||||
|
rp.AppPath = filepath.Join(rp.BasePath, "app")
|
||||||
|
|
||||||
|
// Sanity check , ensure app and conf paths exist
|
||||||
|
if !utils.DirExists(rp.AppPath) {
|
||||||
|
return rp, fmt.Errorf("%w: %s", ErrNoApp, rp.AppPath)
|
||||||
|
}
|
||||||
|
if !utils.DirExists(filepath.Join(rp.BasePath, "conf")) {
|
||||||
|
return rp, fmt.Errorf("%w: %s", ErrNoConfig, filepath.Join(rp.BasePath, "conf"))
|
||||||
|
}
|
||||||
|
|
||||||
|
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...)
|
||||||
|
|
||||||
|
rp.Config, err = config.LoadContext("app.conf", rp.ConfPaths)
|
||||||
|
if err != nil {
|
||||||
|
return rp, fmt.Errorf("unable to load configuration file %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
return rp, fmt.Errorf("app.conf: %w %s %s", ErrNotFound, "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 == "" {
|
||||||
|
return rp, ErrMissingCert
|
||||||
|
}
|
||||||
|
|
||||||
|
if rp.HTTPSslKey == "" {
|
||||||
|
return rp, ErrMissingKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
utils.Logger.Info("Loading modules")
|
||||||
|
if err := rp.loadModules(callback); err != nil {
|
||||||
|
return rp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.FireEvent(REVEL_AFTER_MODULES_LOADED, nil)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadMimeConfig load mime-types.conf on init.
|
||||||
|
func (rp *RevelContainer) LoadMimeConfig() (err error) {
|
||||||
|
rp.MimeConfig, err = config.LoadContext("mime-types.conf", rp.ConfPaths)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load mime type config: %s %w", "error", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) (err error) {
|
||||||
|
keys := []string{}
|
||||||
|
keys = append(keys, rp.Config.Options("module.")...)
|
||||||
|
|
||||||
|
// 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.Info("Missing module ", "module_import_path", moduleImportPath, "error", err)
|
||||||
|
|
||||||
|
if err := callback.PackageResolver(moduleImportPath); err != nil {
|
||||||
|
return fmt.Errorf("failed to resolve package %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
modulePath, err = rp.ResolveImportPath(moduleImportPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load module. Import of path failed %s:%s %s:%w ", "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})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a module paths to the container object.
|
||||||
|
func (rp *RevelContainer) addModulePaths(name, importPath, modulePath string) {
|
||||||
|
utils.Logger.Info("Adding module path", "name", name, "import path", importPath, "system path", modulePath)
|
||||||
|
if codePath := filepath.Join(modulePath, "app"); utils.DirExists(codePath) {
|
||||||
|
rp.CodePaths = append(rp.CodePaths, codePath)
|
||||||
|
rp.ModulePathMap[name] = &ModuleInfo{importPath, 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
|
||||||
|
}
|
||||||
|
config := &packages.Config{
|
||||||
|
Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports |
|
||||||
|
packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo,
|
||||||
|
Dir: rp.AppPath,
|
||||||
|
}
|
||||||
|
config.Env = utils.ReducedEnv(false)
|
||||||
|
pkgs, err := packages.Load(config, importPath)
|
||||||
|
if len(pkgs) == 0 {
|
||||||
|
return "", fmt.Errorf("%w %s using app path %s", ErrNoPackages, importPath, rp.AppPath)
|
||||||
|
}
|
||||||
|
// modPkg, err := build.Import(importPath, rp.AppPath, build.FindOnly)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(pkgs[0].GoFiles) > 0 {
|
||||||
|
return filepath.Dir(pkgs[0].GoFiles[0]), nil
|
||||||
|
}
|
||||||
|
return pkgs[0].PkgPath, fmt.Errorf("%w: %s", ErrNoFiles, importPath)
|
||||||
|
}
|
||||||
141
model/source_info.go
Normal file
141
model/source_info.go
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
// SourceInfo is the top-level struct containing all extracted information
|
||||||
|
// about the app source code, used to generate main.go.
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
// packageMap a map of import to system directory (if available)
|
||||||
|
PackageMap map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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:]...)
|
||||||
|
//nolint:ineffassign // huh?
|
||||||
|
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 {
|
||||||
|
utils.Logger.Info("Scanning controller specifications for types ", "typePath", RevelImportPath+".Controller", "speclen", len(s.controllerSpecs))
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SourceInfo) Merge(srcInfo2 *SourceInfo) {
|
||||||
|
s.StructSpecs = append(s.StructSpecs, srcInfo2.StructSpecs...)
|
||||||
|
s.InitImportPaths = append(s.InitImportPaths, srcInfo2.InitImportPaths...)
|
||||||
|
for k, v := range srcInfo2.ValidationKeys {
|
||||||
|
if _, ok := s.ValidationKeys[k]; ok {
|
||||||
|
utils.Logger.Warn("Warn: Key conflict when scanning validation calls:", "key", k)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.ValidationKeys[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
101
model/type_expr.go
Normal file
101
model/type_expr.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
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 {
|
||||||
|
err := ""
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
err = 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:
|
||||||
|
err = fmt.Sprintf("Failed to generate name for field: %v Package: %v. Make sure the field name is valid.", expr, pkgName)
|
||||||
|
}
|
||||||
|
return NewTypeExprFromData(err, "", 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{}{ //nolint:gochecknoglobals
|
||||||
|
"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 arguments.
|
||||||
|
func FirstNonEmpty(strs ...string) string {
|
||||||
|
for _, str := range strs {
|
||||||
|
if len(str) > 0 {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
15
model/type_info.go
Normal file
15
model/type_info.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
126
model/version.go
Normal file
126
model/version.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Version struct {
|
||||||
|
Prefix string
|
||||||
|
Major int
|
||||||
|
Minor int
|
||||||
|
Maintenance int
|
||||||
|
Suffix string
|
||||||
|
BuildDate string
|
||||||
|
MinGoVersion string
|
||||||
|
}
|
||||||
|
|
||||||
|
// The compatibility list.
|
||||||
|
var frameworkCompatibleRangeList = [][]string{
|
||||||
|
{"0.0.0", "0.20.0"}, // minimum Revel version to use with this version of the tool
|
||||||
|
{"0.19.99", "0.30.0"}, // Compatible with Framework V 0.19.99 - 0.30.0
|
||||||
|
{"1.0.0", "1.9.0"}, // Compatible with Framework V 1.0 - 1.9
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a version like v1.2.3a or 1.2.
|
||||||
|
var versionRegExp = regexp.MustCompile(`([^\d]*)?([0-9]*)\.([0-9]*)(\.([0-9]*))?(.*)`)
|
||||||
|
|
||||||
|
// Parse the version and return it as a Version object.
|
||||||
|
func ParseVersion(version string) (v *Version, err error) {
|
||||||
|
v = &Version{}
|
||||||
|
return v, v.ParseVersion(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the version and return it as a Version object.
|
||||||
|
func (v *Version) ParseVersion(version string) (err error) {
|
||||||
|
parsedResult := versionRegExp.FindAllStringSubmatch(version, -1)
|
||||||
|
if len(parsedResult) != 1 {
|
||||||
|
err = errors.Errorf("Invalid version %s", version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(parsedResult[0]) != 7 {
|
||||||
|
err = errors.Errorf("Invalid version %s", version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Prefix = parsedResult[0][1]
|
||||||
|
v.Major = v.intOrZero(parsedResult[0][2])
|
||||||
|
v.Minor = v.intOrZero(parsedResult[0][3])
|
||||||
|
v.Maintenance = v.intOrZero(parsedResult[0][5])
|
||||||
|
v.Suffix = parsedResult[0][6]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns 0 or an int value for the string, errors are returned as 0.
|
||||||
|
func (v *Version) intOrZero(input string) (value int) {
|
||||||
|
if input != "" {
|
||||||
|
value, _ = strconv.Atoi(input)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if this major revision is compatible.
|
||||||
|
func (v *Version) CompatibleFramework(c *CommandConfig) error {
|
||||||
|
for i, rv := range frameworkCompatibleRangeList {
|
||||||
|
start, _ := ParseVersion(rv[0])
|
||||||
|
end, _ := ParseVersion(rv[1])
|
||||||
|
if !v.Newer(start) || v.Newer(end) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Framework is older then 0.20, turn on historic mode
|
||||||
|
if i == 0 {
|
||||||
|
c.HistoricMode = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("Tool out of date - do a 'go get -u github.com/revel/cmd/revel'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if this major revision is newer then the passed in.
|
||||||
|
func (v *Version) MajorNewer(o *Version) bool {
|
||||||
|
if v.Major != o.Major {
|
||||||
|
return v.Major > o.Major
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if this major or major and minor revision is newer then the value passed in.
|
||||||
|
func (v *Version) MinorNewer(o *Version) bool {
|
||||||
|
if v.Major != o.Major {
|
||||||
|
return v.Major > o.Major
|
||||||
|
}
|
||||||
|
if v.Minor != o.Minor {
|
||||||
|
return v.Minor > o.Minor
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the version is newer then the current on.
|
||||||
|
func (v *Version) Newer(o *Version) bool {
|
||||||
|
if v.Major != o.Major {
|
||||||
|
return v.Major > o.Major
|
||||||
|
}
|
||||||
|
if v.Minor != o.Minor {
|
||||||
|
return v.Minor > o.Minor
|
||||||
|
}
|
||||||
|
if v.Maintenance != o.Maintenance {
|
||||||
|
return v.Maintenance > o.Maintenance
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the version to a string.
|
||||||
|
func (v *Version) VersionString() string {
|
||||||
|
return fmt.Sprintf("%s%d.%d.%d%s", v.Prefix, v.Major, v.Minor, v.Maintenance, v.Suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the version build date and go version to a string.
|
||||||
|
func (v *Version) String() string {
|
||||||
|
return fmt.Sprintf("Version: %s%d.%d.%d%s\nBuild Date: %s\n Minimum Go Version: %s",
|
||||||
|
v.Prefix, v.Major, v.Minor, v.Maintenance, v.Suffix, v.BuildDate, v.MinGoVersion)
|
||||||
|
}
|
||||||
35
model/version_test.go
Normal file
35
model/version_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package model_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var versionTests = [][]string{
|
||||||
|
{"v0.20.0-dev", "v0.20.0-dev"},
|
||||||
|
{"v0.20-dev", "v0.20.0-dev"},
|
||||||
|
{"v0.20.", "v0.20.0"},
|
||||||
|
{"2.0", "2.0.0"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that the event handler can be attached and it dispatches the event received.
|
||||||
|
func TestVersion(t *testing.T) {
|
||||||
|
for _, v := range versionTests {
|
||||||
|
p, e := model.ParseVersion(v[0])
|
||||||
|
assert.Nil(t, e, "Should have parsed %s", v)
|
||||||
|
assert.Equal(t, p.String(), v[1], "Should be equal %s==%s", p.String(), v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test the ranges.
|
||||||
|
func TestVersionRange(t *testing.T) {
|
||||||
|
a, _ := model.ParseVersion("0.1.2")
|
||||||
|
b, _ := model.ParseVersion("0.2.1")
|
||||||
|
c, _ := model.ParseVersion("1.0.1")
|
||||||
|
assert.True(t, b.MinorNewer(a), "B is newer then A")
|
||||||
|
assert.False(t, b.MajorNewer(a), "B is not major newer then A")
|
||||||
|
assert.False(t, b.MajorNewer(c), "B is not major newer then A")
|
||||||
|
assert.True(t, c.MajorNewer(b), "C is major newer then b")
|
||||||
|
}
|
||||||
224
parser/appends.go
Normal file
224
parser/appends.go
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// If this Decl is a struct type definition, it is summarized and added to specs.
|
||||||
|
// Else, specs is returned unchanged.
|
||||||
|
func appendStruct(fileName string, specs []*model.TypeInfo, pkgImportPath string, pkg *ast.Package, decl ast.Decl, imports map[string]string, fset *token.FileSet) []*model.TypeInfo {
|
||||||
|
// Filter out non-Struct type declarations.
|
||||||
|
spec, found := getStructTypeDecl(decl, fset)
|
||||||
|
if !found {
|
||||||
|
return specs
|
||||||
|
}
|
||||||
|
|
||||||
|
structType := spec.Type.(*ast.StructType)
|
||||||
|
|
||||||
|
// At this point we know it's a type declaration for a struct.
|
||||||
|
// Fill in the rest of the info by diving into the fields.
|
||||||
|
// Add it provisionally to the Controller list -- it's later filtered using field info.
|
||||||
|
controllerSpec := &model.TypeInfo{
|
||||||
|
StructName: spec.Name.Name,
|
||||||
|
ImportPath: pkgImportPath,
|
||||||
|
PackageName: pkg.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, field := range structType.Fields.List {
|
||||||
|
// If field.Names is set, it's not an embedded type.
|
||||||
|
if field.Names != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// A direct "sub-type" has an ast.Field as either:
|
||||||
|
// Ident { "AppController" }
|
||||||
|
// SelectorExpr { "rev", "Controller" }
|
||||||
|
// Additionally, that can be wrapped by StarExprs.
|
||||||
|
fieldType := field.Type
|
||||||
|
pkgName, typeName := func() (string, string) {
|
||||||
|
// Drill through any StarExprs.
|
||||||
|
for {
|
||||||
|
if starExpr, ok := fieldType.(*ast.StarExpr); ok {
|
||||||
|
fieldType = starExpr.X
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the embedded type is in the same package, it's an Ident.
|
||||||
|
if ident, ok := fieldType.(*ast.Ident); ok {
|
||||||
|
return "", ident.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
|
||||||
|
if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
|
||||||
|
return pkgIdent.Name, selectorExpr.Sel.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
|
}()
|
||||||
|
|
||||||
|
// If a typename wasn't found, skip it.
|
||||||
|
if typeName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the import path for this type.
|
||||||
|
// If it was referenced without a package name, use the current package import path.
|
||||||
|
// Else, look up the package's import path by name.
|
||||||
|
var importPath string
|
||||||
|
if pkgName == "" {
|
||||||
|
importPath = pkgImportPath
|
||||||
|
} else {
|
||||||
|
var ok bool
|
||||||
|
if importPath, ok = imports[pkgName]; !ok {
|
||||||
|
utils.Logger.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{
|
||||||
|
ImportPath: importPath,
|
||||||
|
StructName: typeName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(specs, controllerSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If decl is a Method declaration, it is summarized and added to the array
|
||||||
|
// underneath its receiver type.
|
||||||
|
// e.g. "Login" => {MethodSpec, MethodSpec, ..}.
|
||||||
|
func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPath, pkgName string, imports map[string]string) {
|
||||||
|
// Func declaration?
|
||||||
|
funcDecl, ok := decl.(*ast.FuncDecl)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have a receiver?
|
||||||
|
if funcDecl.Recv == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it public?
|
||||||
|
if !funcDecl.Name.IsExported() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does it return a Result?
|
||||||
|
if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if selExpr.Sel.Name != "Result" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != model.RevelImportPath {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
method := &model.MethodSpec{
|
||||||
|
Name: funcDecl.Name.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a description of the arguments to the method.
|
||||||
|
for _, field := range funcDecl.Type.Params.List {
|
||||||
|
for _, name := range field.Names {
|
||||||
|
var importPath string
|
||||||
|
typeExpr := model.NewTypeExprFromAst(pkgName, field.Type)
|
||||||
|
if !typeExpr.Valid {
|
||||||
|
utils.Logger.Warnf("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, getFuncName(funcDecl))
|
||||||
|
return // We didn't understand one of the args. Ignore this action.
|
||||||
|
}
|
||||||
|
// Local object
|
||||||
|
if typeExpr.PkgName == pkgName {
|
||||||
|
importPath = pkgImportPath
|
||||||
|
} else if typeExpr.PkgName != "" {
|
||||||
|
var ok bool
|
||||||
|
if importPath, ok = imports[typeExpr.PkgName]; !ok {
|
||||||
|
utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
method.Args = append(method.Args, &model.MethodArg{
|
||||||
|
Name: name.Name,
|
||||||
|
TypeExpr: typeExpr,
|
||||||
|
ImportPath: importPath,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a description of the calls to Render from the method.
|
||||||
|
// Inspect every node (e.g. always return true).
|
||||||
|
method.RenderCalls = []*model.MethodCall{}
|
||||||
|
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||||
|
// Is it a function call?
|
||||||
|
callExpr, ok := node.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it calling (*Controller).Render?
|
||||||
|
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type of the receiver is not easily available, so just store every
|
||||||
|
// call to any method called Render.
|
||||||
|
if selExpr.Sel.Name != "Render" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this call's args to the renderArgs.
|
||||||
|
pos := fset.Position(callExpr.Lparen)
|
||||||
|
methodCall := &model.MethodCall{
|
||||||
|
Line: pos.Line,
|
||||||
|
Names: []string{},
|
||||||
|
}
|
||||||
|
for _, arg := range callExpr.Args {
|
||||||
|
argIdent, ok := arg.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
methodCall.Names = append(methodCall.Names, argIdent.Name)
|
||||||
|
}
|
||||||
|
method.RenderCalls = append(method.RenderCalls, methodCall)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
var recvTypeName string
|
||||||
|
recvType := funcDecl.Recv.List[0].Type
|
||||||
|
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||||
|
recvTypeName = recvStarType.X.(*ast.Ident).Name
|
||||||
|
} else {
|
||||||
|
recvTypeName = recvType.(*ast.Ident).Name
|
||||||
|
}
|
||||||
|
|
||||||
|
mm[recvTypeName] = append(mm[recvTypeName], method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine the 2 source info models into one.
|
||||||
|
func appendSourceInfo(srcInfo1, srcInfo2 *model.SourceInfo) *model.SourceInfo {
|
||||||
|
if srcInfo1 == nil {
|
||||||
|
return srcInfo2
|
||||||
|
}
|
||||||
|
|
||||||
|
srcInfo1.StructSpecs = append(srcInfo1.StructSpecs, srcInfo2.StructSpecs...)
|
||||||
|
srcInfo1.InitImportPaths = append(srcInfo1.InitImportPaths, srcInfo2.InitImportPaths...)
|
||||||
|
for k, v := range srcInfo2.ValidationKeys {
|
||||||
|
if _, ok := srcInfo1.ValidationKeys[k]; ok {
|
||||||
|
utils.Logger.Warn("Warn: Key conflict when scanning validation calls:", "key", k)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
srcInfo1.ValidationKeys[k] = v
|
||||||
|
}
|
||||||
|
return srcInfo1
|
||||||
|
}
|
||||||
87
parser/imports.go
Normal file
87
parser/imports.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/build"
|
||||||
|
"go/token"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add imports to the map from the source dir.
|
||||||
|
func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if genDecl.Tok != token.IMPORT {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, spec := range genDecl.Specs {
|
||||||
|
importSpec := spec.(*ast.ImportSpec)
|
||||||
|
var pkgAlias string
|
||||||
|
if importSpec.Name != nil {
|
||||||
|
pkgAlias = importSpec.Name.Name
|
||||||
|
if pkgAlias == "_" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\""
|
||||||
|
if quotedPath == `"C"` {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes
|
||||||
|
|
||||||
|
// If the package was not aliased (common case), we have to import it
|
||||||
|
// to see what the package name is.
|
||||||
|
// TODO: Can improve performance here a lot:
|
||||||
|
// 1. Do not import everything over and over again. Keep a cache.
|
||||||
|
// 2. Exempt the standard library; their directories always match the package name.
|
||||||
|
// 3. Can use build.FindOnly and then use parser.ParseDir with mode PackageClauseOnly
|
||||||
|
if pkgAlias == "" {
|
||||||
|
utils.Logger.Debug("Reading from build", "path", fullPath, "srcPath", srcDir, "gopath", build.Default.GOPATH)
|
||||||
|
pkg, err := build.Import(fullPath, srcDir, 0)
|
||||||
|
if err != nil {
|
||||||
|
// We expect this to happen for apps using reverse routing (since we
|
||||||
|
// have not yet generated the routes). Don't log that.
|
||||||
|
if !strings.HasSuffix(fullPath, "/app/routes") {
|
||||||
|
utils.Logger.Warn("Could not find import:", "path", fullPath, "srcPath", srcDir, "error", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
utils.Logger.Debug("Found package in dir", "dir", pkg.Dir, "name", pkg.ImportPath)
|
||||||
|
}
|
||||||
|
pkgAlias = pkg.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
imports[pkgAlias] = fullPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a valid import string from the path
|
||||||
|
// using the build.Defaul.GOPATH to determine the root.
|
||||||
|
func importPathFromPath(root, basePath string) string {
|
||||||
|
vendorTest := filepath.Join(basePath, "vendor")
|
||||||
|
if len(root) > len(vendorTest) && root[:len(vendorTest)] == vendorTest {
|
||||||
|
return filepath.ToSlash(root[len(vendorTest)+1:])
|
||||||
|
}
|
||||||
|
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
|
||||||
|
srcPath := filepath.Join(gopath, "src")
|
||||||
|
if strings.HasPrefix(root, srcPath) {
|
||||||
|
return filepath.ToSlash(root[len(srcPath)+1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
srcPath := filepath.Join(build.Default.GOROOT, "src", "pkg")
|
||||||
|
if strings.HasPrefix(root, srcPath) {
|
||||||
|
utils.Logger.Warn("Code path should be in GOPATH, but is in GOROOT:", "path", root)
|
||||||
|
return filepath.ToSlash(root[len(srcPath)+1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Logger.Error("Unexpected! Code path is not in GOPATH:", "path", root)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
249
parser/reflect.go
Normal file
249
parser/reflect.go
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
// 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 parser
|
||||||
|
|
||||||
|
// This file handles the app code introspection.
|
||||||
|
// It catalogs the controllers, their methods, and their arguments.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/scanner"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A container used to support the reflection package.
|
||||||
|
type processContainer struct {
|
||||||
|
root, rootImportPath string // The paths
|
||||||
|
paths *model.RevelContainer // The Revel paths
|
||||||
|
srcInfo *model.SourceInfo // The source information container
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps a controller simple name (e.g. "Login") to the methods for which it is a
|
||||||
|
// receiver.
|
||||||
|
type methodMap map[string][]*model.MethodSpec
|
||||||
|
|
||||||
|
// ProcessSource parses the app controllers directory and
|
||||||
|
// returns a list of the controller types found.
|
||||||
|
// Otherwise CompileError if the parsing fails.
|
||||||
|
func ProcessSource(paths *model.RevelContainer) (_ *model.SourceInfo, compileError error) {
|
||||||
|
pc := &processContainer{paths: paths}
|
||||||
|
for _, root := range paths.CodePaths {
|
||||||
|
rootImportPath := importPathFromPath(root, paths.BasePath)
|
||||||
|
if rootImportPath == "" {
|
||||||
|
utils.Logger.Info("Skipping empty code path", "path", root)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pc.root, pc.rootImportPath = root, rootImportPath
|
||||||
|
|
||||||
|
// Start walking the directory tree.
|
||||||
|
compileError = utils.Walk(root, pc.processPath)
|
||||||
|
if compileError != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pc.srcInfo, compileError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called during the "walk process".
|
||||||
|
func (pc *processContainer) processPath(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Error("Error scanning app source:", "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() || info.Name() == "tmp" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the import path of the package.
|
||||||
|
pkgImportPath := pc.rootImportPath
|
||||||
|
if pc.root != path {
|
||||||
|
pkgImportPath = pc.rootImportPath + "/" + filepath.ToSlash(path[len(pc.root)+1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse files within the path.
|
||||||
|
var pkgs map[string]*ast.Package
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
pkgs, err = parser.ParseDir(
|
||||||
|
fset,
|
||||||
|
path,
|
||||||
|
func(f os.FileInfo) bool {
|
||||||
|
return !f.IsDir() && !strings.HasPrefix(f.Name(), ".") && strings.HasSuffix(f.Name(), ".go")
|
||||||
|
},
|
||||||
|
0)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
var errList scanner.ErrorList
|
||||||
|
if errors.As(err, &errList) {
|
||||||
|
pos := errList[0].Pos
|
||||||
|
newError := &utils.SourceError{
|
||||||
|
SourceType: ".go source",
|
||||||
|
Title: "Go Compilation Error",
|
||||||
|
Path: pos.Filename,
|
||||||
|
Description: errList[0].Msg,
|
||||||
|
Line: pos.Line,
|
||||||
|
Column: pos.Column,
|
||||||
|
SourceLines: utils.MustReadLines(pos.Filename),
|
||||||
|
}
|
||||||
|
|
||||||
|
errorLink := pc.paths.Config.StringDefault("error.link", "")
|
||||||
|
if errorLink != "" {
|
||||||
|
newError.SetLink(errorLink)
|
||||||
|
}
|
||||||
|
return newError
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is exception, err already checked above. Here just a print
|
||||||
|
ast.Print(nil, err)
|
||||||
|
utils.Logger.Fatal("Failed to parse dir", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip "main" packages.
|
||||||
|
delete(pkgs, "main")
|
||||||
|
|
||||||
|
// Ignore packages that end with _test
|
||||||
|
// These cannot be included in source code that is not generated specifically as a test
|
||||||
|
for i := range pkgs {
|
||||||
|
if len(i) > 6 {
|
||||||
|
if i[len(i)-5:] == "_test" {
|
||||||
|
delete(pkgs, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no code in this directory, skip it.
|
||||||
|
if len(pkgs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// There should be only one package in this directory.
|
||||||
|
if len(pkgs) > 1 {
|
||||||
|
for i := range pkgs {
|
||||||
|
println("Found package ", i)
|
||||||
|
}
|
||||||
|
utils.Logger.Fatal("Most unexpected! Multiple packages in a single directory:", "packages", pkgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkg *ast.Package
|
||||||
|
for _, v := range pkgs {
|
||||||
|
pkg = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkg != nil {
|
||||||
|
pc.srcInfo = appendSourceInfo(pc.srcInfo, processPackage(fset, pkgImportPath, path, pkg))
|
||||||
|
} else {
|
||||||
|
utils.Logger.Info("Ignoring package, because it contained no packages", "path", path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process a single package within a file.
|
||||||
|
func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast.Package) *model.SourceInfo {
|
||||||
|
var (
|
||||||
|
structSpecs []*model.TypeInfo
|
||||||
|
initImportPaths []string
|
||||||
|
|
||||||
|
methodSpecs = make(methodMap)
|
||||||
|
validationKeys = make(map[string]map[int]string)
|
||||||
|
scanControllers = strings.HasSuffix(pkgImportPath, "/controllers") ||
|
||||||
|
strings.Contains(pkgImportPath, "/controllers/")
|
||||||
|
scanTests = strings.HasSuffix(pkgImportPath, "/tests") ||
|
||||||
|
strings.Contains(pkgImportPath, "/tests/")
|
||||||
|
)
|
||||||
|
|
||||||
|
// For each source file in the package...
|
||||||
|
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{}
|
||||||
|
|
||||||
|
// For each declaration in the source file...
|
||||||
|
for _, decl := range file.Decls {
|
||||||
|
addImports(imports, decl, pkgPath)
|
||||||
|
|
||||||
|
if scanControllers {
|
||||||
|
// Match and add both structs and methods
|
||||||
|
structSpecs = appendStruct(fname, structSpecs, pkgImportPath, pkg, decl, imports, fset)
|
||||||
|
appendAction(fset, methodSpecs, decl, pkgImportPath, pkg.Name, imports)
|
||||||
|
} else if scanTests {
|
||||||
|
structSpecs = appendStruct(fname, structSpecs, pkgImportPath, pkg, decl, imports, fset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(fname, fset, funcDecl, imports)
|
||||||
|
if len(lineKeys) > 0 {
|
||||||
|
validationKeys[pkgImportPath+"."+getFuncName(funcDecl)] = lineKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's an init function.
|
||||||
|
if funcDecl.Name.Name == "init" {
|
||||||
|
initImportPaths = []string{pkgImportPath}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the method specs to the struct specs.
|
||||||
|
for _, spec := range structSpecs {
|
||||||
|
spec.MethodSpecs = methodSpecs[spec.StructName]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.SourceInfo{
|
||||||
|
StructSpecs: structSpecs,
|
||||||
|
ValidationKeys: validationKeys,
|
||||||
|
InitImportPaths: initImportPaths,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFuncName returns a name for this func or method declaration.
|
||||||
|
// e.g. "(*Application).SayHello" for a method, "SayHello" for a func.
|
||||||
|
func getFuncName(funcDecl *ast.FuncDecl) string {
|
||||||
|
prefix := ""
|
||||||
|
if funcDecl.Recv != nil {
|
||||||
|
recvType := funcDecl.Recv.List[0].Type
|
||||||
|
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||||
|
prefix = "(*" + recvStarType.X.(*ast.Ident).Name + ")"
|
||||||
|
} else {
|
||||||
|
prefix = recvType.(*ast.Ident).Name
|
||||||
|
}
|
||||||
|
prefix += "."
|
||||||
|
}
|
||||||
|
return prefix + funcDecl.Name.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStructTypeDecl checks if the given decl is a type declaration for a
|
||||||
|
// struct. If so, the TypeSpec is returned.
|
||||||
|
func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) {
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if genDecl.Tok != token.TYPE {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(genDecl.Specs) == 0 {
|
||||||
|
utils.Logger.Warn("Warn: Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
spec = genDecl.Specs[0].(*ast.TypeSpec)
|
||||||
|
_, found = spec.Type.(*ast.StructType)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -2,19 +2,18 @@
|
|||||||
// Revel Framework source code and usage is governed by a MIT style
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package harness
|
package parser_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
revelParser "github.com/revel/cmd/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
const validationKeysSource = `
|
const validationKeysSource = `
|
||||||
@@ -68,7 +67,7 @@ var expectedValidationKeys = []map[int]string{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// This tests the recording of line number to validation key of the preceeding
|
// This tests the recording of line number to validation key of the preceding
|
||||||
// example source.
|
// example source.
|
||||||
func TestGetValidationKeys(t *testing.T) {
|
func TestGetValidationKeys(t *testing.T) {
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
@@ -82,7 +81,7 @@ func TestGetValidationKeys(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, decl := range file.Decls {
|
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] {
|
for k, v := range expectedValidationKeys[i] {
|
||||||
if lineKeys[k] != v {
|
if lineKeys[k] != v {
|
||||||
t.Errorf("Not found - %d: %v - Actual Map: %v", k, v, lineKeys)
|
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{
|
var TypeExprs = map[string]model.TypeExpr{
|
||||||
"int": {"int", "", 0, true},
|
"int": model.NewTypeExprFromData("int", "", 0, true),
|
||||||
"*int": {"*int", "", 1, true},
|
"*int": model.NewTypeExprFromData("*int", "", 1, true),
|
||||||
"[]int": {"[]int", "", 2, true},
|
"[]int": model.NewTypeExprFromData("[]int", "", 2, true),
|
||||||
"...int": {"[]int", "", 2, true},
|
"...int": model.NewTypeExprFromData("[]int", "", 2, true),
|
||||||
"[]*int": {"[]*int", "", 3, true},
|
"[]*int": model.NewTypeExprFromData("[]*int", "", 3, true),
|
||||||
"...*int": {"[]*int", "", 3, true},
|
"...*int": model.NewTypeExprFromData("[]*int", "", 3, true),
|
||||||
"MyType": {"MyType", "pkg", 0, true},
|
"MyType": model.NewTypeExprFromData("MyType", "pkg", 0, true),
|
||||||
"*MyType": {"*MyType", "pkg", 1, true},
|
"*MyType": model.NewTypeExprFromData("*MyType", "pkg", 1, true),
|
||||||
"[]MyType": {"[]MyType", "pkg", 2, true},
|
"[]MyType": model.NewTypeExprFromData("[]MyType", "pkg", 2, true),
|
||||||
"...MyType": {"[]MyType", "pkg", 2, true},
|
"...MyType": model.NewTypeExprFromData("[]MyType", "pkg", 2, true),
|
||||||
"[]*MyType": {"[]*MyType", "pkg", 3, true},
|
"[]*MyType": model.NewTypeExprFromData("[]*MyType", "pkg", 3, true),
|
||||||
"...*MyType": {"[]*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) {
|
func TestTypeExpr(t *testing.T) {
|
||||||
@@ -136,60 +137,9 @@ func TestTypeExpr(t *testing.T) {
|
|||||||
expr = &ast.Ellipsis{Ellipsis: expr.Pos(), Elt: expr}
|
expr = &ast.Ellipsis{Ellipsis: expr.Pos(), Elt: expr}
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := NewTypeExpr("pkg", expr)
|
actual := model.NewTypeExprFromAst("pkg", expr)
|
||||||
if !reflect.DeepEqual(expected, actual) {
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
t.Error("Fail, expected", expected, ", was", 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
116
parser/validation.go
Normal file
116
parser/validation.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scan app source code for calls to X.Y(), where X is of type *Validation.
|
||||||
|
//
|
||||||
|
// Recognize these scenarios:
|
||||||
|
// - "Y" = "Validation" and is a member of the receiver.
|
||||||
|
// (The common case for inline validation)
|
||||||
|
// - "X" is passed in to the func as a parameter.
|
||||||
|
// (For structs implementing Validated)
|
||||||
|
//
|
||||||
|
// The line number to which a validation call is attributed is that of the
|
||||||
|
// surrounding ExprStmt. This is so that it matches what runtime.Callers()
|
||||||
|
// reports.
|
||||||
|
//
|
||||||
|
// The end result is that we can set the default validation key for each call to
|
||||||
|
// be the same as the local variable.
|
||||||
|
func GetValidationKeys(fname string, fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[string]string) map[int]string {
|
||||||
|
var (
|
||||||
|
lineKeys = make(map[int]string)
|
||||||
|
|
||||||
|
// Check the func parameters and the receiver's members for the *revel.Validation type.
|
||||||
|
validationParam = getValidationParameter(funcDecl, imports)
|
||||||
|
)
|
||||||
|
|
||||||
|
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||||
|
// e.g. c.Validation.Required(arg) or v.Required(arg)
|
||||||
|
callExpr, ok := node.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// e.g. c.Validation.Required or v.Required
|
||||||
|
funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch x := funcSelector.X.(type) {
|
||||||
|
case *ast.SelectorExpr: // e.g. c.Validation
|
||||||
|
if x.Sel.Name != "Validation" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.Ident: // e.g. v
|
||||||
|
if validationParam == nil || x.Obj != validationParam {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(callExpr.Args) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the validation expression, extract the key.
|
||||||
|
key := callExpr.Args[0]
|
||||||
|
switch expr := key.(type) {
|
||||||
|
case *ast.BinaryExpr:
|
||||||
|
// If the argument is a binary expression, take the first expression.
|
||||||
|
// (e.g. c.Validation.Required(myName != ""))
|
||||||
|
key = expr.X
|
||||||
|
case *ast.UnaryExpr:
|
||||||
|
// If the argument is a unary expression, drill in.
|
||||||
|
// (e.g. c.Validation.Required(!myBool)
|
||||||
|
key = expr.X
|
||||||
|
case *ast.BasicLit:
|
||||||
|
// If it's a literal, skip it.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if typeExpr := model.NewTypeExprFromAst("", key); typeExpr.Valid {
|
||||||
|
lineKeys[fset.Position(callExpr.End()).Line] = typeExpr.TypeName("")
|
||||||
|
} else {
|
||||||
|
utils.Logger.Error("Error: Failed to generate key for field validation. Make sure the field name is valid.", "file", fname,
|
||||||
|
"line", fset.Position(callExpr.End()).Line, "function", funcDecl.Name.String())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return lineKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if there is a *revel.Validation as an argument.
|
||||||
|
func getValidationParameter(funcDecl *ast.FuncDecl, imports map[string]string) *ast.Object {
|
||||||
|
for _, field := range funcDecl.Type.Params.List {
|
||||||
|
starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == model.RevelImportPath {
|
||||||
|
return field.Names[0].Obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
431
parser2/source_info_processor.go
Normal file
431
parser2/source_info_processor.go
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
package parser2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/logger"
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
SourceInfoProcessor struct {
|
||||||
|
sourceProcessor *SourceProcessor
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSourceInfoProcessor(sourceProcessor *SourceProcessor) *SourceInfoProcessor {
|
||||||
|
return &SourceInfoProcessor{sourceProcessor: sourceProcessor}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *model.SourceInfo) {
|
||||||
|
sourceInfo = &model.SourceInfo{
|
||||||
|
ValidationKeys: map[string]map[int]string{},
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
isController = strings.HasSuffix(p.PkgPath, "/controllers") ||
|
||||||
|
strings.Contains(p.PkgPath, "/controllers/")
|
||||||
|
isTest = strings.HasSuffix(p.PkgPath, "/tests") ||
|
||||||
|
strings.Contains(p.PkgPath, "/tests/")
|
||||||
|
methodMap = map[string][]*model.MethodSpec{}
|
||||||
|
)
|
||||||
|
localImportMap := map[string]string{}
|
||||||
|
log := s.sourceProcessor.log.New("package", p.PkgPath)
|
||||||
|
log.Info("Processing package")
|
||||||
|
for _, tree := range p.Syntax {
|
||||||
|
for _, decl := range tree.Decls {
|
||||||
|
s.sourceProcessor.packageMap[p.PkgPath] = filepath.Dir(p.Fset.Position(decl.Pos()).Filename)
|
||||||
|
if !s.addImport(decl, p, localImportMap, log) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
spec, found := s.getStructTypeDecl(decl, p.Fset)
|
||||||
|
// log.Info("Checking file","filename", p.Fset.Position(decl.Pos()).Filename,"found",found)
|
||||||
|
if found {
|
||||||
|
if isController || isTest {
|
||||||
|
controllerSpec := s.getControllerSpec(spec, p, localImportMap)
|
||||||
|
sourceInfo.StructSpecs = append(sourceInfo.StructSpecs, controllerSpec)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not a type definition, this could be a method for a controller try to extract that
|
||||||
|
// Func declaration?
|
||||||
|
funcDecl, ok := decl.(*ast.FuncDecl)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// This could be a controller action endpoint, check and add if needed
|
||||||
|
if isController &&
|
||||||
|
funcDecl.Recv != nil && // Must have a receiver
|
||||||
|
funcDecl.Name.IsExported() && // be public
|
||||||
|
funcDecl.Type.Results != nil && len(funcDecl.Type.Results.List) == 1 {
|
||||||
|
// return one result
|
||||||
|
if m, receiver := s.getControllerFunc(funcDecl, p, localImportMap); m != nil {
|
||||||
|
methodMap[receiver] = append(methodMap[receiver], m)
|
||||||
|
log.Info("Added method map to ", "receiver", receiver, "method", m.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for validation
|
||||||
|
if lineKeyMap := s.getValidation(funcDecl, p); len(lineKeyMap) > 1 {
|
||||||
|
sourceInfo.ValidationKeys[p.PkgPath+"."+s.getFuncName(funcDecl)] = lineKeyMap
|
||||||
|
}
|
||||||
|
if funcDecl.Name.Name == "init" {
|
||||||
|
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, p.PkgPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the method specs to the struct specs.
|
||||||
|
for _, spec := range sourceInfo.StructSpecs {
|
||||||
|
spec.MethodSpecs = methodMap[spec.StructName]
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan app source code for calls to X.Y(), where X is of type *Validation.
|
||||||
|
//
|
||||||
|
// Recognize these scenarios:
|
||||||
|
// - "Y" = "Validation" and is a member of the receiver.
|
||||||
|
// (The common case for inline validation)
|
||||||
|
// - "X" is passed in to the func as a parameter.
|
||||||
|
// (For structs implementing Validated)
|
||||||
|
//
|
||||||
|
// The line number to which a validation call is attributed is that of the
|
||||||
|
// surrounding ExprStmt. This is so that it matches what runtime.Callers()
|
||||||
|
// reports.
|
||||||
|
//
|
||||||
|
// The end result is that we can set the default validation key for each call to
|
||||||
|
// be the same as the local variable.
|
||||||
|
func (s *SourceInfoProcessor) getValidation(funcDecl *ast.FuncDecl, p *packages.Package) map[int]string {
|
||||||
|
var (
|
||||||
|
lineKeys = make(map[int]string)
|
||||||
|
|
||||||
|
// Check the func parameters and the receiver's members for the *revel.Validation type.
|
||||||
|
validationParam = s.getValidationParameter(funcDecl)
|
||||||
|
)
|
||||||
|
|
||||||
|
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||||
|
// e.g. c.Validation.Required(arg) or v.Required(arg)
|
||||||
|
callExpr, ok := node.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// e.g. c.Validation.Required or v.Required
|
||||||
|
funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch x := funcSelector.X.(type) {
|
||||||
|
case *ast.SelectorExpr: // e.g. c.Validation
|
||||||
|
if x.Sel.Name != "Validation" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.Ident: // e.g. v
|
||||||
|
if validationParam == nil || x.Obj != validationParam {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(callExpr.Args) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the validation expression, extract the key.
|
||||||
|
key := callExpr.Args[0]
|
||||||
|
switch expr := key.(type) {
|
||||||
|
case *ast.BinaryExpr:
|
||||||
|
// If the argument is a binary expression, take the first expression.
|
||||||
|
// (e.g. c.Validation.Required(myName != ""))
|
||||||
|
key = expr.X
|
||||||
|
case *ast.UnaryExpr:
|
||||||
|
// If the argument is a unary expression, drill in.
|
||||||
|
// (e.g. c.Validation.Required(!myBool)
|
||||||
|
key = expr.X
|
||||||
|
case *ast.BasicLit:
|
||||||
|
// If it's a literal, skip it.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if typeExpr := model.NewTypeExprFromAst("", key); typeExpr.Valid {
|
||||||
|
lineKeys[p.Fset.Position(callExpr.End()).Line] = typeExpr.TypeName("")
|
||||||
|
} else {
|
||||||
|
s.sourceProcessor.log.Error("Error: Failed to generate key for field validation. Make sure the field name is valid.", "file", p.PkgPath,
|
||||||
|
"line", p.Fset.Position(callExpr.End()).Line, "function", funcDecl.Name.String())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return lineKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if there is a *revel.Validation as an argument.
|
||||||
|
func (s *SourceInfoProcessor) getValidationParameter(funcDecl *ast.FuncDecl) *ast.Object {
|
||||||
|
for _, field := range funcDecl.Type.Params.List {
|
||||||
|
starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if selExpr.Sel.Name == "Validation" && s.sourceProcessor.importMap[xIdent.Name] == model.RevelImportPath {
|
||||||
|
return field.Names[0].Obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SourceInfoProcessor) getControllerFunc(funcDecl *ast.FuncDecl, p *packages.Package, localImportMap map[string]string) (method *model.MethodSpec, recvTypeName string) {
|
||||||
|
selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if selExpr.Sel.Name != "Result" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || s.sourceProcessor.importMap[pkgIdent.Name] != model.RevelImportPath {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
method = &model.MethodSpec{
|
||||||
|
Name: funcDecl.Name.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a description of the arguments to the method.
|
||||||
|
for _, field := range funcDecl.Type.Params.List {
|
||||||
|
for _, name := range field.Names {
|
||||||
|
var importPath string
|
||||||
|
typeExpr := model.NewTypeExprFromAst(p.Name, field.Type)
|
||||||
|
if !typeExpr.Valid {
|
||||||
|
utils.Logger.Warn("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, s.getFuncName(funcDecl))
|
||||||
|
return // We didn't understand one of the args. Ignore this action.
|
||||||
|
}
|
||||||
|
// Local object
|
||||||
|
if typeExpr.PkgName == p.Name {
|
||||||
|
importPath = p.PkgPath
|
||||||
|
} else if typeExpr.PkgName != "" {
|
||||||
|
var ok bool
|
||||||
|
if importPath, ok = localImportMap[typeExpr.PkgName]; !ok {
|
||||||
|
if importPath, ok = s.sourceProcessor.importMap[typeExpr.PkgName]; !ok {
|
||||||
|
utils.Logger.Error("Unable to find import", "importMap", s.sourceProcessor.importMap, "localimport", localImportMap)
|
||||||
|
utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
method.Args = append(method.Args, &model.MethodArg{
|
||||||
|
Name: name.Name,
|
||||||
|
TypeExpr: typeExpr,
|
||||||
|
ImportPath: importPath,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a description of the calls to Render from the method.
|
||||||
|
// Inspect every node (e.g. always return true).
|
||||||
|
method.RenderCalls = []*model.MethodCall{}
|
||||||
|
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||||
|
// Is it a function call?
|
||||||
|
callExpr, ok := node.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it calling (*Controller).Render?
|
||||||
|
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type of the receiver is not easily available, so just store every
|
||||||
|
// call to any method called Render.
|
||||||
|
if selExpr.Sel.Name != "Render" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this call's args to the renderArgs.
|
||||||
|
pos := p.Fset.Position(callExpr.Lparen)
|
||||||
|
methodCall := &model.MethodCall{
|
||||||
|
Line: pos.Line,
|
||||||
|
Names: []string{},
|
||||||
|
}
|
||||||
|
for _, arg := range callExpr.Args {
|
||||||
|
argIdent, ok := arg.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
methodCall.Names = append(methodCall.Names, argIdent.Name)
|
||||||
|
}
|
||||||
|
method.RenderCalls = append(method.RenderCalls, methodCall)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
recvType := funcDecl.Recv.List[0].Type
|
||||||
|
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||||
|
recvTypeName = recvStarType.X.(*ast.Ident).Name
|
||||||
|
} else {
|
||||||
|
recvTypeName = recvType.(*ast.Ident).Name
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages.Package, localImportMap map[string]string) (controllerSpec *model.TypeInfo) {
|
||||||
|
structType := spec.Type.(*ast.StructType)
|
||||||
|
|
||||||
|
// At this point we know it's a type declaration for a struct.
|
||||||
|
// Fill in the rest of the info by diving into the fields.
|
||||||
|
// Add it provisionally to the Controller list -- it's later filtered using field info.
|
||||||
|
controllerSpec = &model.TypeInfo{
|
||||||
|
StructName: spec.Name.Name,
|
||||||
|
ImportPath: p.PkgPath,
|
||||||
|
PackageName: p.Name,
|
||||||
|
}
|
||||||
|
log := s.sourceProcessor.log.New("file", p.Fset.Position(spec.Pos()).Filename, "position", p.Fset.Position(spec.Pos()).Line)
|
||||||
|
for _, field := range structType.Fields.List {
|
||||||
|
// If field.Names is set, it's not an embedded type.
|
||||||
|
if field.Names != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// A direct "sub-type" has an ast.Field as either:
|
||||||
|
// Ident { "AppController" }
|
||||||
|
// SelectorExpr { "rev", "Controller" }
|
||||||
|
// Additionally, that can be wrapped by StarExprs.
|
||||||
|
fieldType := field.Type
|
||||||
|
pkgName, typeName := func() (string, string) {
|
||||||
|
// Drill through any StarExprs.
|
||||||
|
for {
|
||||||
|
if starExpr, ok := fieldType.(*ast.StarExpr); ok {
|
||||||
|
fieldType = starExpr.X
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the embedded type is in the same package, it's an Ident.
|
||||||
|
if ident, ok := fieldType.(*ast.Ident); ok {
|
||||||
|
return "", ident.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
|
||||||
|
if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
|
||||||
|
return pkgIdent.Name, selectorExpr.Sel.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
|
}()
|
||||||
|
|
||||||
|
// If a typename wasn't found, skip it.
|
||||||
|
if typeName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the import path for this type.
|
||||||
|
// If it was referenced without a package name, use the current package import path.
|
||||||
|
// Else, look up the package's import path by name.
|
||||||
|
var importPath string
|
||||||
|
if pkgName == "" {
|
||||||
|
importPath = p.PkgPath
|
||||||
|
} else {
|
||||||
|
var ok bool
|
||||||
|
if importPath, ok = localImportMap[pkgName]; !ok {
|
||||||
|
log.Debug("Debug: Unusual, failed to find package locally ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin")
|
||||||
|
if importPath, ok = s.sourceProcessor.importMap[pkgName]; !ok {
|
||||||
|
log.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{
|
||||||
|
ImportPath: importPath,
|
||||||
|
StructName: typeName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
s.sourceProcessor.log.Info("Added controller spec", "name", controllerSpec.StructName, "package", controllerSpec.ImportPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SourceInfoProcessor) getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) {
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if genDecl.Tok != token.TYPE {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(genDecl.Specs) == 0 {
|
||||||
|
utils.Logger.Warn("Warn: Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
spec = genDecl.Specs[0].(*ast.TypeSpec)
|
||||||
|
_, found = spec.Type.(*ast.StructType)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SourceInfoProcessor) getFuncName(funcDecl *ast.FuncDecl) string {
|
||||||
|
prefix := ""
|
||||||
|
if funcDecl.Recv != nil {
|
||||||
|
recvType := funcDecl.Recv.List[0].Type
|
||||||
|
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||||
|
prefix = "(*" + recvStarType.X.(*ast.Ident).Name + ")"
|
||||||
|
} else {
|
||||||
|
prefix = recvType.(*ast.Ident).Name
|
||||||
|
}
|
||||||
|
prefix += "."
|
||||||
|
}
|
||||||
|
return prefix + funcDecl.Name.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SourceInfoProcessor) addImport(decl ast.Decl, p *packages.Package, localImportMap map[string]string, log logger.MultiLogger) (shouldContinue bool) {
|
||||||
|
shouldContinue = true
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if genDecl.Tok == token.IMPORT {
|
||||||
|
shouldContinue = false
|
||||||
|
for _, spec := range genDecl.Specs {
|
||||||
|
importSpec := spec.(*ast.ImportSpec)
|
||||||
|
// fmt.Printf("*** import specification %#v\n", importSpec)
|
||||||
|
var pkgAlias string
|
||||||
|
if importSpec.Name != nil {
|
||||||
|
pkgAlias = importSpec.Name.Name
|
||||||
|
if pkgAlias == "_" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\""
|
||||||
|
fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes
|
||||||
|
if pkgAlias == "" {
|
||||||
|
pkgAlias = fullPath
|
||||||
|
if index := strings.LastIndex(pkgAlias, "/"); index > 0 {
|
||||||
|
pkgAlias = pkgAlias[index+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
localImportMap[pkgAlias] = fullPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
253
parser2/source_processor.go
Normal file
253
parser2/source_processor.go
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
package parser2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/scanner"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/logger"
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
SourceProcessor struct {
|
||||||
|
revelContainer *model.RevelContainer
|
||||||
|
log logger.MultiLogger
|
||||||
|
packageList []*packages.Package
|
||||||
|
importMap map[string]string
|
||||||
|
packageMap map[string]string
|
||||||
|
sourceInfoProcessor *SourceInfoProcessor
|
||||||
|
sourceInfo *model.SourceInfo
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProcessSource(revelContainer *model.RevelContainer) (sourceInfo *model.SourceInfo, compileError error) {
|
||||||
|
utils.Logger.Info("ProcessSource")
|
||||||
|
processor := NewSourceProcessor(revelContainer)
|
||||||
|
compileError = processor.parse()
|
||||||
|
sourceInfo = processor.sourceInfo
|
||||||
|
if compileError == nil {
|
||||||
|
processor.log.Infof("From parsers : Structures:%d InitImports:%d ValidationKeys:%d %v", len(sourceInfo.StructSpecs), len(sourceInfo.InitImportPaths), len(sourceInfo.ValidationKeys), sourceInfo.PackageMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSourceProcessor(revelContainer *model.RevelContainer) *SourceProcessor {
|
||||||
|
s := &SourceProcessor{revelContainer: revelContainer, log: utils.Logger.New("parser", "SourceProcessor")}
|
||||||
|
s.sourceInfoProcessor = NewSourceInfoProcessor(s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SourceProcessor) parse() (compileError error) {
|
||||||
|
print("Parsing packages, (may require download if not cached)...")
|
||||||
|
if compileError = s.addPackages(); compileError != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
println(" Completed")
|
||||||
|
if compileError = s.addImportMap(); compileError != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if compileError = s.addSourceInfo(); compileError != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.sourceInfo.PackageMap = map[string]string{}
|
||||||
|
getImportFromMap := func(packagePath string) string {
|
||||||
|
for path := range s.packageMap {
|
||||||
|
if strings.Index(path, packagePath) == 0 {
|
||||||
|
fullPath := s.packageMap[path]
|
||||||
|
return fullPath[:(len(fullPath) - len(path) + len(packagePath))]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
s.sourceInfo.PackageMap[model.RevelImportPath] = getImportFromMap(model.RevelImportPath)
|
||||||
|
s.sourceInfo.PackageMap[s.revelContainer.ImportPath] = getImportFromMap(s.revelContainer.ImportPath)
|
||||||
|
for _, module := range s.revelContainer.ModulePathMap {
|
||||||
|
s.sourceInfo.PackageMap[module.ImportPath] = getImportFromMap(module.ImportPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using the packages.Load function load all the packages and type specifications (forces compile).
|
||||||
|
// this sets the SourceProcessor.packageList []*packages.Package.
|
||||||
|
func (s *SourceProcessor) addPackages() (err error) {
|
||||||
|
allPackages := []string{model.RevelImportPath + "/..."}
|
||||||
|
for _, module := range s.revelContainer.ModulePathMap {
|
||||||
|
allPackages = append(allPackages, module.ImportPath+"/...") // +"/app/controllers/...")
|
||||||
|
}
|
||||||
|
s.log.Info("Reading packages", "packageList", allPackages)
|
||||||
|
// allPackages = []string{s.revelContainer.ImportPath + "/..."} //+"/app/controllers/..."}
|
||||||
|
|
||||||
|
config := &packages.Config{
|
||||||
|
// ode: packages.NeedSyntax | packages.NeedCompiledGoFiles,
|
||||||
|
Mode: packages.NeedTypes | // For compile error
|
||||||
|
packages.NeedDeps | // To load dependent files
|
||||||
|
packages.NeedName | // Loads the full package name
|
||||||
|
packages.NeedSyntax, // To load ast tree (for end points)
|
||||||
|
// Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles |
|
||||||
|
// packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile |
|
||||||
|
// packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo |
|
||||||
|
// packages.NeedTypesSizes,
|
||||||
|
|
||||||
|
// Mode: packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedFiles |
|
||||||
|
// packages.NeedCompiledGoFiles | packages.NeedTypesSizes |
|
||||||
|
// packages.NeedSyntax | packages.NeedCompiledGoFiles ,
|
||||||
|
// Mode: packages.NeedSyntax | packages.NeedCompiledGoFiles | packages.NeedName | packages.NeedFiles |
|
||||||
|
// packages.LoadTypes | packages.NeedTypes | packages.NeedDeps, //, // |
|
||||||
|
// packages.NeedTypes, // packages.LoadTypes | packages.NeedSyntax | packages.NeedTypesInfo,
|
||||||
|
// packages.LoadSyntax | packages.NeedDeps,
|
||||||
|
Dir: s.revelContainer.AppPath,
|
||||||
|
}
|
||||||
|
config.Env = utils.ReducedEnv(false)
|
||||||
|
s.packageList, err = packages.Load(config, allPackages...)
|
||||||
|
s.log.Info("Loaded modules ", "len results", len(s.packageList), "error", err)
|
||||||
|
|
||||||
|
// Now process the files in the aap source folder s.revelContainer.ImportPath + "/...",
|
||||||
|
err = utils.Walk(s.revelContainer.BasePath, s.processPath)
|
||||||
|
s.log.Info("Loaded apps and modules ", "len results", len(s.packageList), "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// This callback is used to build the packages for the "app" package. This allows us to
|
||||||
|
// parse the source files without doing a full compile on them
|
||||||
|
// This callback only processes folders, so any files passed to this will return a nil.
|
||||||
|
func (s *SourceProcessor) processPath(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error("Error scanning app source:", "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore files and folders not marked tmp (since those are generated)
|
||||||
|
if !info.IsDir() || info.Name() == "tmp" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Real work for processing the folder
|
||||||
|
pkgImportPath := s.revelContainer.ImportPath
|
||||||
|
appPath := s.revelContainer.BasePath
|
||||||
|
if appPath != path {
|
||||||
|
pkgImportPath = s.revelContainer.ImportPath + "/" + filepath.ToSlash(path[len(appPath)+1:])
|
||||||
|
}
|
||||||
|
s.log.Info("Processing source package folder", "package", pkgImportPath, "path", path)
|
||||||
|
|
||||||
|
// Parse files within the path.
|
||||||
|
var pkgMap map[string]*ast.Package
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
pkgMap, err = parser.ParseDir(
|
||||||
|
fset,
|
||||||
|
path,
|
||||||
|
func(f os.FileInfo) bool {
|
||||||
|
return !f.IsDir() && !strings.HasPrefix(f.Name(), ".") && strings.HasSuffix(f.Name(), ".go")
|
||||||
|
},
|
||||||
|
0)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
var errList scanner.ErrorList
|
||||||
|
if errors.As(err, &errList) {
|
||||||
|
pos := errList[0].Pos
|
||||||
|
newError := &utils.SourceError{
|
||||||
|
SourceType: ".go source",
|
||||||
|
Title: "Go Compilation Error",
|
||||||
|
Path: pos.Filename,
|
||||||
|
Description: errList[0].Msg,
|
||||||
|
Line: pos.Line,
|
||||||
|
Column: pos.Column,
|
||||||
|
SourceLines: utils.MustReadLines(pos.Filename),
|
||||||
|
}
|
||||||
|
|
||||||
|
errorLink := s.revelContainer.Config.StringDefault("error.link", "")
|
||||||
|
if errorLink != "" {
|
||||||
|
newError.SetLink(errorLink)
|
||||||
|
}
|
||||||
|
return newError
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is exception, err already checked above. Here just a print
|
||||||
|
ast.Print(nil, err)
|
||||||
|
s.log.Fatal("Failed to parse dir", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip "main" packages.
|
||||||
|
delete(pkgMap, "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 pkgMap {
|
||||||
|
if len(i) > 6 {
|
||||||
|
if i[len(i)-5:] == "_test" {
|
||||||
|
delete(pkgMap, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no code in this directory, skip it.
|
||||||
|
if len(pkgMap) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// There should be only one package in this directory.
|
||||||
|
if len(pkgMap) > 1 {
|
||||||
|
for i := range pkgMap {
|
||||||
|
println("Found duplicate packages in single directory ", i)
|
||||||
|
}
|
||||||
|
utils.Logger.Fatal("Most unexpected! Multiple packages in a single directory:", "packages", pkgMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point there is only one package in the pkgs map,
|
||||||
|
p := &packages.Package{}
|
||||||
|
p.PkgPath = pkgImportPath
|
||||||
|
p.Fset = fset
|
||||||
|
for _, pkg := range pkgMap {
|
||||||
|
p.Name = pkg.Name
|
||||||
|
s.log.Info("Found package", "pkg.Name", pkg.Name, "p.Name", p.PkgPath)
|
||||||
|
for filename, astFile := range pkg.Files {
|
||||||
|
p.Syntax = append(p.Syntax, astFile)
|
||||||
|
p.GoFiles = append(p.GoFiles, filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.packageList = append(s.packageList, p)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is used to populate a map so that we can lookup controller embedded types in order to determine
|
||||||
|
// if a Struct inherits from from revel.Controller.
|
||||||
|
func (s *SourceProcessor) addImportMap() (err error) {
|
||||||
|
s.importMap = map[string]string{}
|
||||||
|
s.packageMap = map[string]string{}
|
||||||
|
for _, p := range s.packageList {
|
||||||
|
if len(p.Errors) > 0 {
|
||||||
|
// Generate a compile error
|
||||||
|
for _, e := range p.Errors {
|
||||||
|
s.log.Info("While reading packages encountered import error ignoring ", "PkgPath", p.PkgPath, "error", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, tree := range p.Syntax {
|
||||||
|
s.importMap[tree.Name.Name] = p.PkgPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SourceProcessor) addSourceInfo() (err error) {
|
||||||
|
for _, p := range s.packageList {
|
||||||
|
if sourceInfo := s.sourceInfoProcessor.processPackage(p); sourceInfo != nil {
|
||||||
|
if s.sourceInfo != nil {
|
||||||
|
s.sourceInfo.Merge(sourceInfo)
|
||||||
|
} else {
|
||||||
|
s.sourceInfo = sourceInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
12
proxy/proxy.go
Normal file
12
proxy/proxy.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// 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
|
||||||
268
revel/build.go
268
revel/build.go
@@ -11,85 +11,162 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/revel/cmd/harness"
|
"github.com/revel/cmd/harness"
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdBuild = &Command{
|
var cmdBuild = &Command{
|
||||||
UsageLine: "build [import path] [target path] [run mode]",
|
UsageLine: "revel build [-r [run mode]] [import path] [target path] ",
|
||||||
Short: "build a Revel application (e.g. for deployment)",
|
Short: "build a Revel application (e.g. for deployment)",
|
||||||
Long: `
|
Long: `
|
||||||
Build the Revel web application named by the given import path.
|
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.
|
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:
|
For example:
|
||||||
|
|
||||||
revel build github.com/revel/examples/chat /tmp/chat
|
revel build github.com/revel/examples/chat /tmp/chat
|
||||||
|
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
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 c.Build.TargetPath == "" {
|
||||||
|
c.Build.TargetPath = "target"
|
||||||
|
}
|
||||||
|
if len(args) == 0 && c.Build.ImportPath != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// If arguments were passed in then there must be two
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long)
|
fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Build.ImportPath = args[0]
|
||||||
|
c.Build.TargetPath = args[1]
|
||||||
|
if len(args) > 2 {
|
||||||
|
c.Build.Mode = args[2]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main entry point to build application from command line.
|
||||||
|
func buildApp(c *model.CommandConfig) (err error) {
|
||||||
|
appImportPath, destPath, mode := c.ImportPath, c.Build.TargetPath, DefaultRunMode
|
||||||
|
if len(c.Build.Mode) > 0 {
|
||||||
|
mode = c.Build.Mode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert target to absolute path
|
||||||
|
c.Build.TargetPath, _ = filepath.Abs(destPath)
|
||||||
|
c.Build.Mode = mode
|
||||||
|
c.Build.ImportPath = appImportPath
|
||||||
|
|
||||||
|
revelPaths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
appImportPath, destPath, mode := args[0], args[1], DefaultRunMode
|
if err = buildSafetyCheck(destPath); err != nil {
|
||||||
if len(args) >= 3 {
|
return
|
||||||
mode = args[2]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !revel.Initialized {
|
// Ensure the application can be built, this generates the main file
|
||||||
revel.Init(mode, appImportPath, "")
|
app, err := harness.Build(c, revelPaths)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
// Copy files
|
||||||
// First, verify that it is either already empty or looks like a previous
|
|
||||||
// build (to avoid clobbering anything)
|
|
||||||
if exists(destPath) && !empty(destPath) && !exists(filepath.Join(destPath, "run.sh")) {
|
|
||||||
errorf("Abort: %s exists and does not look like a build directory.", destPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
|
|
||||||
revel.ERROR.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(destPath, 0777); err != nil {
|
|
||||||
revel.ERROR.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
app, reverr := harness.Build()
|
|
||||||
panicOnError(reverr, "Failed to build")
|
|
||||||
|
|
||||||
// Included are:
|
// Included are:
|
||||||
// - run scripts
|
// - run scripts
|
||||||
// - binary
|
// - binary
|
||||||
// - revel
|
// - revel
|
||||||
// - app
|
// - app
|
||||||
|
|
||||||
|
packageFolders, err := buildCopyFiles(c, app, revelPaths)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = buildCopyModules(c, revelPaths, packageFolders, app)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = buildWriteScripts(c, app)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the files to the target.
|
||||||
|
func buildCopyFiles(c *model.CommandConfig, app *harness.App, revelPaths *model.RevelContainer) (packageFolders []string, err error) {
|
||||||
|
appImportPath, destPath := c.ImportPath, c.Build.TargetPath
|
||||||
|
|
||||||
// Revel and the app are in a directory structure mirroring import path
|
// Revel and the app are in a directory structure mirroring import path
|
||||||
srcPath := filepath.Join(destPath, "src")
|
srcPath := filepath.Join(destPath, "src")
|
||||||
destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath))
|
destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath))
|
||||||
tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(revel.RevelImportPath))
|
tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(model.RevelImportPath))
|
||||||
mustCopyFile(destBinaryPath, app.BinaryPath)
|
if err = utils.CopyFile(destBinaryPath, filepath.Join(revelPaths.BasePath, app.BinaryPath)); err != nil {
|
||||||
mustChmod(destBinaryPath, 0755)
|
return
|
||||||
_ = mustCopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revel.RevelPath, "conf"), nil)
|
}
|
||||||
_ = mustCopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revel.RevelPath, "templates"), nil)
|
utils.MustChmod(destBinaryPath, 0755)
|
||||||
_ = mustCopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revel.BasePath, nil)
|
|
||||||
|
|
||||||
|
// Copy the templates from the revel
|
||||||
|
if err = utils.CopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revelPaths.RevelPath, "conf"), nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = utils.CopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revelPaths.RevelPath, "templates"), nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the folders to be packaged
|
||||||
|
packageFolders = strings.Split(revelPaths.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 {
|
||||||
|
err = utils.CopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revelPaths.BasePath, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, folder := range packageFolders {
|
||||||
|
err = utils.CopyDir(
|
||||||
|
filepath.Join(srcPath, filepath.FromSlash(appImportPath), folder),
|
||||||
|
filepath.Join(revelPaths.BasePath, folder),
|
||||||
|
nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on the section copy over the build modules.
|
||||||
|
func buildCopyModules(c *model.CommandConfig, revelPaths *model.RevelContainer, packageFolders []string, app *harness.App) (err error) {
|
||||||
|
destPath := filepath.Join(c.Build.TargetPath, "src")
|
||||||
// Find all the modules used and copy them over.
|
// Find all the modules used and copy them over.
|
||||||
config := revel.Config.Raw()
|
config := revelPaths.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
|
||||||
|
moduleImportList := []string{}
|
||||||
for _, section := range config.Sections() {
|
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)
|
options, _ := config.SectionOptions(section)
|
||||||
for _, key := range options {
|
for _, key := range options {
|
||||||
if !strings.HasPrefix(key, "module.") {
|
if !strings.HasPrefix(key, "module.") {
|
||||||
@@ -99,32 +176,91 @@ func buildApp(args []string) {
|
|||||||
if moduleImportPath == "" {
|
if moduleImportPath == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
modulePath, err := revel.ResolveImportPath(moduleImportPath)
|
moduleImportList = append(moduleImportList, moduleImportPath)
|
||||||
if err != nil {
|
|
||||||
revel.ERROR.Fatalln("Failed to load module %s: %s", key[len("module."):], err)
|
|
||||||
}
|
|
||||||
modulePaths[moduleImportPath] = modulePath
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for importPath, fsPath := range modulePaths {
|
|
||||||
_ = mustCopyDir(filepath.Join(srcPath, importPath), fsPath, nil)
|
// Copy the the paths for each of the modules
|
||||||
|
for _, importPath := range moduleImportList {
|
||||||
|
fsPath := app.PackagePathMap[importPath]
|
||||||
|
utils.Logger.Info("Copy files ", "to", filepath.Join(destPath, importPath), "from", fsPath)
|
||||||
|
if c.Build.CopySource {
|
||||||
|
err = utils.CopyDir(filepath.Join(destPath, importPath), fsPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, folder := range packageFolders {
|
||||||
|
err = utils.CopyDir(
|
||||||
|
filepath.Join(destPath, importPath, folder),
|
||||||
|
filepath.Join(fsPath, folder),
|
||||||
|
nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tmplData, runShPath := map[string]interface{}{
|
return
|
||||||
"BinName": filepath.Base(app.BinaryPath),
|
|
||||||
"ImportPath": appImportPath,
|
|
||||||
"Mode": mode,
|
|
||||||
}, filepath.Join(destPath, "run.sh")
|
|
||||||
|
|
||||||
mustRenderTemplate(
|
|
||||||
runShPath,
|
|
||||||
filepath.Join(revel.RevelPath, "..", "cmd", "revel", "package_run.sh.template"),
|
|
||||||
tmplData)
|
|
||||||
|
|
||||||
mustChmod(runShPath, 0755)
|
|
||||||
|
|
||||||
mustRenderTemplate(
|
|
||||||
filepath.Join(destPath, "run.bat"),
|
|
||||||
filepath.Join(revel.RevelPath, "..", "cmd", "revel", "package_run.bat.template"),
|
|
||||||
tmplData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write the run scripts for the build.
|
||||||
|
func buildWriteScripts(c *model.CommandConfig, app *harness.App) (err error) {
|
||||||
|
tmplData := map[string]interface{}{
|
||||||
|
"BinName": filepath.Base(app.BinaryPath),
|
||||||
|
"ImportPath": c.Build.ImportPath,
|
||||||
|
"Mode": c.Build.Mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = utils.GenerateTemplate(
|
||||||
|
filepath.Join(c.Build.TargetPath, "run.sh"),
|
||||||
|
PACKAGE_RUN_SH,
|
||||||
|
tmplData,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.MustChmod(filepath.Join(c.Build.TargetPath, "run.sh"), 0755)
|
||||||
|
err = utils.GenerateTemplate(
|
||||||
|
filepath.Join(c.Build.TargetPath, "run.bat"),
|
||||||
|
PACKAGE_RUN_BAT,
|
||||||
|
tmplData,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Your application has been built in:", c.Build.TargetPath)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks to see if the target folder exists and can be created.
|
||||||
|
func buildSafetyCheck(destPath string) error {
|
||||||
|
// First, verify that it is either already empty or looks like a previous
|
||||||
|
// build (to avoid clobbering anything)
|
||||||
|
if utils.Exists(destPath) && !utils.Empty(destPath) && !utils.Exists(filepath.Join(destPath, "run.sh")) {
|
||||||
|
return utils.NewBuildError("Abort: %s exists and does not look like a build directory.", "path", destPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
|
||||||
|
return utils.NewBuildIfError(err, "Remove all error", "path", destPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(destPath, 0777); err != nil {
|
||||||
|
return utils.NewBuildIfError(err, "MkDir all error", "path", destPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const PACKAGE_RUN_SH = `#!/bin/sh
|
||||||
|
|
||||||
|
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}}
|
||||||
|
`
|
||||||
|
|||||||
50
revel/build_test.go
Normal file
50
revel/build_test.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package main_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
main "github.com/revel/cmd/revel"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// test the commands.
|
||||||
|
func TestBuild(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
gopath := setup("revel-test-build", a)
|
||||||
|
|
||||||
|
t.Run("Build", func(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
c := newApp("build-test", model.NEW, nil, a)
|
||||||
|
a.Nil(main.Commands[model.NEW].RunWith(c), "failed to run new")
|
||||||
|
c.Index = model.BUILD
|
||||||
|
c.Build.TargetPath = filepath.Join(gopath, "build-test", "target")
|
||||||
|
c.Build.ImportPath = c.ImportPath
|
||||||
|
a.Nil(main.Commands[model.BUILD].RunWith(c), "Failed to run build-test")
|
||||||
|
a.True(utils.Exists(filepath.Join(gopath, "build-test", "target")))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Build-withFlags", func(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
c := newApp("build-test-WithFlags", model.NEW, nil, a)
|
||||||
|
c.BuildFlags = []string{
|
||||||
|
"build-test-WithFlags/app.AppVersion=SomeValue",
|
||||||
|
"build-test-WithFlags/app.SomeOtherValue=SomeValue",
|
||||||
|
}
|
||||||
|
a.Nil(main.Commands[model.NEW].RunWith(c), "failed to run new")
|
||||||
|
c.Index = model.BUILD
|
||||||
|
c.Build.TargetPath = filepath.Join(gopath, "build-test", "target")
|
||||||
|
c.Build.ImportPath = c.ImportPath
|
||||||
|
a.Nil(main.Commands[model.BUILD].RunWith(c), "Failed to run build-test-withFlags")
|
||||||
|
a.True(utils.Exists(filepath.Join(gopath, "build-test", "target")))
|
||||||
|
})
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
if err := os.RemoveAll(gopath); err != nil {
|
||||||
|
a.Fail("Failed to remove test path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,9 +6,11 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdClean = &Command{
|
var cmdClean = &Command{
|
||||||
@@ -22,36 +24,44 @@ For example:
|
|||||||
revel clean github.com/revel/examples/chat
|
revel clean github.com/revel/examples/chat
|
||||||
|
|
||||||
It removes the app/tmp and app/routes directory.
|
It removes the app/tmp and app/routes directory.
|
||||||
|
|
||||||
|
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
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 && c.Clean.ImportPath != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
fmt.Fprintf(os.Stderr, cmdClean.Long)
|
fmt.Fprintf(os.Stderr, cmdClean.Long)
|
||||||
return
|
return false
|
||||||
}
|
|
||||||
|
|
||||||
appPkg, err := build.Import(args[0], "", build.FindOnly)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Abort: Failed to find import path:", err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
c.Clean.ImportPath = args[0]
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean the source directory of generated files.
|
||||||
|
func cleanApp(c *model.CommandConfig) (err error) {
|
||||||
purgeDirs := []string{
|
purgeDirs := []string{
|
||||||
filepath.Join(appPkg.Dir, "app", "tmp"),
|
filepath.Join(c.AppPath, "app", "tmp"),
|
||||||
filepath.Join(appPkg.Dir, "app", "routes"),
|
filepath.Join(c.AppPath, "app", "routes"),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dir := range purgeDirs {
|
for _, dir := range purgeDirs {
|
||||||
fmt.Println("Removing:", dir)
|
fmt.Println("Removing:", dir)
|
||||||
err = os.RemoveAll(dir)
|
err = os.RemoveAll(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Abort:", err)
|
utils.Logger.Error("Failed to clean dir", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
40
revel/clean_test.go
Normal file
40
revel/clean_test.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package main_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
main "github.com/revel/cmd/revel"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// test the commands.
|
||||||
|
func TestClean(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
gopath := setup("revel-test-clean", a)
|
||||||
|
|
||||||
|
t.Run("Clean", func(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
c := newApp("clean-test", model.NEW, nil, a)
|
||||||
|
|
||||||
|
a.Nil(main.Commands[model.NEW].RunWith(c), "failed to run new")
|
||||||
|
|
||||||
|
c.Index = model.TEST
|
||||||
|
a.Nil(main.Commands[model.TEST].RunWith(c), "failed to run test")
|
||||||
|
|
||||||
|
a.True(utils.Exists(filepath.Join(gopath, "clean-test", "app", "tmp", "main.go")),
|
||||||
|
"Missing main from path "+filepath.Join(gopath, "clean-test", "app", "tmp", "main.go"))
|
||||||
|
c.Clean.ImportPath = c.ImportPath
|
||||||
|
a.Nil(main.Commands[model.CLEAN].RunWith(c), "Failed to run clean-test")
|
||||||
|
a.False(utils.Exists(filepath.Join(gopath, "clean-test", "app", "tmp", "main.go")),
|
||||||
|
"Did not remove main from path "+filepath.Join(gopath, "clean-test", "app", "tmp", "main.go"))
|
||||||
|
})
|
||||||
|
if !t.Failed() {
|
||||||
|
if err := os.RemoveAll(gopath); err != nil {
|
||||||
|
a.Fail("Failed to remove test path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
revel/command_test.go
Normal file
95
revel/command_test.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package main_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/logger"
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test that the event handler can be attached and it dispatches the event received.
|
||||||
|
func setup(suffix string, a *assert.Assertions) string {
|
||||||
|
temp := os.TempDir()
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
utils.InitLogger(wd, logger.LvlInfo)
|
||||||
|
gopath := filepath.Join(temp, "revel-test", suffix)
|
||||||
|
if utils.Exists(gopath) {
|
||||||
|
utils.Logger.Info("Removing test path", "path", gopath)
|
||||||
|
if err := os.RemoveAll(gopath); err != nil {
|
||||||
|
a.Fail("Failed to remove test path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := os.MkdirAll(gopath, os.ModePerm)
|
||||||
|
a.Nil(err, "Failed to create gopath "+gopath)
|
||||||
|
|
||||||
|
// So this is the issue, on the mac when folders are created in a temp folder they are returned like
|
||||||
|
// /var/folders/nz/vv4_9tw56nv9k3tkvyszvwg80000gn/T/revel-test/revel-test-build
|
||||||
|
// But if you change into that directory and read the current folder it is
|
||||||
|
// /private/var/folders/nz/vv4_9tw56nv9k3tkvyszvwg80000gn/T/revel-test/revel-test-build
|
||||||
|
// So to make this work on darwin this code was added
|
||||||
|
if err := os.Chdir(gopath); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newwd, _ := os.Getwd()
|
||||||
|
gopath = newwd
|
||||||
|
defaultBuild := build.Default
|
||||||
|
defaultBuild.GOPATH = gopath
|
||||||
|
build.Default = defaultBuild
|
||||||
|
utils.Logger.Info("Setup stats", "original wd", wd, "new wd", newwd, "gopath", gopath, "gopath exists", utils.DirExists(gopath), "wd exists", utils.DirExists(newwd))
|
||||||
|
|
||||||
|
return gopath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new app for the name.
|
||||||
|
func newApp(name string, command model.COMMAND, precall func(c *model.CommandConfig), a *assert.Assertions) *model.CommandConfig {
|
||||||
|
c := &model.CommandConfig{Vendored: true}
|
||||||
|
switch command {
|
||||||
|
case model.NEW:
|
||||||
|
c.New.ImportPath = name
|
||||||
|
c.New.Callback = func() error {
|
||||||
|
// On callback we will invoke a specific branch of revel so that it works
|
||||||
|
|
||||||
|
goModCmd := exec.Command("go", "mod", "tidy")
|
||||||
|
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
|
||||||
|
getOutput, _ := goModCmd.CombinedOutput()
|
||||||
|
fmt.Printf("Calling go mod tidy %s", string(getOutput))
|
||||||
|
|
||||||
|
goModCmd = exec.Command("go", "mod", "edit", "-replace=github.com/revel/revel=github.com/revel/revel@develop")
|
||||||
|
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
|
||||||
|
getOutput, _ = goModCmd.CombinedOutput()
|
||||||
|
fmt.Printf("Calling go mod edit %v", string(getOutput))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case model.BUILD:
|
||||||
|
c.Build.ImportPath = name
|
||||||
|
case model.TEST:
|
||||||
|
c.Test.ImportPath = name
|
||||||
|
case model.PACKAGE:
|
||||||
|
c.Package.ImportPath = name
|
||||||
|
case model.VERSION:
|
||||||
|
c.Version.ImportPath = name
|
||||||
|
case model.CLEAN:
|
||||||
|
c.Clean.ImportPath = name
|
||||||
|
default:
|
||||||
|
a.Fail("Unknown command ", command)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Index = command
|
||||||
|
if precall != nil {
|
||||||
|
precall(c)
|
||||||
|
}
|
||||||
|
if c.UpdateImportPath() != nil {
|
||||||
|
a.Fail("Unable to update import path")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.InitPackageResolver()
|
||||||
|
return c
|
||||||
|
}
|
||||||
356
revel/new.go
356
revel/new.go
@@ -5,21 +5,23 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const ErrNoSkeleton Error = "failed to find skeleton in filepath"
|
||||||
|
|
||||||
var cmdNew = &Command{
|
var cmdNew = &Command{
|
||||||
UsageLine: "new [path] [skeleton]",
|
UsageLine: "new -i [path] -s [skeleton] -p [package name]",
|
||||||
Short: "create a skeleton Revel application",
|
Short: "create a skeleton Revel application",
|
||||||
Long: `
|
Long: `
|
||||||
New creates a few files to get a new Revel application running quickly.
|
New creates a few files to get a new Revel application running quickly.
|
||||||
@@ -31,193 +33,257 @@ Skeleton is an optional argument, provided as an import path
|
|||||||
|
|
||||||
For example:
|
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() {
|
func init() {
|
||||||
cmdNew.Run = newApp
|
cmdNew.RunWith = newApp
|
||||||
|
cmdNew.UpdateConfig = updateNewConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// 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(c.New.Package) > 0 {
|
||||||
|
c.New.NotVendored = false
|
||||||
|
}
|
||||||
|
c.Vendored = !c.New.NotVendored
|
||||||
|
|
||||||
// go related paths
|
|
||||||
gopath string
|
|
||||||
gocmd string
|
|
||||||
srcRoot string
|
|
||||||
|
|
||||||
// revel related paths
|
|
||||||
revelPkg *build.Package
|
|
||||||
revelCmdPkg *build.Package
|
|
||||||
appPath string
|
|
||||||
appName string
|
|
||||||
basePath string
|
|
||||||
importPath string
|
|
||||||
skeletonPath string
|
|
||||||
)
|
|
||||||
|
|
||||||
func newApp(args []string) {
|
|
||||||
// check for proper args by count
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
errorf("No import path given.\nRun 'revel help new' for usage.\n")
|
if len(c.New.ImportPath) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, cmdNew.Long)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
if len(args) > 2 {
|
c.New.ImportPath = args[0]
|
||||||
errorf("Too many arguments provided.\nRun 'revel help new' for usage.\n")
|
if len(args) > 1 {
|
||||||
|
c.New.SkeletonPath = args[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
revel.ERROR.SetFlags(log.LstdFlags)
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// checking and setting go paths
|
// Call to create a new application.
|
||||||
initGoPaths()
|
func newApp(c *model.CommandConfig) (err error) {
|
||||||
|
// Check for an existing folder so we don't clobber it
|
||||||
// checking and setting application
|
_, err = build.Import(c.ImportPath, "", build.FindOnly)
|
||||||
setApplicationPath(args)
|
if err == nil || !utils.Empty(c.AppPath) {
|
||||||
|
return utils.NewBuildError("Abort: Import path already exists.", "path", c.ImportPath, "apppath", c.AppPath)
|
||||||
|
}
|
||||||
|
|
||||||
// checking and setting skeleton
|
// checking and setting skeleton
|
||||||
setSkeletonPath(args)
|
if err = setSkeletonPath(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create application path
|
||||||
|
if err := os.MkdirAll(c.AppPath, os.ModePerm); err != nil {
|
||||||
|
return utils.NewBuildError("Abort: Unable to create app path.", "path", c.AppPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checking and setting application
|
||||||
|
if err = setApplicationPath(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// This kicked off the download of the revel app, not needed for vendor
|
||||||
|
if !c.Vendored {
|
||||||
|
// At this point the versions can be set
|
||||||
|
if err = c.SetVersions(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// copy files to new app directory
|
// copy files to new app directory
|
||||||
copyNewAppFiles()
|
if err = copyNewAppFiles(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the vendor tool if needed
|
||||||
|
if c.Vendored {
|
||||||
|
if err = createModVendor(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// goodbye world
|
// goodbye world
|
||||||
fmt.Fprintln(os.Stdout, "Your application is ready:\n ", appPath)
|
fmt.Fprintln(os.Stdout, "Your application has been created in:\n ", c.AppPath)
|
||||||
fmt.Fprintln(os.Stdout, "\nYou can run it with:\n revel run", importPath)
|
// Check to see if it should be run right off
|
||||||
|
if c.New.Run {
|
||||||
|
// Need to prep the run command
|
||||||
|
c.Run.ImportPath = c.ImportPath
|
||||||
|
updateRunConfig(c, nil)
|
||||||
|
|
||||||
|
if err = c.UpdateImportPath(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = runApp(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(os.Stdout, "\nYou can run it with:\n revel run -a", c.ImportPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createModVendor(c *model.CommandConfig) (err error) {
|
||||||
|
utils.Logger.Info("Creating a new mod app")
|
||||||
|
goModCmd := exec.Command("go", "mod", "init", filepath.Join(c.New.Package, c.AppName))
|
||||||
|
|
||||||
|
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
|
||||||
|
|
||||||
|
utils.Logger.Info("Exec:", "args", goModCmd.Args, "env", goModCmd.Env, "workingdir", goModCmd.Dir)
|
||||||
|
|
||||||
|
getOutput, err := goModCmd.CombinedOutput()
|
||||||
|
if c.New.Callback != nil {
|
||||||
|
err = c.New.Callback()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return utils.NewBuildIfError(err, string(getOutput))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to generate a new secret key.
|
||||||
const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
|
||||||
|
// Generate a secret key.
|
||||||
func generateSecret() string {
|
func generateSecret() string {
|
||||||
chars := make([]byte, 64)
|
chars := make([]byte, 64)
|
||||||
for i := 0; i < 64; i++ {
|
for i := 0; i < 64; i++ {
|
||||||
chars[i] = alphaNumeric[rand.Intn(len(alphaNumeric))]
|
chars[i] = alphaNumeric[rand.Intn(len(alphaNumeric))]
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(chars)
|
return string(chars)
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookup and set Go related variables
|
// Sets the application path.
|
||||||
func initGoPaths() {
|
func setApplicationPath(c *model.CommandConfig) (err error) {
|
||||||
// lookup go path
|
|
||||||
gopath = build.Default.GOPATH
|
|
||||||
if gopath == "" {
|
|
||||||
errorf("Abort: GOPATH environment variable is not set. " +
|
|
||||||
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for go executable
|
|
||||||
var err error
|
|
||||||
gocmd, err = exec.LookPath("go")
|
|
||||||
if err != nil {
|
|
||||||
errorf("Go executable not found in PATH.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// revel/revel#1004 choose go path relative to current working directory
|
|
||||||
workingDir, _ := os.Getwd()
|
|
||||||
goPathList := filepath.SplitList(gopath)
|
|
||||||
for _, path := range goPathList {
|
|
||||||
if strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
|
|
||||||
srcRoot = path
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
path, _ = filepath.EvalSymlinks(path)
|
|
||||||
if len(path) > 0 && strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
|
|
||||||
srcRoot = path
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(srcRoot) == 0 {
|
|
||||||
revel.ERROR.Fatalln("Abort: could not create a Revel application outside of GOPATH.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// set go src path
|
|
||||||
srcRoot = filepath.Join(srcRoot, "src")
|
|
||||||
}
|
|
||||||
|
|
||||||
func setApplicationPath(args []string) {
|
|
||||||
var err error
|
|
||||||
importPath = args[0]
|
|
||||||
|
|
||||||
// revel/revel#1014 validate relative path, we cannot use built-in functions
|
// revel/revel#1014 validate relative path, we cannot use built-in functions
|
||||||
// since Go import path is valid relative path too.
|
// since Go import path is valid relative path too.
|
||||||
// so check basic part of the path, which is "."
|
// so check basic part of the path, which is "."
|
||||||
if filepath.IsAbs(importPath) || strings.HasPrefix(importPath, ".") {
|
|
||||||
errorf("Abort: '%s' looks like a directory. Please provide a Go import path instead.",
|
|
||||||
importPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = build.Import(importPath, "", build.FindOnly)
|
// If we are running a vendored version of Revel we do not need to check for it.
|
||||||
if err == nil {
|
if !c.Vendored {
|
||||||
errorf("Abort: Import path %s already exists.\n", 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)
|
||||||
revelPkg, err = build.Import(revel.RevelImportPath, "", build.FindOnly)
|
}
|
||||||
if err != nil {
|
_, err = build.Import(model.RevelImportPath, "", build.FindOnly)
|
||||||
errorf("Abort: Could not find Revel source code: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
appPath = filepath.Join(srcRoot, filepath.FromSlash(importPath))
|
|
||||||
appName = filepath.Base(appPath)
|
|
||||||
basePath = filepath.ToSlash(filepath.Dir(importPath))
|
|
||||||
|
|
||||||
if basePath == "." {
|
|
||||||
// we need to remove the a single '.' when
|
|
||||||
// the app is in the $GOROOT/src directory
|
|
||||||
basePath = ""
|
|
||||||
} else {
|
|
||||||
// we need to append a '/' when the app is
|
|
||||||
// is a subdirectory such as $GOROOT/src/path/to/revelapp
|
|
||||||
basePath += "/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setSkeletonPath(args []string) {
|
|
||||||
var err error
|
|
||||||
if len(args) == 2 { // user specified
|
|
||||||
skeletonName := args[1]
|
|
||||||
_, err = build.Import(skeletonName, "", build.FindOnly)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Execute "go get <pkg>"
|
// Go get the revel project
|
||||||
getCmd := exec.Command(gocmd, "get", "-d", skeletonName)
|
err = c.PackageResolver(model.RevelImportPath)
|
||||||
fmt.Println("Exec:", getCmd.Args)
|
if err != nil {
|
||||||
getOutput, err := getCmd.CombinedOutput()
|
return utils.NewBuildIfError(err, "Failed to fetch revel "+model.RevelImportPath)
|
||||||
|
|
||||||
// check getOutput for no buildible string
|
|
||||||
bpos := bytes.Index(getOutput, []byte("no buildable Go source files in"))
|
|
||||||
if err != nil && bpos == -1 {
|
|
||||||
errorf("Abort: Could not find or 'go get' Skeleton source code: %s\n%s\n", getOutput, skeletonName)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// use the
|
|
||||||
skeletonPath = filepath.Join(srcRoot, skeletonName)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// use the revel default
|
|
||||||
revelCmdPkg, err = build.Import(RevelCmdImportPath, "", build.FindOnly)
|
|
||||||
if err != nil {
|
|
||||||
errorf("Abort: Could not find Revel Cmd source code: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
skeletonPath = filepath.Join(revelCmdPkg.Dir, "revel", "skeleton")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.AppName = filepath.Base(c.AppPath)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyNewAppFiles() {
|
// Set the skeleton path.
|
||||||
var err error
|
func setSkeletonPath(c *model.CommandConfig) (err error) {
|
||||||
err = os.MkdirAll(appPath, 0777)
|
if len(c.New.SkeletonPath) == 0 {
|
||||||
panicOnError(err, "Failed to create directory "+appPath)
|
c.New.SkeletonPath = "https://" + RevelSkeletonsImportPath + ":basic/bootstrap4"
|
||||||
|
}
|
||||||
|
|
||||||
_ = mustCopyDir(appPath, skeletonPath, map[string]interface{}{
|
// First check to see the protocol of the string
|
||||||
|
sp, err := url.Parse(c.New.SkeletonPath)
|
||||||
|
if err == nil {
|
||||||
|
utils.Logger.Info("Detected skeleton path", "path", sp)
|
||||||
|
|
||||||
|
switch strings.ToLower(sp.Scheme) {
|
||||||
|
// TODO Add support for ftp, sftp, scp ??
|
||||||
|
case "":
|
||||||
|
sp.Scheme = "file"
|
||||||
|
fallthrough
|
||||||
|
case "file":
|
||||||
|
fullpath := sp.String()[7:]
|
||||||
|
if !filepath.IsAbs(fullpath) {
|
||||||
|
fullpath, err = filepath.Abs(fullpath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.New.SkeletonPath = fullpath
|
||||||
|
utils.Logger.Info("Set skeleton path to ", fullpath)
|
||||||
|
if !utils.DirExists(fullpath) {
|
||||||
|
return fmt.Errorf("%w %s %s", ErrNoSkeleton, fullpath, sp.String())
|
||||||
|
}
|
||||||
|
case "git":
|
||||||
|
fallthrough
|
||||||
|
case "http":
|
||||||
|
fallthrough
|
||||||
|
case "https":
|
||||||
|
if err := newLoadFromGit(c, sp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
utils.Logger.Fatal("Unsupported skeleton schema ", "path", c.New.SkeletonPath)
|
||||||
|
}
|
||||||
|
// TODO check to see if the path needs to be extracted
|
||||||
|
} else {
|
||||||
|
utils.Logger.Fatal("Invalid skeleton path format", "path", c.New.SkeletonPath)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load skeleton from git.
|
||||||
|
func newLoadFromGit(c *model.CommandConfig, sp *url.URL) (err error) {
|
||||||
|
// This method indicates we need to fetch from a repository using git
|
||||||
|
// Execute "git clone get <pkg>"
|
||||||
|
targetPath := filepath.Join(os.TempDir(), "revel", "skeleton")
|
||||||
|
os.RemoveAll(targetPath)
|
||||||
|
pathpart := strings.Split(sp.Path, ":")
|
||||||
|
getCmd := exec.Command("git", "clone", sp.Scheme+"://"+sp.Host+pathpart[0], targetPath)
|
||||||
|
utils.Logger.Info("Exec:", "args", getCmd.Args)
|
||||||
|
getOutput, err := getCmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Abort: could not clone the Skeleton source code: ", "output", string(getOutput), "path", c.New.SkeletonPath)
|
||||||
|
}
|
||||||
|
outputPath := targetPath
|
||||||
|
if len(pathpart) > 1 {
|
||||||
|
outputPath = filepath.Join(targetPath, filepath.Join(strings.Split(pathpart[1], string('/'))...))
|
||||||
|
}
|
||||||
|
outputPath, _ = filepath.Abs(outputPath)
|
||||||
|
if !strings.HasPrefix(outputPath, targetPath) {
|
||||||
|
utils.Logger.Fatal("Unusual target path outside root path", "target", outputPath, "root", targetPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.New.SkeletonPath = outputPath
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyNewAppFiles(c *model.CommandConfig) (err error) {
|
||||||
|
err = os.MkdirAll(c.AppPath, 0777)
|
||||||
|
if err != nil {
|
||||||
|
return utils.NewBuildIfError(err, "MKDIR failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = utils.CopyDir(c.AppPath, c.New.SkeletonPath, map[string]interface{}{
|
||||||
// app.conf
|
// app.conf
|
||||||
"AppName": appName,
|
"AppName": c.AppName,
|
||||||
"BasePath": basePath,
|
"BasePath": c.AppPath,
|
||||||
"Secret": generateSecret(),
|
"Secret": generateSecret(),
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("err %v", err)
|
||||||
|
return utils.NewBuildIfError(err, "Copy Dir failed")
|
||||||
|
}
|
||||||
|
|
||||||
// Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore.
|
// Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore.
|
||||||
gitignore := ".gitignore"
|
gitignore := ".gitignore"
|
||||||
mustCopyFile(filepath.Join(appPath, gitignore), filepath.Join(skeletonPath, gitignore))
|
return utils.CopyFile(filepath.Join(c.AppPath, gitignore), filepath.Join(c.New.SkeletonPath, gitignore))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
55
revel/new_test.go
Normal file
55
revel/new_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package main_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
main "github.com/revel/cmd/revel"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// test the commands.
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
gopath := setup("revel-test-new", a)
|
||||||
|
|
||||||
|
t.Run("New", func(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
c := newApp("new-test", model.NEW, nil, a)
|
||||||
|
a.Nil(main.Commands[model.NEW].RunWith(c), "New failed")
|
||||||
|
})
|
||||||
|
t.Run("New-NotVendoredmode", func(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
c := newApp("new-notvendored", model.NEW, nil, a)
|
||||||
|
c.New.NotVendored = true
|
||||||
|
a.Nil(main.Commands[model.NEW].RunWith(c), "New failed")
|
||||||
|
})
|
||||||
|
t.Run("Path", func(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
c := newApp("new/test/a", model.NEW, nil, a)
|
||||||
|
a.Nil(main.Commands[model.NEW].RunWith(c), "New path failed")
|
||||||
|
})
|
||||||
|
t.Run("Path-Duplicate", func(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
c := newApp("new/test/b", model.NEW, nil, a)
|
||||||
|
a.Nil(main.Commands[model.NEW].RunWith(c), "New path failed")
|
||||||
|
c = newApp("new/test/b", model.NEW, nil, a)
|
||||||
|
a.NotNil(main.Commands[model.NEW].RunWith(c), "Duplicate path Did Not failed")
|
||||||
|
})
|
||||||
|
t.Run("Skeleton-Git", func(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
c := newApp("new/test/c/1", model.NEW, nil, a)
|
||||||
|
c.New.SkeletonPath = "git://github.com/revel/skeletons:basicnsadnsak"
|
||||||
|
a.NotNil(main.Commands[model.NEW].RunWith(c), "Expected Failed to run with new")
|
||||||
|
// We need to pick a different path
|
||||||
|
c = newApp("new/test/c/2", model.NEW, nil, a)
|
||||||
|
c.New.SkeletonPath = "git://github.com/revel/skeletons:basic/bootstrap4"
|
||||||
|
a.Nil(main.Commands[model.NEW].RunWith(c), "Failed to run with new skeleton git")
|
||||||
|
})
|
||||||
|
if !t.Failed() {
|
||||||
|
if err := os.RemoveAll(gopath); err != nil {
|
||||||
|
a.Fail("Failed to remove test path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,11 +10,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdPackage = &Command{
|
var cmdPackage = &Command{
|
||||||
UsageLine: "package [import path] [run mode]",
|
UsageLine: "package [-r [run mode]] [application] ",
|
||||||
Short: "package a Revel application (e.g. for deployment)",
|
Short: "package a Revel application (e.g. for deployment)",
|
||||||
Long: `
|
Long: `
|
||||||
Package the Revel web application named by the given import path.
|
Package the Revel web application named by the given import path.
|
||||||
@@ -32,38 +33,66 @@ For example:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
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.
|
||||||
if len(args) == 0 {
|
func updatePackageConfig(c *model.CommandConfig, args []string) bool {
|
||||||
fmt.Fprint(os.Stderr, cmdPackage.Long)
|
c.Index = model.PACKAGE
|
||||||
|
if len(args) == 0 && c.Package.ImportPath != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
c.Package.ImportPath = args[0]
|
||||||
|
if len(args) > 1 {
|
||||||
|
c.Package.Mode = args[1]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called to package the app.
|
||||||
|
func packageApp(c *model.CommandConfig) (err error) {
|
||||||
|
// Determine the run mode.
|
||||||
|
mode := c.Package.Mode
|
||||||
|
|
||||||
|
appImportPath := c.ImportPath
|
||||||
|
revelPaths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the run mode.
|
|
||||||
mode := DefaultRunMode
|
|
||||||
if len(args) >= 2 {
|
|
||||||
mode = args[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
appImportPath := args[0]
|
|
||||||
revel.Init(mode, appImportPath, "")
|
|
||||||
|
|
||||||
// Remove the archive if it already exists.
|
// Remove the archive if it already exists.
|
||||||
destFile := filepath.Base(revel.BasePath) + ".tar.gz"
|
destFile := filepath.Join(c.AppPath, filepath.Base(revelPaths.BasePath)+".tar.gz")
|
||||||
|
if c.Package.TargetPath != "" {
|
||||||
|
if filepath.IsAbs(c.Package.TargetPath) {
|
||||||
|
destFile = c.Package.TargetPath
|
||||||
|
} else {
|
||||||
|
destFile = filepath.Join(c.AppPath, c.Package.TargetPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := os.Remove(destFile); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(destFile); err != nil && !os.IsNotExist(err) {
|
||||||
revel.ERROR.Fatal(err)
|
return utils.NewBuildError("Unable to remove target file", "error", err, "file", destFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect stuff in a temp directory.
|
// Collect stuff in a temp directory.
|
||||||
tmpDir, err := ioutil.TempDir("", filepath.Base(revel.BasePath))
|
tmpDir, err := ioutil.TempDir("", filepath.Base(revelPaths.BasePath))
|
||||||
panicOnError(err, "Failed to get temp dir")
|
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
|
||||||
|
c.Build.Mode = c.Package.Mode
|
||||||
|
c.Build.TargetPath = tmpDir
|
||||||
|
c.Build.CopySource = c.Package.CopySource
|
||||||
|
if err = buildApp(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Create the zip file.
|
// Create the zip file.
|
||||||
archiveName := mustTarGzDir(destFile, tmpDir)
|
|
||||||
|
archiveName, err := utils.TarGzDir(destFile, tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("Your archive is ready:", archiveName)
|
fmt.Println("Your archive is ready:", archiveName)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
@echo off
|
|
||||||
{{.BinName}} -importPath {{.ImportPath}} -srcPath %CD%\src -runMode {{.Mode}}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
|
||||||
"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}
|
|
||||||
31
revel/package_test.go
Normal file
31
revel/package_test.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package main_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
main "github.com/revel/cmd/revel"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// test the commands.
|
||||||
|
func TestPackage(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
gopath := setup("revel-test-package", a)
|
||||||
|
|
||||||
|
t.Run("Package", func(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
c := newApp("package-test", model.NEW, nil, a)
|
||||||
|
a.Nil(main.Commands[model.NEW].RunWith(c), "failed to run new")
|
||||||
|
c.Index = model.PACKAGE
|
||||||
|
c.Package.ImportPath = c.ImportPath
|
||||||
|
a.Nil(main.Commands[model.PACKAGE].RunWith(c), "Failed to run package-test")
|
||||||
|
})
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
if err := os.RemoveAll(gopath); err != nil {
|
||||||
|
a.Fail("Failed to remove test path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
revel/rev.go
144
revel/rev.go
@@ -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())
|
|
||||||
}
|
|
||||||
159
revel/revel.go
Normal file
159
revel/revel.go
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/agtorre/gocolorize"
|
||||||
|
"github.com/jessevdk/go-flags"
|
||||||
|
"github.com/revel/cmd/logger"
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error is used for constant errors.
|
||||||
|
type Error string
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ErrInvalidCommandLine Error = "invalid command line arguments"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RevelCmdImportPath Revel framework cmd tool import path.
|
||||||
|
RevelSkeletonsImportPath = "github.com/revel/skeletons"
|
||||||
|
|
||||||
|
// 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) error
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commands defines the available 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 len(os.Args) < 2 {
|
||||||
|
parser.WriteHelp(os.Stdout)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ParseArgs(c, parser, os.Args[1:]); err != nil {
|
||||||
|
fmt.Fprint(os.Stderr, err.Error()+"\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch based on the verbose flag
|
||||||
|
if len(c.Verbose) > 1 {
|
||||||
|
utils.InitLogger(wd, logger.LvlDebug)
|
||||||
|
} else if len(c.Verbose) > 0 {
|
||||||
|
utils.InitLogger(wd, logger.LvlInfo)
|
||||||
|
} else {
|
||||||
|
utils.InitLogger(wd, logger.LvlWarn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup package resolver
|
||||||
|
c.InitPackageResolver()
|
||||||
|
|
||||||
|
if err := c.UpdateImportPath(); err != nil {
|
||||||
|
utils.Logger.Error(err.Error())
|
||||||
|
parser.WriteHelp(os.Stdout)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
command := Commands[c.Index]
|
||||||
|
println("Revel executing:", command.Short)
|
||||||
|
|
||||||
|
if err := command.RunWith(c); err != nil {
|
||||||
|
utils.Logger.Error("Unable to execute", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the arguments passed into the model.CommandConfig.
|
||||||
|
func ParseArgs(c *model.CommandConfig, parser *flags.Parser, args []string) (err error) {
|
||||||
|
var extraArgs []string
|
||||||
|
if ini := flag.String("ini", "none", ""); *ini != "none" {
|
||||||
|
if err = flags.NewIniParser(parser).ParseFile(*ini); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if extraArgs, err = parser.ParseArgs(args); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Commands[c.Index].UpdateConfig(c, extraArgs) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
parser.WriteHelp(buffer)
|
||||||
|
err = fmt.Errorf("%w %v\n%s", ErrInvalidCommandLine, extraArgs, buffer.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
}
|
||||||
164
revel/run.go
164
revel/run.go
@@ -5,138 +5,158 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/build"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/revel/cmd/harness"
|
"github.com/revel/cmd/harness"
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdRun = &Command{
|
var cmdRun = &Command{
|
||||||
UsageLine: "run [import path] [run mode] [port]",
|
UsageLine: "run [-m [run mode] -p [port]] [import path] ",
|
||||||
Short: "run a Revel application",
|
Short: "run a Revel application",
|
||||||
Long: `
|
Long: `
|
||||||
Run the Revel web application named by the given import path.
|
Run the Revel web application named by the given import path.
|
||||||
|
|
||||||
For example, to run the chat room sample application:
|
For example, to run the chat room sample application:
|
||||||
|
|
||||||
revel run github.com/revel/examples/chat dev
|
revel run -m dev github.com/revel/examples/chat
|
||||||
|
|
||||||
The run mode is used to select which set of app.conf configuration should
|
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.
|
apply and may be used to determine logic in the application itself.
|
||||||
|
|
||||||
Run mode defaults to "dev".
|
Run mode defaults to "dev".
|
||||||
|
|
||||||
You can set a port as an optional third parameter. For example:
|
You can set a port as well. For example:
|
||||||
|
|
||||||
revel run github.com/revel/examples/chat prod 8080`,
|
revel run -m prod -p 8080 github.com/revel/examples/chat `,
|
||||||
}
|
|
||||||
|
|
||||||
// RunArgs holds revel run parameters
|
|
||||||
type RunArgs struct {
|
|
||||||
ImportPath string
|
|
||||||
Mode string
|
|
||||||
Port int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmdRun.Run = runApp
|
cmdRun.RunWith = runApp
|
||||||
|
cmdRun.UpdateConfig = updateRunConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRunArgs(args []string) *RunArgs {
|
func updateRunConfig(c *model.CommandConfig, args []string) bool {
|
||||||
inputArgs := RunArgs{
|
convertPort := func(value string) int {
|
||||||
ImportPath: importPathFromCurrentDir(),
|
if value != "" {
|
||||||
Mode: DefaultRunMode,
|
port, err := strconv.Atoi(value)
|
||||||
Port: revel.HTTPPort,
|
if err != nil {
|
||||||
|
utils.Logger.Fatalf("Failed to parse port as integer: %s", c.Run.Port)
|
||||||
|
}
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 3:
|
case 3:
|
||||||
// Possibile combinations
|
// Possible combinations
|
||||||
// revel run [import-path] [run-mode] [port]
|
// revel run [import-path] [run-mode] [port]
|
||||||
port, err := strconv.Atoi(args[2])
|
c.Run.ImportPath = args[0]
|
||||||
if err != nil {
|
c.Run.Mode = args[1]
|
||||||
errorf("Failed to parse port as integer: %s", args[2])
|
c.Run.Port = convertPort(args[2])
|
||||||
}
|
|
||||||
inputArgs.ImportPath = args[0]
|
|
||||||
inputArgs.Mode = args[1]
|
|
||||||
inputArgs.Port = port
|
|
||||||
case 2:
|
case 2:
|
||||||
// Possibile combinations
|
// Possible combinations
|
||||||
// 1. revel run [import-path] [run-mode]
|
// 1. revel run [import-path] [run-mode]
|
||||||
// 2. revel run [import-path] [port]
|
// 2. revel run [import-path] [port]
|
||||||
// 3. revel run [run-mode] [port]
|
// 3. revel run [run-mode] [port]
|
||||||
if _, err := build.Import(args[0], "", build.FindOnly); err == nil {
|
|
||||||
|
// Check to see if the import path evaluates out to something that may be on a gopath
|
||||||
|
if runIsImportPath(args[0]) {
|
||||||
// 1st arg is the import path
|
// 1st arg is the import path
|
||||||
inputArgs.ImportPath = args[0]
|
c.Run.ImportPath = args[0]
|
||||||
if port, err := strconv.Atoi(args[1]); err == nil {
|
|
||||||
|
if _, err := strconv.Atoi(args[1]); err == nil {
|
||||||
// 2nd arg is the port number
|
// 2nd arg is the port number
|
||||||
inputArgs.Port = port
|
c.Run.Port = convertPort(args[1])
|
||||||
} else {
|
} else {
|
||||||
// 2nd arg is the run mode
|
// 2nd arg is the run mode
|
||||||
inputArgs.Mode = args[1]
|
c.Run.Mode = args[1]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 1st arg is the run mode
|
// 1st arg is the run mode
|
||||||
port, err := strconv.Atoi(args[1])
|
c.Run.Mode = args[0]
|
||||||
if err != nil {
|
c.Run.Port = convertPort(args[1])
|
||||||
errorf("Failed to parse port as integer: %s", args[1])
|
|
||||||
}
|
|
||||||
inputArgs.Mode = args[0]
|
|
||||||
inputArgs.Port = port
|
|
||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
// Possibile combinations
|
// Possible combinations
|
||||||
// 1. revel run [import-path]
|
// 1. revel run [import-path]
|
||||||
// 2. revel run [port]
|
// 2. revel run [port]
|
||||||
// 3. revel run [run-mode]
|
// 3. revel run [run-mode]
|
||||||
_, err := build.Import(args[0], "", build.FindOnly)
|
if runIsImportPath(args[0]) {
|
||||||
if err != nil {
|
|
||||||
revel.WARN.Printf("Unable to run using an import path, assuming import path is working directory %s %s", args[0], err.Error())
|
|
||||||
}
|
|
||||||
println("Trying to build with", args[0], err)
|
|
||||||
if err == nil {
|
|
||||||
// 1st arg is the import path
|
// 1st arg is the import path
|
||||||
inputArgs.ImportPath = args[0]
|
c.Run.ImportPath = args[0]
|
||||||
} else if port, err := strconv.Atoi(args[0]); err == nil {
|
} else if _, err := strconv.Atoi(args[0]); err == nil {
|
||||||
// 1st arg is the port number
|
// 1st arg is the port number
|
||||||
inputArgs.Port = port
|
c.Run.Port = convertPort(args[0])
|
||||||
} else {
|
} else {
|
||||||
// 1st arg is the run mode
|
// 1st arg is the run mode
|
||||||
inputArgs.Mode = args[0]
|
c.Run.Mode = args[0]
|
||||||
|
}
|
||||||
|
case 0:
|
||||||
|
// Attempt to set the import path to the current working director.
|
||||||
|
if c.Run.ImportPath == "" {
|
||||||
|
c.Run.ImportPath, _ = os.Getwd()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
c.Index = model.RUN
|
||||||
return &inputArgs
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func runApp(args []string) {
|
// Returns true if this is an absolute path or a relative gopath.
|
||||||
runArgs := parseRunArgs(args)
|
func runIsImportPath(pathToCheck string) bool {
|
||||||
|
return utils.DirExists(pathToCheck)
|
||||||
|
}
|
||||||
|
|
||||||
// Find and parse app.conf
|
// Called to run the app.
|
||||||
revel.Init(runArgs.Mode, runArgs.ImportPath, "")
|
func runApp(c *model.CommandConfig) (err error) {
|
||||||
revel.LoadMimeConfig()
|
if c.Run.Mode == "" {
|
||||||
|
c.Run.Mode = "dev"
|
||||||
// fallback to default port
|
|
||||||
if runArgs.Port == 0 {
|
|
||||||
runArgs.Port = revel.HTTPPort
|
|
||||||
}
|
}
|
||||||
|
|
||||||
revel.INFO.Printf("Running %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, runArgs.Mode)
|
revelPath, err := model.NewRevelPaths(c.Run.Mode, c.ImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
|
||||||
revel.TRACE.Println("Base path:", revel.BasePath)
|
if err != nil {
|
||||||
|
return utils.NewBuildIfError(err, "Revel paths")
|
||||||
|
}
|
||||||
|
if c.Run.Port > -1 {
|
||||||
|
revelPath.HTTPPort = c.Run.Port
|
||||||
|
} else {
|
||||||
|
c.Run.Port = revelPath.HTTPPort
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Logger.Infof("Running %s (%s) in %s mode\n", revelPath.AppName, revelPath.ImportPath, revelPath.RunMode)
|
||||||
|
utils.Logger.Debug("Base path:", "path", revelPath.BasePath)
|
||||||
|
|
||||||
// If the app is run in "watched" mode, use the harness to run it.
|
// 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) {
|
if revelPath.Config.BoolDefault("watch", true) && revelPath.Config.BoolDefault("watch.code", true) {
|
||||||
revel.TRACE.Println("Running in watched mode.")
|
utils.Logger.Info("Running in watched mode.")
|
||||||
revel.HTTPPort = runArgs.Port
|
|
||||||
harness.NewHarness().Run() // Never returns.
|
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v}`, revelPath.RunMode, c.GetVerbose())
|
||||||
|
if c.HistoricMode {
|
||||||
|
runMode = revelPath.RunMode
|
||||||
|
}
|
||||||
|
// **** Never returns.
|
||||||
|
harness.NewHarness(c, revelPath, runMode, c.Run.NoProxy).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Else, just build and run the app.
|
// Else, just build and run the app.
|
||||||
revel.TRACE.Println("Running in live build mode.")
|
utils.Logger.Debug("Running in live build mode.")
|
||||||
app, err := harness.Build()
|
app, err := harness.Build(c, revelPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("Failed to build app: %s", err)
|
utils.Logger.Errorf("Failed to build app: %s", err)
|
||||||
}
|
}
|
||||||
app.Port = runArgs.Port
|
app.Port = revelPath.HTTPPort
|
||||||
app.Cmd().Run()
|
var paths []byte
|
||||||
|
if len(app.PackagePathMap) > 0 {
|
||||||
|
paths, _ = json.Marshal(app.PackagePathMap)
|
||||||
|
}
|
||||||
|
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, app.Paths.RunMode, c.GetVerbose(), string(paths))
|
||||||
|
if c.HistoricMode {
|
||||||
|
runMode = revelPath.RunMode
|
||||||
|
}
|
||||||
|
app.Cmd(runMode).Run(c)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
22
revel/run_test.go
Normal file
22
revel/run_test.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package main_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// test the commands.
|
||||||
|
func TestRun(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
gopath := setup("revel-test-run", a)
|
||||||
|
|
||||||
|
// TODO Testing run
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
if err := os.RemoveAll(gopath); err != nil {
|
||||||
|
a.Fail("Failed to remove test path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
revel/skeleton/.gitignore
vendored
3
revel/skeleton/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
test-results/
|
|
||||||
tmp/
|
|
||||||
routes/
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Welcome to Revel
|
|
||||||
|
|
||||||
A high-productivity web framework for the [Go language](http://www.golang.org/).
|
|
||||||
|
|
||||||
|
|
||||||
### Start the web server:
|
|
||||||
|
|
||||||
revel run myapp
|
|
||||||
|
|
||||||
### Go to http://localhost:9000/ and you'll see:
|
|
||||||
|
|
||||||
"It works"
|
|
||||||
|
|
||||||
## Code Layout
|
|
||||||
|
|
||||||
The directory structure of a generated Revel application:
|
|
||||||
|
|
||||||
conf/ Configuration directory
|
|
||||||
app.conf Main app configuration file
|
|
||||||
routes Routes definition file
|
|
||||||
|
|
||||||
app/ App sources
|
|
||||||
init.go Interceptor registration
|
|
||||||
controllers/ App controllers go here
|
|
||||||
views/ Templates directory
|
|
||||||
|
|
||||||
messages/ Message files
|
|
||||||
|
|
||||||
public/ Public static assets
|
|
||||||
css/ CSS files
|
|
||||||
js/ Javascript files
|
|
||||||
images/ Image files
|
|
||||||
|
|
||||||
tests/ Test suites
|
|
||||||
|
|
||||||
|
|
||||||
## Help
|
|
||||||
|
|
||||||
* The [Getting Started with Revel](http://revel.github.io/tutorial/gettingstarted.html).
|
|
||||||
* The [Revel guides](http://revel.github.io/manual/index.html).
|
|
||||||
* The [Revel sample apps](http://revel.github.io/examples/index.html).
|
|
||||||
* The [API documentation](https://godoc.org/github.com/revel/revel).
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/revel/revel"
|
|
||||||
)
|
|
||||||
|
|
||||||
type App struct {
|
|
||||||
*revel.Controller
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c App) Index() revel.Result {
|
|
||||||
return c.Render()
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/revel/revel"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// AppVersion revel app version (ldflags)
|
|
||||||
AppVersion string
|
|
||||||
|
|
||||||
// BuildTime revel app build-time (ldflags)
|
|
||||||
BuildTime string
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Filters is the default set of global filters.
|
|
||||||
revel.Filters = []revel.Filter{
|
|
||||||
revel.PanicFilter, // Recover from panics and display an error page instead.
|
|
||||||
revel.RouterFilter, // Use the routing table to select the right Action
|
|
||||||
revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
|
|
||||||
revel.ParamsFilter, // Parse parameters into Controller.Params.
|
|
||||||
revel.SessionFilter, // Restore and write the session cookie.
|
|
||||||
revel.FlashFilter, // Restore and write the flash cookie.
|
|
||||||
revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
|
|
||||||
revel.I18nFilter, // Resolve the requested language
|
|
||||||
HeaderFilter, // Add some security based headers
|
|
||||||
revel.InterceptorFilter, // Run interceptors around the action.
|
|
||||||
revel.CompressFilter, // Compress the result.
|
|
||||||
revel.ActionInvoker, // Invoke the action.
|
|
||||||
}
|
|
||||||
|
|
||||||
// register startup functions with OnAppStart
|
|
||||||
// revel.DevMode and revel.RunMode only work inside of OnAppStart. See Example Startup Script
|
|
||||||
// ( order dependent )
|
|
||||||
// revel.OnAppStart(ExampleStartupScript)
|
|
||||||
// revel.OnAppStart(InitDB)
|
|
||||||
// revel.OnAppStart(FillCache)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderFilter adds common security headers
|
|
||||||
// TODO turn this into revel.HeaderFilter
|
|
||||||
// should probably also have a filter for CSRF
|
|
||||||
// not sure if it can go in the same filter or not
|
|
||||||
var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) {
|
|
||||||
c.Response.SetHttpHeader("X-Frame-Options", "SAMEORIGIN")
|
|
||||||
c.Response.SetHttpHeader("X-XSS-Protection", "1; mode=block")
|
|
||||||
c.Response.SetHttpHeader("X-Content-Type-Options", "nosniff")
|
|
||||||
|
|
||||||
fc[0](c, fc[1:]) // Execute the next filter stage.
|
|
||||||
}
|
|
||||||
|
|
||||||
//func ExampleStartupScript() {
|
|
||||||
// // revel.DevMod and revel.RunMode work here
|
|
||||||
// // Use this script to check for dev mode and set dev/prod startup scripts here!
|
|
||||||
// if revel.DevMode == true {
|
|
||||||
// // Dev mode
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{{set . "title" "Home"}}
|
|
||||||
{{template "header.html" .}}
|
|
||||||
|
|
||||||
<header class="jumbotron" style="background-color:#A9F16C">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<h1>It works!</h1>
|
|
||||||
<p></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="span6">
|
|
||||||
{{template "flash.html" .}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{template "footer.html" .}}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
<style type="text/css">
|
|
||||||
#sidebar {
|
|
||||||
position: absolute;
|
|
||||||
right: 0px;
|
|
||||||
top:69px;
|
|
||||||
max-width: 75%;
|
|
||||||
z-index: 1000;
|
|
||||||
background-color: #fee;
|
|
||||||
border: thin solid grey;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
#toggleSidebar {
|
|
||||||
position: absolute;
|
|
||||||
right: 0px;
|
|
||||||
top: 50px;
|
|
||||||
background-color: #fee;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
<div id="sidebar" style="display:none;">
|
|
||||||
<h4>Available pipelines</h4>
|
|
||||||
<dl>
|
|
||||||
{{ range $index, $value := .}}
|
|
||||||
<dt>{{$index}}</dt>
|
|
||||||
<dd>{{$value}}</dd>
|
|
||||||
{{end}}
|
|
||||||
</dl>
|
|
||||||
<h4>Flash</h4>
|
|
||||||
<dl>
|
|
||||||
{{ range $index, $value := .flash}}
|
|
||||||
<dt>{{$index}}</dt>
|
|
||||||
<dd>{{$value}}</dd>
|
|
||||||
{{end}}
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>Errors</h4>
|
|
||||||
<dl>
|
|
||||||
{{ range $index, $value := .errors}}
|
|
||||||
<dt>{{$index}}</dt>
|
|
||||||
<dd>{{$value}}</dd>
|
|
||||||
{{end}}
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
<a id="toggleSidebar" href="#" class="toggles"><i class="glyphicon glyphicon-chevron-left"></i></a>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
$sidebar = 0;
|
|
||||||
$('#toggleSidebar').click(function() {
|
|
||||||
if ($sidebar === 1) {
|
|
||||||
$('#sidebar').hide();
|
|
||||||
$('#toggleSidebar i').addClass('glyphicon-chevron-left');
|
|
||||||
$('#toggleSidebar i').removeClass('glyphicon-chevron-right');
|
|
||||||
$sidebar = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$('#sidebar').show();
|
|
||||||
$('#toggleSidebar i').addClass('glyphicon-chevron-right');
|
|
||||||
$('#toggleSidebar i').removeClass('glyphicon-chevron-left');
|
|
||||||
$sidebar = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Not found</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{{if eq .RunMode "dev"}}
|
|
||||||
{{template "errors/404-dev.html" .}}
|
|
||||||
{{else}}
|
|
||||||
{{with .Error}}
|
|
||||||
<h1>
|
|
||||||
{{.Title}}
|
|
||||||
</h1>
|
|
||||||
<p>
|
|
||||||
{{.Description}}
|
|
||||||
</p>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Application error</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{{if eq .RunMode "dev"}}
|
|
||||||
{{template "errors/500-dev.html" .}}
|
|
||||||
{{else}}
|
|
||||||
<h1>Oops, an error occured</h1>
|
|
||||||
<p>
|
|
||||||
This exception has been logged.
|
|
||||||
</p>
|
|
||||||
{{end}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{{if .flash.success}}
|
|
||||||
<div class="alert alert-success">
|
|
||||||
{{.flash.success}}
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if or .errors .flash.error}}
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
{{if .flash.error}}
|
|
||||||
{{.flash.error}}
|
|
||||||
{{end}}
|
|
||||||
<ul style="margin-top:10px;">
|
|
||||||
{{range .errors}}
|
|
||||||
<li>{{.}}</li>
|
|
||||||
{{end}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{{if eq .RunMode "dev"}}
|
|
||||||
{{template "debug.html" .}}
|
|
||||||
{{end}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>{{.title}}</title>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/public/css/bootstrap-3.3.6.min.css">
|
|
||||||
<link rel="shortcut icon" type="image/png" href="/public/img/favicon.png">
|
|
||||||
<script src="/public/js/jquery-2.2.4.min.js"></script>
|
|
||||||
<script src="/public/js/bootstrap-3.3.6.min.js"></script>
|
|
||||||
{{range .moreStyles}}
|
|
||||||
<link rel="stylesheet" type="text/css" href="/public/{{.}}">
|
|
||||||
{{end}}
|
|
||||||
{{range .moreScripts}}
|
|
||||||
<script src="/public/{{.}}" type="text/javascript" charset="utf-8"></script>
|
|
||||||
{{end}}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
@@ -1,258 +0,0 @@
|
|||||||
################################################################################
|
|
||||||
# Revel configuration file
|
|
||||||
# More info at http://revel.github.io/manual/appconf.html
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
# Sets `revel.AppName` for use in-app.
|
|
||||||
# Example:
|
|
||||||
# `if revel.AppName {...}`
|
|
||||||
app.name = {{ .AppName }}
|
|
||||||
|
|
||||||
# A secret string which is passed to cryptographically sign the cookie to prevent
|
|
||||||
# (and detect) user modification.
|
|
||||||
# Keep this string secret or users will be able to inject arbitrary cookie values
|
|
||||||
# into your application
|
|
||||||
app.secret = {{ .Secret }}
|
|
||||||
|
|
||||||
# Revel running behind proxy like nginx, haproxy, etc.
|
|
||||||
app.behind.proxy = false
|
|
||||||
|
|
||||||
|
|
||||||
# The IP address on which to listen.
|
|
||||||
http.addr =
|
|
||||||
|
|
||||||
# The port on which to listen.
|
|
||||||
http.port = 9000
|
|
||||||
|
|
||||||
# Whether to use SSL or not.
|
|
||||||
http.ssl = false
|
|
||||||
|
|
||||||
# Path to an X509 certificate file, if using SSL.
|
|
||||||
#http.sslcert =
|
|
||||||
|
|
||||||
# Path to an X509 certificate key, if using SSL.
|
|
||||||
#http.sslkey =
|
|
||||||
|
|
||||||
|
|
||||||
# Timeout specifies a time limit for request (in seconds) made by a single client.
|
|
||||||
# A Timeout of zero means no timeout.
|
|
||||||
http.timeout.read = 90
|
|
||||||
http.timeout.write = 60
|
|
||||||
|
|
||||||
|
|
||||||
# For any cookies set by Revel (Session,Flash,Error) these properties will set
|
|
||||||
# the fields of:
|
|
||||||
# http://golang.org/pkg/net/http/#Cookie
|
|
||||||
#
|
|
||||||
# Each cookie set by Revel is prefixed with this string.
|
|
||||||
cookie.prefix = REVEL
|
|
||||||
|
|
||||||
# A secure cookie has the secure attribute enabled and is only used via HTTPS,
|
|
||||||
# ensuring that the cookie is always encrypted when transmitting from client to
|
|
||||||
# server. This makes the cookie less likely to be exposed to cookie theft via
|
|
||||||
# eavesdropping.
|
|
||||||
#
|
|
||||||
# Defaults to false. If 'http.ssl' is enabled, this will be defaulted to true.
|
|
||||||
# This should only be true when Revel is handling SSL connections. If you are
|
|
||||||
# using a proxy in front of revel (Nginx, Apache, etc), then this should be left
|
|
||||||
# as false.
|
|
||||||
# cookie.secure = false
|
|
||||||
|
|
||||||
# Limit cookie access to a given domain.
|
|
||||||
#cookie.domain =
|
|
||||||
|
|
||||||
# Define when your session cookie expires.
|
|
||||||
# Values:
|
|
||||||
# "720h"
|
|
||||||
# A time duration (http://golang.org/pkg/time/#ParseDuration) after which
|
|
||||||
# the cookie expires and the session is invalid.
|
|
||||||
# "session"
|
|
||||||
# Sets a session cookie which invalidates the session when the user close
|
|
||||||
# the browser.
|
|
||||||
session.expires = 720h
|
|
||||||
|
|
||||||
|
|
||||||
# The date format used by Revel. Possible formats defined by the Go `time`
|
|
||||||
# package (http://golang.org/pkg/time/#Parse)
|
|
||||||
format.date = 2006-01-02
|
|
||||||
format.datetime = 2006-01-02 15:04
|
|
||||||
|
|
||||||
|
|
||||||
# Determines whether the template rendering should use chunked encoding.
|
|
||||||
# Chunked encoding can decrease the time to first byte on the client side by
|
|
||||||
# sending data before the entire template has been fully rendered.
|
|
||||||
results.chunked = false
|
|
||||||
|
|
||||||
|
|
||||||
# Prefixes for each log message line.
|
|
||||||
# User can override these prefix values within any section
|
|
||||||
# For e.g: [dev], [prod], etc
|
|
||||||
log.trace.prefix = "TRACE "
|
|
||||||
log.info.prefix = "INFO "
|
|
||||||
log.warn.prefix = "WARN "
|
|
||||||
log.error.prefix = "ERROR "
|
|
||||||
|
|
||||||
|
|
||||||
# The default language of this application.
|
|
||||||
i18n.default_language = en
|
|
||||||
|
|
||||||
# The default format when message is missing.
|
|
||||||
# The original message shows in %s
|
|
||||||
#i18n.unknown_format = "??? %s ???"
|
|
||||||
|
|
||||||
|
|
||||||
# Module to serve static content such as CSS, JavaScript and Media files
|
|
||||||
# Allows Routes like this:
|
|
||||||
# `Static.ServeModule("modulename","public")`
|
|
||||||
module.static=github.com/revel/modules/static
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
# Section: dev
|
|
||||||
# This section is evaluated when running Revel in dev mode. Like so:
|
|
||||||
# `revel run path/to/myapp`
|
|
||||||
[dev]
|
|
||||||
|
|
||||||
# This sets `revel.DevMode` for use in-app.
|
|
||||||
# Example:
|
|
||||||
# `if revel.DevMode {...}`
|
|
||||||
# or in your templates with
|
|
||||||
# `{{.DevMode}}`
|
|
||||||
# Values:
|
|
||||||
# "true"
|
|
||||||
# Sets `DevMode` to `true`.
|
|
||||||
# "false"
|
|
||||||
# Sets `DevMode` to `false`.
|
|
||||||
mode.dev = true
|
|
||||||
|
|
||||||
|
|
||||||
# Pretty print JSON/XML when calling RenderJSON/RenderXML
|
|
||||||
# Values:
|
|
||||||
# "true"
|
|
||||||
# Enables pretty printing.
|
|
||||||
# "false"
|
|
||||||
# Disables pretty printing.
|
|
||||||
results.pretty = true
|
|
||||||
|
|
||||||
|
|
||||||
# Watch your applicaton files for changes and automatically rebuild
|
|
||||||
# Values:
|
|
||||||
# "true"
|
|
||||||
# Enables auto rebuilding.
|
|
||||||
# "false"
|
|
||||||
# Disables auto rebuilding.
|
|
||||||
watch = true
|
|
||||||
|
|
||||||
|
|
||||||
# Define when to rebuild new changes.
|
|
||||||
# Values:
|
|
||||||
# "normal"
|
|
||||||
# Rebuild when a new request is received and changes have been detected.
|
|
||||||
# "eager"
|
|
||||||
# Rebuild as soon as changes are detected.
|
|
||||||
watch.mode = normal
|
|
||||||
|
|
||||||
# Watch the entire `$GOPATH` for changes.
|
|
||||||
# Values:
|
|
||||||
# "true"
|
|
||||||
# Includes `$GOPATH` in watch path.
|
|
||||||
# "false"
|
|
||||||
# Excludes `$GOPATH` from watch path. Default value.
|
|
||||||
#watch.gopath = true
|
|
||||||
|
|
||||||
|
|
||||||
# Module to run code tests in the browser
|
|
||||||
# See:
|
|
||||||
# http://revel.github.io/manual/testing.html
|
|
||||||
module.testrunner = github.com/revel/modules/testrunner
|
|
||||||
|
|
||||||
|
|
||||||
# Where to log the various Revel logs
|
|
||||||
# Values:
|
|
||||||
# "off"
|
|
||||||
# Disable log output.
|
|
||||||
# "stdout"
|
|
||||||
# Log to OS's standard output.
|
|
||||||
# "stderr"
|
|
||||||
# Log to Os's standard error output. Default value.
|
|
||||||
# "relative/path/to/log"
|
|
||||||
# Log to file.
|
|
||||||
log.trace.output = off
|
|
||||||
log.info.output = stderr
|
|
||||||
log.warn.output = stderr
|
|
||||||
log.error.output = stderr
|
|
||||||
|
|
||||||
|
|
||||||
# Revel log flags. Possible flags defined by the Go `log` package. Go log is
|
|
||||||
# "Bits OR'ed together to control what's printed
|
|
||||||
# See:
|
|
||||||
# https://golang.org/pkg/log/#pkg-constants
|
|
||||||
# Values:
|
|
||||||
# "0"
|
|
||||||
# Just log the message, turn off the flags.
|
|
||||||
# "3"
|
|
||||||
# log.LstdFlags (log.Ldate|log.Ltime)
|
|
||||||
# "19"
|
|
||||||
# log.Ldate|log.Ltime|log.Lshortfile
|
|
||||||
# "23"
|
|
||||||
# log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile
|
|
||||||
log.trace.flags = 19
|
|
||||||
log.info.flags = 19
|
|
||||||
log.warn.flags = 19
|
|
||||||
log.error.flags = 19
|
|
||||||
|
|
||||||
|
|
||||||
# Revel request access log
|
|
||||||
# Access log line format:
|
|
||||||
# RequestStartTime ClientIP ResponseStatus RequestLatency HTTPMethod URLPath
|
|
||||||
# Sample format:
|
|
||||||
# 2016/05/25 17:46:37.112 127.0.0.1 200 270.157µs GET /
|
|
||||||
log.request.output = stderr
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Section: prod
|
|
||||||
# This section is evaluated when running Revel in production mode. Like so:
|
|
||||||
# `revel run path/to/myapp prod`
|
|
||||||
# See:
|
|
||||||
# [dev] section for documentation of the various settings
|
|
||||||
[prod]
|
|
||||||
|
|
||||||
mode.dev = false
|
|
||||||
|
|
||||||
results.pretty = false
|
|
||||||
|
|
||||||
watch = false
|
|
||||||
|
|
||||||
module.testrunner =
|
|
||||||
|
|
||||||
log.trace.output = off
|
|
||||||
log.info.output = off
|
|
||||||
log.warn.output = log/%(app.name)s.log
|
|
||||||
log.error.output = log/%(app.name)s.log
|
|
||||||
|
|
||||||
# Revel log flags. Possible flags defined by the Go `log` package,
|
|
||||||
# please refer https://golang.org/pkg/log/#pkg-constants
|
|
||||||
# Go log is "Bits or'ed together to control what's printed"
|
|
||||||
# Examples:
|
|
||||||
# 0 => just log the message, turn off the flags
|
|
||||||
# 3 => log.LstdFlags (log.Ldate|log.Ltime)
|
|
||||||
# 19 => log.Ldate|log.Ltime|log.Lshortfile
|
|
||||||
# 23 => log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile
|
|
||||||
log.trace.flags = 3
|
|
||||||
log.info.flags = 3
|
|
||||||
log.warn.flags = 3
|
|
||||||
log.error.flags = 3
|
|
||||||
|
|
||||||
|
|
||||||
# Revel request access log
|
|
||||||
# Access log line format:
|
|
||||||
# RequestStartTime ClientIP ResponseStatus RequestLatency HTTPMethod URLPath
|
|
||||||
# Sample format:
|
|
||||||
# 2016/05/25 17:46:37.112 127.0.0.1 200 270.157µs GET /
|
|
||||||
# Example:
|
|
||||||
# log.request.output = %(app.name)s-request.log
|
|
||||||
log.request.output = off
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Routes Config
|
|
||||||
#
|
|
||||||
# This file defines all application routes (Higher priority routes first)
|
|
||||||
#
|
|
||||||
|
|
||||||
module:testrunner
|
|
||||||
# module:jobs
|
|
||||||
|
|
||||||
|
|
||||||
GET / App.Index
|
|
||||||
|
|
||||||
# Ignore favicon requests
|
|
||||||
GET /favicon.ico 404
|
|
||||||
|
|
||||||
# Map static resources from the /app/public folder to the /public path
|
|
||||||
GET /public/*filepath Static.Serve("public")
|
|
||||||
|
|
||||||
# Catch all
|
|
||||||
* /:controller/:action :controller.:action
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Sample messages file for the English language (en)
|
|
||||||
# Message file extensions should be ISO 639-1 codes (http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
|
|
||||||
# Sections within each message file can optionally override the defaults using ISO 3166-1 alpha-2 codes (http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
|
|
||||||
# See also:
|
|
||||||
# - http://www.rfc-editor.org/rfc/bcp/bcp47.txt
|
|
||||||
# - http://www.w3.org/International/questions/qa-accept-lang-locales
|
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 5.5 KiB |
File diff suppressed because one or more lines are too long
4
revel/skeleton/public/js/jquery-2.2.4.min.js
vendored
4
revel/skeleton/public/js/jquery-2.2.4.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,23 +0,0 @@
|
|||||||
package tests
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/revel/revel/testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AppTest struct {
|
|
||||||
testing.TestSuite
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AppTest) Before() {
|
|
||||||
println("Set up")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AppTest) TestThatIndexPageWorks() {
|
|
||||||
t.Get("/")
|
|
||||||
t.AssertOk()
|
|
||||||
t.AssertContentType("text/html; charset=utf-8")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AppTest) After() {
|
|
||||||
println("Tear down")
|
|
||||||
}
|
|
||||||
183
revel/test.go
183
revel/test.go
@@ -16,12 +16,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/revel/cmd/harness"
|
"github.com/revel/cmd/harness"
|
||||||
"github.com/revel/modules/testrunner/app/controllers"
|
"github.com/revel/cmd/model"
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd/tests"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdTest = &Command{
|
var cmdTest = &Command{
|
||||||
UsageLine: "test [import path] [run mode] [suite.method]",
|
UsageLine: "test <import path> [<run mode> <suite.method>]",
|
||||||
Short: "run all tests from the command-line",
|
Short: "run all tests from the command-line",
|
||||||
Long: `
|
Long: `
|
||||||
Run all tests for the Revel app named by the given import path.
|
Run all tests for the Revel app named by the given import path.
|
||||||
@@ -47,70 +48,113 @@ or one of UserTest's methods:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmdTest.Run = testApp
|
cmdTest.RunWith = testApp
|
||||||
|
cmdTest.UpdateConfig = updateTestConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func testApp(args []string) {
|
// Called to update the config command with from the older stype.
|
||||||
var err error
|
func updateTestConfig(c *model.CommandConfig, args []string) bool {
|
||||||
if len(args) == 0 {
|
c.Index = model.TEST
|
||||||
errorf("No import path given.\nRun 'revel help test' for usage.\n")
|
if len(args) == 0 && c.Test.ImportPath != "" {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) (err error) {
|
||||||
mode := DefaultRunMode
|
mode := DefaultRunMode
|
||||||
if len(args) >= 2 {
|
if c.Test.Mode != "" {
|
||||||
mode = args[1]
|
mode = c.Test.Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find and parse app.conf
|
// Find and parse app.conf
|
||||||
revel.Init(mode, args[0], "")
|
revelPath, err := model.NewRevelPaths(mode, c.ImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that the testrunner is loaded in this mode.
|
// todo Ensure that the testrunner is loaded in this mode.
|
||||||
checkTestRunner()
|
|
||||||
|
|
||||||
// Create a directory to hold the test result files.
|
// Create a directory to hold the test result files.
|
||||||
resultPath := filepath.Join(revel.BasePath, "test-results")
|
resultPath := filepath.Join(revelPath.BasePath, "test-results")
|
||||||
if err = os.RemoveAll(resultPath); err != nil {
|
if err = os.RemoveAll(resultPath); err != nil {
|
||||||
errorf("Failed to remove test result directory %s: %s", resultPath, err)
|
return utils.NewBuildError("Failed to remove test result directory ", "path", resultPath, "error", err)
|
||||||
}
|
}
|
||||||
if err = os.Mkdir(resultPath, 0777); err != nil {
|
if err = os.Mkdir(resultPath, 0777); err != nil {
|
||||||
errorf("Failed to create test result directory %s: %s", resultPath, err)
|
return utils.NewBuildError("Failed to create test result directory ", "path", resultPath, "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct all the output into a file in the test-results directory.
|
// 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)
|
file, err := os.OpenFile(filepath.Join(resultPath, "app.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("Failed to create test result log file: %s", err)
|
return utils.NewBuildError("Failed to create test result log file: ", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app, reverr := harness.Build()
|
app, reverr := harness.Build(c, revelPath)
|
||||||
if reverr != nil {
|
if reverr != nil {
|
||||||
errorf("Error building: %s", reverr)
|
return utils.NewBuildIfError(reverr, "Error building: ")
|
||||||
}
|
}
|
||||||
cmd := app.Cmd()
|
var paths []byte
|
||||||
|
if len(app.PackagePathMap) > 0 {
|
||||||
|
paths, _ = json.Marshal(app.PackagePathMap)
|
||||||
|
}
|
||||||
|
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, app.Paths.RunMode, c.GetVerbose(), string(paths))
|
||||||
|
if c.HistoricMode {
|
||||||
|
runMode = app.Paths.RunMode
|
||||||
|
}
|
||||||
|
cmd := app.Cmd(runMode)
|
||||||
|
cmd.Dir = c.AppPath
|
||||||
|
|
||||||
cmd.Stderr = io.MultiWriter(cmd.Stderr, file)
|
cmd.Stderr = io.MultiWriter(cmd.Stderr, file)
|
||||||
cmd.Stdout = io.MultiWriter(cmd.Stderr, file)
|
cmd.Stdout = io.MultiWriter(cmd.Stderr, file)
|
||||||
|
|
||||||
// Start the app...
|
// Start the app...
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(c); err != nil {
|
||||||
errorf("%s", err)
|
return utils.NewBuildError("Unable to start server", "error", err)
|
||||||
}
|
}
|
||||||
defer cmd.Kill()
|
defer cmd.Kill()
|
||||||
revel.INFO.Printf("Testing %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, mode)
|
|
||||||
|
httpAddr := revelPath.HTTPAddr
|
||||||
|
if httpAddr == "" {
|
||||||
|
httpAddr = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
httpProto := "http"
|
||||||
|
if revelPath.HTTPSsl {
|
||||||
|
httpProto = "https"
|
||||||
|
}
|
||||||
|
|
||||||
// Get a list of tests
|
// Get a list of tests
|
||||||
var baseURL = fmt.Sprintf("http://127.0.0.1:%d", revel.HTTPPort)
|
baseURL := fmt.Sprintf("%s://%s:%d", httpProto, httpAddr, revelPath.HTTPPort)
|
||||||
|
|
||||||
|
utils.Logger.Infof("Testing %s (%s) in %s mode URL %s \n", revelPath.AppName, revelPath.ImportPath, mode, baseURL)
|
||||||
testSuites, _ := getTestsList(baseURL)
|
testSuites, _ := getTestsList(baseURL)
|
||||||
|
|
||||||
// If a specific TestSuite[.Method] is specified, only run that suite/test
|
// If a specific TestSuite[.Method] is specified, only run that suite/test
|
||||||
if len(args) == 3 {
|
if c.Test.Function != "" {
|
||||||
testSuites = filterTestSuites(testSuites, args[2])
|
testSuites = filterTestSuites(testSuites, c.Test.Function)
|
||||||
}
|
}
|
||||||
|
|
||||||
testSuiteCount := len(*testSuites)
|
testSuiteCount := len(*testSuites)
|
||||||
fmt.Printf("\n%d test suite%s to run.\n", testSuiteCount, pluralize(testSuiteCount, "", "s"))
|
fmt.Printf("\n%d test suite%s to run.\n", testSuiteCount, pluralize(testSuiteCount, "", "s"))
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
// Run each suite.
|
// Run each suite.
|
||||||
failedResults, overallSuccess := runTestSuites(baseURL, resultPath, testSuites)
|
failedResults, overallSuccess := runTestSuites(revelPath, baseURL, resultPath, testSuites)
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
if overallSuccess {
|
if overallSuccess {
|
||||||
@@ -127,16 +171,20 @@ func testApp(args []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeResultFile(resultPath, "result.failed", "failed")
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Outputs the results to a file.
|
||||||
func writeResultFile(resultPath, name, content string) {
|
func writeResultFile(resultPath, name, content string) {
|
||||||
if err := ioutil.WriteFile(filepath.Join(resultPath, name), []byte(content), 0666); err != nil {
|
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 {
|
func pluralize(num int, singular, plural string) string {
|
||||||
if num == 1 {
|
if num == 1 {
|
||||||
return singular
|
return singular
|
||||||
@@ -145,8 +193,8 @@ func pluralize(num int, singular, plural string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filters test suites and individual tests to match
|
// Filters test suites and individual tests to match
|
||||||
// the parsed command line parameter
|
// 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
|
var suiteName, testName string
|
||||||
argArray := strings.Split(suiteArgument, ".")
|
argArray := strings.Split(suiteArgument, ".")
|
||||||
suiteName = argArray[0]
|
suiteName = argArray[0]
|
||||||
@@ -161,54 +209,34 @@ func filterTestSuites(suites *[]controllers.TestSuiteDesc, suiteArgument string)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if testName == "" {
|
if testName == "" {
|
||||||
return &[]controllers.TestSuiteDesc{suite}
|
return &[]tests.TestSuiteDesc{suite}
|
||||||
}
|
}
|
||||||
// Only run a particular test in a suite
|
// Only run a particular test in a suite
|
||||||
for _, test := range suite.Tests {
|
for _, test := range suite.Tests {
|
||||||
if test.Name != testName {
|
if test.Name != testName {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return &[]controllers.TestSuiteDesc{
|
return &[]tests.TestSuiteDesc{
|
||||||
{
|
{
|
||||||
Name: suite.Name,
|
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
|
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.
|
// Get a list of tests from server.
|
||||||
// Since this is the first request to the server, retry/sleep a couple times
|
// Since this is the first request to the server, retry/sleep a couple times
|
||||||
// in case it hasn't finished starting up yet.
|
// in case it hasn't finished starting up yet.
|
||||||
func getTestsList(baseURL string) (*[]controllers.TestSuiteDesc, error) {
|
func getTestsList(baseURL string) (*[]tests.TestSuiteDesc, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
resp *http.Response
|
resp *http.Response
|
||||||
testSuites []controllers.TestSuiteDesc
|
testSuites []tests.TestSuiteDesc
|
||||||
)
|
)
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
if resp, err = http.Get(baseURL + "/@tests.list"); err == nil {
|
if resp, err = http.Get(baseURL + "/@tests.list"); err == nil {
|
||||||
@@ -221,9 +249,9 @@ func getTestsList(baseURL string) (*[]controllers.TestSuiteDesc, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("Failed to request test list: %s", err)
|
utils.Logger.Fatalf("Failed to request test list: %s %s", baseURL, err)
|
||||||
} else {
|
} 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() {
|
defer func() {
|
||||||
@@ -235,21 +263,14 @@ func getTestsList(baseURL string) (*[]controllers.TestSuiteDesc, error) {
|
|||||||
return &testSuites, err
|
return &testSuites, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTestSuites(baseURL, resultPath string, testSuites *[]controllers.TestSuiteDesc) (*[]controllers.TestSuiteResult, bool) {
|
// Run the testsuites using the container.
|
||||||
// Load the result template, which we execute for each suite.
|
func runTestSuites(paths *model.RevelContainer, baseURL, resultPath string, testSuites *[]tests.TestSuiteDesc) (*[]tests.TestSuiteResult, bool) {
|
||||||
module, _ := revel.ModuleByName("testrunner")
|
// We can determine the testsuite location by finding the test module and extracting the data from it
|
||||||
TemplateLoader := revel.NewTemplateLoader([]string{filepath.Join(module.Path, "app", "views")})
|
resultFilePath := filepath.Join(paths.ModulePathMap["testrunner"].Path, "app", "views", "TestRunner/SuiteResult.html")
|
||||||
if err := TemplateLoader.Refresh(); err != nil {
|
|
||||||
errorf("Failed to compile templates: %s", err)
|
|
||||||
}
|
|
||||||
resultTemplate, err := TemplateLoader.Template("TestRunner/SuiteResult.html")
|
|
||||||
if err != nil {
|
|
||||||
errorf("Failed to load suite result template: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
overallSuccess = true
|
overallSuccess = true
|
||||||
failedResults []controllers.TestSuiteResult
|
failedResults []tests.TestSuiteResult
|
||||||
)
|
)
|
||||||
for _, suite := range *testSuites {
|
for _, suite := range *testSuites {
|
||||||
// Print the name of the suite we're running.
|
// Print the name of the suite we're running.
|
||||||
@@ -261,21 +282,25 @@ func runTestSuites(baseURL, resultPath string, testSuites *[]controllers.TestSui
|
|||||||
|
|
||||||
// Run every test.
|
// Run every test.
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
suiteResult := controllers.TestSuiteResult{Name: suite.Name, Passed: true}
|
suiteResult := tests.TestSuiteResult{Name: suite.Name, Passed: true}
|
||||||
for _, test := range suite.Tests {
|
for _, test := range suite.Tests {
|
||||||
testURL := baseURL + "/@tests/" + suite.Name + "/" + test.Name
|
testURL := baseURL + "/@tests/" + suite.Name + "/" + test.Name
|
||||||
resp, err := http.Get(testURL)
|
resp, err := http.Get(testURL)
|
||||||
if err != nil {
|
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() {
|
defer func() {
|
||||||
_ = resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var testResult controllers.TestResult
|
var testResult tests.TestResult
|
||||||
err = json.NewDecoder(resp.Body).Decode(&testResult)
|
err = json.NewDecoder(resp.Body).Decode(&testResult)
|
||||||
if err == nil && !testResult.Passed {
|
if err == nil && !testResult.Passed {
|
||||||
suiteResult.Passed = false
|
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)
|
suiteResult.Results = append(suiteResult.Results, testResult)
|
||||||
}
|
}
|
||||||
@@ -291,12 +316,8 @@ func runTestSuites(baseURL, resultPath string, testSuites *[]controllers.TestSui
|
|||||||
// Create the result HTML file.
|
// Create the result HTML file.
|
||||||
suiteResultFilename := filepath.Join(resultPath,
|
suiteResultFilename := filepath.Join(resultPath,
|
||||||
fmt.Sprintf("%s.%s.html", suite.Name, strings.ToLower(suiteResultStr)))
|
fmt.Sprintf("%s.%s.html", suite.Name, strings.ToLower(suiteResultStr)))
|
||||||
suiteResultFile, err := os.Create(suiteResultFilename)
|
if err := utils.RenderTemplate(suiteResultFilename, resultFilePath, suiteResult); err != nil {
|
||||||
if err != nil {
|
utils.Logger.Error("Failed to render template", "error", err)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
31
revel/test_test.go
Normal file
31
revel/test_test.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package main_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
main "github.com/revel/cmd/revel"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// test the commands.
|
||||||
|
func TestRevelTest(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
gopath := setup("revel-test-test", a)
|
||||||
|
|
||||||
|
t.Run("Test", func(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
c := newApp("test-test", model.NEW, nil, a)
|
||||||
|
a.Nil(main.Commands[model.NEW].RunWith(c), "Failed to run test-test")
|
||||||
|
c.Index = model.TEST
|
||||||
|
c.Test.ImportPath = c.ImportPath
|
||||||
|
a.Nil(main.Commands[model.TEST].RunWith(c), "Failed to run test-test")
|
||||||
|
})
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
if err := os.RemoveAll(gopath); err != nil {
|
||||||
|
a.Fail("Failed to remove test path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
176
revel/util.go
176
revel/util.go
@@ -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)
|
|
||||||
}
|
|
||||||
217
revel/version.go
217
revel/version.go
@@ -9,30 +9,229 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/revel/revel"
|
"github.com/revel/cmd"
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// The version container.
|
||||||
|
VersionCommand struct {
|
||||||
|
Command *model.CommandConfig // The command
|
||||||
|
revelVersion *model.Version // The Revel framework version
|
||||||
|
modulesVersion *model.Version // The Revel modules version
|
||||||
|
cmdVersion *model.Version // The tool version
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdVersion = &Command{
|
var cmdVersion = &Command{
|
||||||
UsageLine: "version",
|
UsageLine: "revel version",
|
||||||
Short: "displays the Revel Framework and Go version",
|
Short: "displays the Revel Framework and Go version",
|
||||||
Long: `
|
Long: `
|
||||||
Displays the Revel Framework and Go version.
|
Displays the Revel Framework and Go version.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
revel version
|
revel version [<application path>]
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmdVersion.Run = versionApp
|
v := &VersionCommand{}
|
||||||
|
cmdVersion.UpdateConfig = v.UpdateConfig
|
||||||
|
cmdVersion.RunWith = v.RunWith
|
||||||
}
|
}
|
||||||
|
|
||||||
func versionApp(args []string) {
|
// Update the version.
|
||||||
fmt.Printf("Version(s):")
|
func (v *VersionCommand) UpdateConfig(c *model.CommandConfig, args []string) bool {
|
||||||
fmt.Printf("\n Revel v%v (%v)", revel.Version, revel.BuildDate)
|
if len(args) > 0 {
|
||||||
fmt.Printf("\n %s %s/%s\n\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
c.Version.ImportPath = args[0]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Displays the version of go and Revel.
|
||||||
|
func (v *VersionCommand) RunWith(c *model.CommandConfig) (err error) {
|
||||||
|
utils.Logger.Info("Requesting version information", "config", c)
|
||||||
|
v.Command = c
|
||||||
|
|
||||||
|
// Update the versions with the local values
|
||||||
|
v.updateLocalVersions()
|
||||||
|
|
||||||
|
needsUpdates := true
|
||||||
|
versionInfo := ""
|
||||||
|
for x := 0; x < 2 && needsUpdates; x++ {
|
||||||
|
versionInfo, needsUpdates = v.doRepoCheck(x == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s\n\nGo Location:%s\n\n", versionInfo, c.GoCmd)
|
||||||
|
cmd := exec.Command(c.GoCmd, "version")
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
if e := cmd.Start(); e != nil {
|
||||||
|
fmt.Println("Go command error ", e)
|
||||||
|
} else {
|
||||||
|
if err = cmd.Wait(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks the Revel repos for the latest version.
|
||||||
|
func (v *VersionCommand) doRepoCheck(updateLibs bool) (versionInfo string, needsUpdate bool) {
|
||||||
|
var (
|
||||||
|
title string
|
||||||
|
localVersion *model.Version
|
||||||
|
)
|
||||||
|
for _, repo := range []string{"revel", "cmd", "modules"} {
|
||||||
|
versonFromRepo, err := v.versionFromRepo(repo, "", "version.go")
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Info("Failed to get version from repo", "repo", repo, "error", err)
|
||||||
|
}
|
||||||
|
switch repo {
|
||||||
|
case "revel":
|
||||||
|
title, repo, localVersion = "Revel Framework", "github.com/revel/revel", v.revelVersion
|
||||||
|
case "cmd":
|
||||||
|
title, repo, localVersion = "Revel Cmd", "github.com/revel/cmd/revel", v.cmdVersion
|
||||||
|
case "modules":
|
||||||
|
title, repo, localVersion = "Revel Modules", "github.com/revel/modules", v.modulesVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only do an update on the first loop, and if specified to update
|
||||||
|
versionInfo += v.outputVersion(title, repo, localVersion, versonFromRepo)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints out the local and remote versions, calls update if needed.
|
||||||
|
func (v *VersionCommand) outputVersion(title, repo string, local, remote *model.Version) (output string) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
remoteVersion := "Unknown"
|
||||||
|
if remote != nil {
|
||||||
|
remoteVersion = remote.VersionString()
|
||||||
|
}
|
||||||
|
localVersion := "Unknown"
|
||||||
|
if local != nil {
|
||||||
|
localVersion = local.VersionString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(buffer, "%s\t:\t%s\t(%s remote master branch)\n", title, localVersion, remoteVersion)
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the version from the repository.
|
||||||
|
func (v *VersionCommand) versionFromRepo(repoName, branchName, fileName string) (version *model.Version, err error) {
|
||||||
|
if branchName == "" {
|
||||||
|
branchName = "master"
|
||||||
|
}
|
||||||
|
// Try to download the version of file from the repo, just use an http connection to retrieve the source
|
||||||
|
// Assuming that the repo is github
|
||||||
|
fullurl := "https://raw.githubusercontent.com/revel/" + repoName + "/" + branchName + "/" + fileName
|
||||||
|
resp, err := http.Get(fullurl)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.Logger.Info("Got version file", "from", fullurl, "content", string(body))
|
||||||
|
|
||||||
|
return v.versionFromBytes(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VersionCommand) versionFromFilepath(sourcePath string) (version *model.Version, err error) {
|
||||||
|
utils.Logger.Info("Fullpath to revel", "dir", sourcePath)
|
||||||
|
|
||||||
|
sourceStream, err := ioutil.ReadFile(filepath.Join(sourcePath, "version.go"))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return v.versionFromBytes(sourceStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns version information from a file called version on the gopath.
|
||||||
|
func (v *VersionCommand) versionFromBytes(sourceStream []byte) (version *model.Version, err error) {
|
||||||
|
fset := token.NewFileSet() // positions are relative to fset
|
||||||
|
|
||||||
|
// Parse src but stop after processing the imports.
|
||||||
|
f, err := parser.ParseFile(fset, "", sourceStream, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
err = utils.NewBuildError("Failed to parse Revel version error:", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
version = &model.Version{}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
switch spec.Names[0].Name {
|
||||||
|
case "Version":
|
||||||
|
if err = version.ParseVersion(strings.ReplaceAll(r.Value, `"`, "")); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "BuildDate":
|
||||||
|
version.BuildDate = r.Value
|
||||||
|
case "MinimumGoVersion":
|
||||||
|
version.MinGoVersion = r.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the local version of revel from the file system.
|
||||||
|
func (v *VersionCommand) updateLocalVersions() {
|
||||||
|
v.cmdVersion = &model.Version{}
|
||||||
|
|
||||||
|
if err := v.cmdVersion.ParseVersion(cmd.Version); err != nil {
|
||||||
|
utils.Logger.Warn("Error parsing version", "error", err, "version", cmd.Version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v.cmdVersion.BuildDate = cmd.BuildDate
|
||||||
|
v.cmdVersion.MinGoVersion = cmd.MinimumGoVersion
|
||||||
|
|
||||||
|
if v.Command.Version.ImportPath == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pathMap, err := utils.FindSrcPaths(v.Command.AppPath, []string{model.RevelImportPath, model.RevelModulesImportPath}, v.Command.PackageResolver)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Warn("Unable to extract version information from Revel library", "path", pathMap[model.RevelImportPath], "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.Logger.Info("Fullpath to revel modules", "dir", pathMap[model.RevelImportPath])
|
||||||
|
v.revelVersion, err = v.versionFromFilepath(pathMap[model.RevelImportPath])
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Warn("Unable to extract version information from Revel", "error,err")
|
||||||
|
}
|
||||||
|
|
||||||
|
v.modulesVersion, err = v.versionFromFilepath(pathMap[model.RevelModulesImportPath])
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Warn("Unable to extract version information from Revel Modules", "path", pathMap[model.RevelModulesImportPath], "error", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
revel/version_test.go
Normal file
44
revel/version_test.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package main_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
main "github.com/revel/cmd/revel"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// test the commands.
|
||||||
|
func TestVersion(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
gopath := setup("revel-test-version", a)
|
||||||
|
|
||||||
|
t.Run("Version", func(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
c := newApp("version-test", model.NEW, nil, a)
|
||||||
|
a.Nil(main.Commands[model.NEW].RunWith(c), "Check new")
|
||||||
|
c.Build.ImportPath = c.ImportPath
|
||||||
|
c.Build.TargetPath = filepath.Join(gopath, "build-test", "target")
|
||||||
|
a.Nil(main.Commands[model.BUILD].RunWith(c), "Failed to run build")
|
||||||
|
c.Index = model.VERSION
|
||||||
|
c.Version.ImportPath = c.ImportPath
|
||||||
|
a.Nil(main.Commands[model.VERSION].RunWith(c), "Failed to run version-test")
|
||||||
|
})
|
||||||
|
t.Run("Version-Nobuild", func(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
c := newApp("version-test2", model.NEW, nil, a)
|
||||||
|
a.Nil(main.Commands[model.NEW].RunWith(c), "Check new")
|
||||||
|
c.Index = model.VERSION
|
||||||
|
c.Version.ImportPath = c.ImportPath
|
||||||
|
a.Nil(main.Commands[model.VERSION].RunWith(c), "Failed to run version-test")
|
||||||
|
})
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
if err := os.RemoveAll(gopath); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
a.Fail("Failed to remove test path", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
tests/testrunner.go
Normal file
44
tests/testrunner.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// 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 (
|
||||||
|
"html/template"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
107
utils/build_error.go
Normal file
107
utils/build_error.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
BuildError struct {
|
||||||
|
Stack interface{}
|
||||||
|
Message string
|
||||||
|
Args []interface{}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Returns a new builed error.
|
||||||
|
func NewBuildError(message string, args ...interface{}) (b *BuildError) {
|
||||||
|
Logger.Info(message, args...)
|
||||||
|
b = &BuildError{}
|
||||||
|
b.Message = message
|
||||||
|
b.Args = args
|
||||||
|
b.Stack = logger.NewCallStack()
|
||||||
|
Logger.Info("Stack", "stack", b.Stack)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a new BuildError if err is not nil.
|
||||||
|
func NewBuildIfError(err error, message string, args ...interface{}) (b error) {
|
||||||
|
if err != nil {
|
||||||
|
var berr *BuildError
|
||||||
|
if errors.As(err, &berr) {
|
||||||
|
// This is already a build error so just append the args
|
||||||
|
berr.Args = append(berr.Args, args...)
|
||||||
|
return berr
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, "error", err.Error())
|
||||||
|
b = NewBuildError(message, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildError implements Error() string.
|
||||||
|
func (b *BuildError) Error() string {
|
||||||
|
return fmt.Sprint(b.Message, b.Args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the output of the "go build" command.
|
||||||
|
// Return a detailed Error.
|
||||||
|
func NewCompileError(importPath, errorLink string, err error) *SourceError {
|
||||||
|
// Get the stack from the error
|
||||||
|
|
||||||
|
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
|
||||||
|
FindSubmatch([]byte(err.Error()))
|
||||||
|
if errorMatch == nil {
|
||||||
|
errorMatch = regexp.MustCompile(`(?m)^(.*?):(\d+):\s(.*?)$`).FindSubmatch([]byte(err.Error()))
|
||||||
|
|
||||||
|
if errorMatch == nil {
|
||||||
|
Logger.Error("Failed to parse build errors", "error", err)
|
||||||
|
return &SourceError{
|
||||||
|
SourceType: "Go code",
|
||||||
|
Title: "Go Compilation Error",
|
||||||
|
Description: "See console for build error.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMatch = append(errorMatch, errorMatch[3])
|
||||||
|
|
||||||
|
Logger.Error("Build errors", "errors", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the source for the offending file.
|
||||||
|
var (
|
||||||
|
relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go"
|
||||||
|
absFilename = relFilename
|
||||||
|
line, _ = strconv.Atoi(string(errorMatch[2]))
|
||||||
|
description = string(errorMatch[4])
|
||||||
|
compileError = &SourceError{
|
||||||
|
SourceType: "Go code",
|
||||||
|
Title: "Go Compilation Error",
|
||||||
|
Path: relFilename,
|
||||||
|
Description: description,
|
||||||
|
Line: line,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// errorLink := paths.Config.StringDefault("error.link", "")
|
||||||
|
|
||||||
|
if errorLink != "" {
|
||||||
|
compileError.SetLink(errorLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileStr, err := ReadLines(absFilename)
|
||||||
|
if err != nil {
|
||||||
|
compileError.MetaError = absFilename + ": " + err.Error()
|
||||||
|
Logger.Info("Unable to readlines "+compileError.MetaError, "error", err)
|
||||||
|
return compileError
|
||||||
|
}
|
||||||
|
|
||||||
|
compileError.SourceLines = fileStr
|
||||||
|
return compileError
|
||||||
|
}
|
||||||
49
utils/command.go
Normal file
49
utils/command.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"go/build"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize the command based on the GO environment.
|
||||||
|
func CmdInit(c *exec.Cmd, addGoPath bool, basePath string) {
|
||||||
|
c.Dir = basePath
|
||||||
|
// Dep does not like paths that are not real, convert all paths in go to real paths
|
||||||
|
// Fetch the rest of the env variables
|
||||||
|
c.Env = ReducedEnv(addGoPath)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReducedEnv returns a list of environment vairables by using os.Env
|
||||||
|
// it will remove the GOPATH, GOROOT if addGoPath is true
|
||||||
|
func ReducedEnv(addGoPath bool) []string {
|
||||||
|
realPath := &bytes.Buffer{}
|
||||||
|
env := []string{}
|
||||||
|
if addGoPath {
|
||||||
|
for _, p := range filepath.SplitList(build.Default.GOPATH) {
|
||||||
|
rp, _ := filepath.EvalSymlinks(p)
|
||||||
|
if realPath.Len() > 0 {
|
||||||
|
realPath.WriteString(string(filepath.ListSeparator))
|
||||||
|
}
|
||||||
|
realPath.WriteString(rp)
|
||||||
|
}
|
||||||
|
// Go 1.8 fails if we do not include the GOROOT
|
||||||
|
env = []string{"GOPATH=" + realPath.String(), "GOROOT=" + os.Getenv("GOROOT")}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range os.Environ() {
|
||||||
|
pair := strings.Split(e, "=")
|
||||||
|
// Always exclude gomodcache
|
||||||
|
if pair[0] == "GOMODCACHE" {
|
||||||
|
continue
|
||||||
|
} else if !addGoPath && (pair[0] == "GOPATH" || pair[0] == "GOROOT") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
env = append(env, e)
|
||||||
|
}
|
||||||
|
return env
|
||||||
|
}
|
||||||
92
utils/error.go
Normal file
92
utils/error.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The error is a wrapper for the.
|
||||||
|
type (
|
||||||
|
SourceError struct {
|
||||||
|
SourceType string // The type of source that failed to build.
|
||||||
|
Title, Path, Description string // Description of the error, as presented to the user.
|
||||||
|
Line, Column int // Where the error was encountered.
|
||||||
|
SourceLines []string // The entire source file, split into lines.
|
||||||
|
Stack string // The raw stack trace string from debug.Stack().
|
||||||
|
MetaError string // Error that occurred producing the error page.
|
||||||
|
Link string // A configurable link to wrap the error source in
|
||||||
|
}
|
||||||
|
SourceLine struct {
|
||||||
|
Source string
|
||||||
|
Line int
|
||||||
|
IsError bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Return a new error object.
|
||||||
|
func NewError(source, title, path, description string) *SourceError {
|
||||||
|
return &SourceError{
|
||||||
|
SourceType: source,
|
||||||
|
Title: title,
|
||||||
|
Path: path,
|
||||||
|
Description: description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a link based on the configuration setting "errors.link".
|
||||||
|
func (e *SourceError) SetLink(errorLink string) {
|
||||||
|
errorLink = strings.ReplaceAll(errorLink, "{{Path}}", e.Path)
|
||||||
|
errorLink = strings.ReplaceAll(errorLink, "{{Line}}", strconv.Itoa(e.Line))
|
||||||
|
|
||||||
|
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 *SourceError) 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 *SourceError) 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
|
||||||
|
}
|
||||||
409
utils/file.go
Normal file
409
utils/file.go
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DirExists returns true if the given path exists and is a directory.
|
||||||
|
func DirExists(filename string) bool {
|
||||||
|
fileInfo, err := os.Stat(filename)
|
||||||
|
return err == nil && fileInfo.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy file returns error.
|
||||||
|
func CopyFile(destFilename, srcFilename string) (err error) {
|
||||||
|
destFile, err := os.Create(destFilename)
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to create file", "file", destFilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcFile, err := os.Open(srcFilename)
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to open file", "file", srcFilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(destFile, srcFile)
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to copy data", "fromfile", srcFilename, "tofile", destFilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = destFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to close file", "file", destFilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = srcFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to close file", "file", srcFilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateTemplate renders the given template to produce source code, which it writes
|
||||||
|
// to the given file.
|
||||||
|
func GenerateTemplate(filename, templateSource string, args map[string]interface{}) (err error) {
|
||||||
|
tmpl := template.Must(template.New("").Parse(templateSource))
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err = tmpl.Execute(&b, args); err != nil {
|
||||||
|
return NewBuildIfError(err, "ExecuteTemplate: Execute failed")
|
||||||
|
}
|
||||||
|
sourceCode := b.String()
|
||||||
|
filePath := filepath.Dir(filename)
|
||||||
|
if !DirExists(filePath) {
|
||||||
|
err = os.MkdirAll(filePath, 0777)
|
||||||
|
if err != nil && !os.IsExist(err) {
|
||||||
|
return NewBuildIfError(err, "Failed to make directory", "dir", filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the file
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
Logger.Fatal("Failed to create file", "error", err)
|
||||||
|
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 RenderTemplate(destPath, srcPath string, data interface{}) (err error) {
|
||||||
|
tmpl, err := template.ParseFiles(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to parse template "+srcPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(destPath)
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to create ", "path", destPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tmpl.Execute(f, data)
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to Render template "+srcPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to close file stream "+destPath)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the target path and source path and data. A template.
|
||||||
|
func RenderTemplateToStream(output io.Writer, srcPath []string, data interface{}) (err error) {
|
||||||
|
tmpl, err := template.ParseFiles(srcPath...)
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to parse template "+srcPath[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tmpl.Execute(output, data)
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to render template "+srcPath[0])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
var serr *SourceError
|
||||||
|
if (errors.As(err, &serr) && serr != nil) || err != nil {
|
||||||
|
Logger.Panicf("Abort: %s: %s %s", msg, serr, 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 CopyDir(destDir, srcDir string, data map[string]interface{}) error {
|
||||||
|
if !DirExists(srcDir) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
return NewBuildIfError(err, "Failed to create directory", "path", destDir+"/"+relSrcPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this file ends in ".template", render it as a template.
|
||||||
|
if strings.HasSuffix(relSrcPath, ".template") {
|
||||||
|
return RenderTemplate(destPath[:len(destPath)-len(".template")], srcPath, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, just copy it over.
|
||||||
|
|
||||||
|
return CopyFile(destPath, srcPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shortcut to fsWalk.
|
||||||
|
func Walk(root string, walkFn filepath.WalkFunc) error {
|
||||||
|
return fsWalk(root, root, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk the path tree using the function
|
||||||
|
// Every file found will call the function.
|
||||||
|
func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
|
||||||
|
fsWalkFunc := func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tar gz the folder.
|
||||||
|
func TarGzDir(destFilename, srcDir string) (name string, err error) {
|
||||||
|
zipFile, err := os.Create(destFilename)
|
||||||
|
if err != nil {
|
||||||
|
return "", NewBuildIfError(err, "Failed to create archive", "file", destFilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = zipFile.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
gzipWriter := gzip.NewWriter(zipFile)
|
||||||
|
defer func() {
|
||||||
|
_ = gzipWriter.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
tarWriter := tar.NewWriter(gzipWriter)
|
||||||
|
defer func() {
|
||||||
|
_ = tarWriter.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = fsWalk(srcDir, srcDir, func(srcPath string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
Logger.Debugf("error in walkFn: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
srcFile, err := os.Open(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to read file", "file", srcPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to write tar entry header", "file", srcPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(tarWriter, srcFile)
|
||||||
|
if err != nil {
|
||||||
|
return NewBuildIfError(err, "Failed to copy file", "file", srcPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return zipFile.Name(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if the file exists.
|
||||||
|
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 {
|
||||||
|
if !DirExists(dirname) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
dir, err := os.Open(dirname)
|
||||||
|
if err != nil {
|
||||||
|
Logger.Infof("error opening directory: %s", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = dir.Close()
|
||||||
|
}()
|
||||||
|
results, _ := dir.Readdir(1)
|
||||||
|
return len(results) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory.
|
||||||
|
func FindSrcPaths(appPath string, packageList []string, packageResolver func(pkgName string) error) (sourcePathsmap map[string]string, err error) {
|
||||||
|
sourcePathsmap, missingList, err := findSrcPaths(appPath, packageList)
|
||||||
|
if err != nil && packageResolver != nil || len(missingList) > 0 {
|
||||||
|
Logger.Info("Failed to find package, attempting to call resolver for missing packages", "missing packages", missingList)
|
||||||
|
for _, item := range missingList {
|
||||||
|
if err = packageResolver(item); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sourcePathsmap, missingList, err = findSrcPaths(appPath, packageList)
|
||||||
|
}
|
||||||
|
if err != nil && len(missingList) > 0 {
|
||||||
|
for _, missing := range missingList {
|
||||||
|
Logger.Error("Unable to import this package", "package", missing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error is used for constant errors.
|
||||||
|
type Error string
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoApp Error = "no app found"
|
||||||
|
ErrNoRevel Error = "no revel found"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory.
|
||||||
|
func findSrcPaths(appPath string, packagesList []string) (sourcePathsmap map[string]string, missingList []string, err error) {
|
||||||
|
// Use packages to fetch
|
||||||
|
// by not specifying env, we will use the default env
|
||||||
|
config := &packages.Config{
|
||||||
|
Mode: packages.NeedName | packages.NeedFiles | packages.NeedDeps,
|
||||||
|
Dir: appPath,
|
||||||
|
}
|
||||||
|
config.Env = ReducedEnv(false)
|
||||||
|
sourcePathsmap = map[string]string{}
|
||||||
|
Logger.Infof("Environment path %s root %s config env %s", os.Getenv("GOPATH"), os.Getenv("GOROOT"), config.Env)
|
||||||
|
|
||||||
|
pkgs, err := packages.Load(config, packagesList...)
|
||||||
|
Logger.Infof("Environment path %s root %s config env %s", os.Getenv("GOPATH"), os.Getenv("GOROOT"), config.Env)
|
||||||
|
Logger.Info("Loaded packages ", "len results", len(pkgs), "error", err, "basedir", appPath)
|
||||||
|
for _, packageName := range packagesList {
|
||||||
|
found := false
|
||||||
|
log := Logger.New("seeking", packageName)
|
||||||
|
for _, pck := range pkgs {
|
||||||
|
log.Info("Found package", "package", pck.ID)
|
||||||
|
if pck.ID == packageName {
|
||||||
|
if pck.Errors != nil && len(pck.Errors) > 0 {
|
||||||
|
log.Error("Error ", "count", len(pck.Errors), "App Import Path", pck.ID, "filesystem path", pck.PkgPath, "errors", pck.Errors)
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
// a,_ := pck.MarshalJSON()
|
||||||
|
log.Info("Found ", "count", len(pck.GoFiles), "App Import Path", pck.ID, "apppath", appPath)
|
||||||
|
if len(pck.GoFiles) > 0 {
|
||||||
|
sourcePathsmap[packageName] = filepath.Dir(pck.GoFiles[0])
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
if packageName == "github.com/revel/revel" {
|
||||||
|
err = ErrNoRevel
|
||||||
|
} else {
|
||||||
|
err = ErrNoApp
|
||||||
|
}
|
||||||
|
missingList = append(missingList, packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user