Compare commits

..

56 Commits

Author SHA1 Message Date
Steve
e2480171e5 Merge pull request #214 from revel/master
v1.1.1 hotfix sync
2022-04-12 15:20:35 -07:00
Brenden Soares
d83d2d5891 Merge pull request #213 from revel/hotfix/v1.1.0
Fixed run mode for verbose
2022-04-12 15:19:24 -07:00
notzippy@gmail.com
55b736e588 Fixed run mode for verbose 2022-04-12 15:08:06 -07:00
Brenden Soares
8162ef3ed6 develop v1.2.0-dev 2022-04-12 13:37:12 -07:00
Brenden Soares
0ec9e69d97 Merge pull request #212 from revel/build_process_update
Updated verbose flag
2022-04-12 08:40:27 -07:00
notzippy@gmail.com
1cdd318d9c Updated verbose flag 2022-04-12 08:30:29 -07:00
Brenden Soares
da2a81d035 release v1.1.0 2022-04-11 23:57:40 -07:00
Brenden Soares
86b4670a2f Update README.md 2022-04-11 20:31:59 -07:00
Brenden Soares
bc376fbee0 Merge pull request #211 from lujiacn/develop
updated golang.org/x/tools, to avoid internal error: package xxx with…
2022-04-11 20:30:13 -07:00
Brenden Soares
5c8ac53937 Merge branch 'develop' into develop 2022-04-11 20:30:05 -07:00
Brenden Soares
c67408482e Merge pull request #201 from ptman/lint
More linting
2022-04-11 20:19:06 -07:00
Brenden Soares
cfe1d9718f Merge branch 'develop' into lint 2022-04-11 20:18:54 -07:00
Brenden Soares
7f9f658392 Merge pull request #209 from glaslos/patch-1
Fixing typo
2022-04-11 20:05:38 -07:00
Brenden Soares
126d20c873 Merge pull request #210 from revel/build_process_update
Updated Launch code
2022-04-11 20:03:26 -07:00
Jia Lu
111264cfa0 updated go.mod 2022-03-25 14:46:52 +08:00
Jia Lu
4087c49b9e updated golang.org/x/tools, to avoid internal error: package xxx without types was imported from ... 2022-03-25 10:34:03 +08:00
Steve
3602eb4ea7 Merge branch 'develop' into patch-1 2022-03-06 09:01:17 -08:00
Steve
192fc6669a Merge pull request #200 from julidau/develop
harness: interrupt process on windows as well
2022-03-06 08:58:38 -08:00
Steve
5689f8679b Merge pull request #204 from shinypb/master
Get rid of redundant space in the output of `revel new -a`
2022-03-06 08:55:23 -08:00
notzippy@gmail.com
6dba0c3d2d Fix bad error syntax
An wrapped error message in the cmd module was referencing the wrong parameter value to be built
closes revel/revel#1532
2022-03-06 07:45:11 -08:00
notzippy@gmail.com
bb926f396a Added additional pattern to test against
Another different missing pacakge error thrown that can be detected and added
This error occurs because a package may have been stripped down when originally loaded
2022-03-05 09:10:51 -08:00
notzippy@gmail.com
3cd5ebbde2 Updated launch scripts 2022-03-04 17:21:29 -08:00
notzippy@gmail.com
25dc05b31e Updated Launch code
Added output to error stack, so terminal errors are displayed
Ficed c.Verbose, it was changed to an array which causes issues launching
Removed . notation from doing anything special. This was already replaced with the -p CLI option
Added documentaiton on adding the package name
Started watcher with force refresh.
2022-02-28 20:01:01 -08:00
Brenden Soares
0a40a2048e Merge pull request #208 from notzippy/build_process_update
Fixed building
2022-02-23 09:44:18 -08:00
Lukas Rist
fcc1319245 Fixing type 2022-02-23 14:55:14 +01:00
notzippy@gmail.com
ea5acb720f Updated shared build environments
Updated check for errors.
Updated go.mod
Added .vscode launch
2022-02-20 17:16:12 -08:00
Mark Christian
25d6352bc5 Get rid of redundant space in the output of revel new -a 2021-08-15 14:29:07 -07:00
Paul Tötterman
ddec572d5d More linting 2021-02-10 16:34:20 +02:00
Julian Daube
7a91d0ca0b interrupt process on windows as well
On Windows, the killing of running services relies on the
60 second kill timeout, making it faster to restart the harness
to force rebuilding.

Since os.Interrupt works for me, remove the runtime os check in
harness/app.go completely.
2021-02-07 00:30:16 +01:00
Steve
b562bd2dc5 Merge pull request #199 from ptman/lint
Lint fixes
2020-10-20 06:29:15 -07:00
Steve
bf17a71166 Merge branch 'develop' into lint 2020-10-19 20:34:02 -07:00
Paul Tötterman
3d924a016b Lint fixes 2020-10-19 13:40:52 +03:00
Steve
3cec19ee62 Merge pull request #187 from Laur1nMartins/master
Fixed parsing of user defined linker flags
2020-07-21 09:12:17 -07:00
Steve
236499f9e5 Merge pull request #192 from revel/hotfix-1
Added local import map to getControllerFunc lookup
2020-07-14 22:10:25 -07:00
notzippy@gmail.com
42e0e3bf2b Added local import map to getControllerFunc lookup 2020-07-14 21:46:37 -07:00
Steve
b0484c9279 Merge pull request #188 from revel/hotfix-1
Fixed issues with test cases and launching app using the run mode
2020-07-14 13:16:12 -07:00
notzippy@gmail.com
c7f4307a5d Changed the local code walker to include all go files in the base path, and not just the app path 2020-07-14 12:45:10 -07:00
notzippy@gmail.com
ebc9c73ba0 Modified run command so launching command would be properly treated 2020-07-13 22:35:22 -07:00
Steve
2e2f22ad7d remove go 1.12 test 2020-07-13 09:40:56 -07:00
Steve
357c382d96 Version compatible Check
Increase version compatibility to 1.0 - 1.9
2020-07-13 09:39:57 -07:00
Laurin
6d4ae81af9 Fixed parsing of user defined linker flags 2020-07-12 11:09:57 +02:00
notzippy@gmail.com
d64c7f164f develop v1.1.0-dev 2020-07-11 22:57:37 -07:00
notzippy@gmail.com
6ecc0a7c0a release v1.0.0 2020-07-11 22:57:36 -07:00
Steve
d8117a33d3 Merge pull request #186 from notzippy/go-mod
Removed version update from Revel
2020-06-06 07:50:58 -07:00
notzippy@gmail.com
6371373eb5 Removed version update
Version control is maintained through go.mod file
Modified harness to only kill the application if not responded after 60 seconds in windows
2020-06-06 07:49:10 -07:00
Steve
28ac65f1c1 Merge pull request #185 from notzippy/go-mod
Go mod updates
2020-05-19 02:42:57 -07:00
notzippy@gmail.com
5070fb8be0 Fixed issue with new and run flag
Updated tests to run final test in non gopath, with new name
2020-05-19 02:23:18 -07:00
notzippy@gmail.com
904cfa2995 Added some informational messages while download 2020-05-18 12:43:00 -07:00
notzippy@gmail.com
223bd3b7c0 Added manual scan on packages in app folder
This allows for source code generation. Packages in <application>/app folder are scanned manually as opposed to the `packages.Load` scan which will fast fail on compile error, and leave you with go files with no syntax.
2020-05-18 11:47:01 -07:00
notzippy@gmail.com
4987ee8319 Added verbose logging to building / testing a no-vendor app
Removed section which raises an error when examining packages, we dont need to check for errors on foreign packages since we are importing only a slice of the data
2020-05-17 05:58:28 -07:00
notzippy@gmail.com
4bab4409b9 Updated Revel command
Added a check to see if harness had already started, saves a recompile on load
Added check to source info for local import renames
Removed the go/build check for path and just check existence of the path
Formatting updates
2020-05-13 22:26:05 -07:00
notzippy@gmail.com
741f49236a Updated scanner
Removed scanning all the import statements, this is not needed
Added recursive scan for revel import path to pick up testunits
2020-05-08 15:41:20 -07:00
Steve
60b88a42c9 Merge pull request #180 from notzippy/go-mod
Initial commit to go mod
2020-05-03 21:47:09 -07:00
notzippy@gmail.com
49eef29bb5 Build and Historic build updates
Modified GOPATH to not modify build with go.mod
Updated go.mod to version 1.12
Updated harness to setup listener before killing process
Updated notvendored flag to --no-vendor
Updated command_config to ensure no-vendor can be build
Added additional checks in source path lookup
2020-05-03 13:39:48 -07:00
notzippy@gmail.com
9d3a554bec Updates
Updated NotVendored flag
Updated travis matrix
Updated build log
2020-04-29 22:01:28 -07:00
notzippy@gmail.com
36bd6b944a Corrected flags 2020-04-29 21:48:11 -07:00
74 changed files with 1687 additions and 1481 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,3 @@
.idea/ .idea/
*.iml *.iml
.temp/

View File

@@ -1,15 +1,14 @@
language: go language: go
go: go:
- "1.12.x" - "1.13.x"
# - "1.13.x" - "1.14.x"
# - "1.14.x" - "tip"
# - "tip"
os: os:
# - osx - osx
- linux - linux
# - windows - windows
sudo: false sudo: false
@@ -19,22 +18,23 @@ branches:
- develop - develop
env: env:
# Setting environments variables
- GO111MODULE=on - GO111MODULE=on
install: install:
# Setting environments variables
- export PATH=$PATH:$HOME/gopath/bin - export PATH=$PATH:$HOME/gopath/bin
- export REVEL_BRANCH="develop" - export REVEL_BRANCH="develop"
- 'if [[ "$TRAVIS_BRANCH" == "master" ]]; then export REVEL_BRANCH="master"; fi' - 'if [[ "$TRAVIS_BRANCH" == "master" ]]; then export REVEL_BRANCH="master"; fi'
- 'echo "Travis branch: $TRAVIS_BRANCH, Revel dependency branch: $REVEL_BRANCH"' - 'echo "Travis branch: $TRAVIS_BRANCH, Revel dependency branch: $REVEL_BRANCH"'
#- git clone -b $REVEL_BRANCH git://github.com/revel/revel ../revel/
#- git clone -b $REVEL_BRANCH git://github.com/revel/modules ../modules/
# Since travis already checks out go build the commandline tool (revel) # Since travis already checks out go build the commandline tool (revel)
- go get -t -v github.com/revel/cmd/revel - mkdir $HOME/GOPATH_PROTECTED
- echo $GOPATH - export GOPATH=$HOME/GOPATH_PROTECTED
- echo $PATH - go build -o $HOME/gopath/bin/revel github.com/revel/cmd/revel
- pwd - pwd
- env
script: script:
- go test -v github.com/revel/cmd/revel/...
# Ensure the new-app flow works (plus the other commands). # Ensure the new-app flow works (plus the other commands).
#- revel version #- revel version
#- revel new my/testapp #- revel new my/testapp
@@ -46,23 +46,25 @@ script:
#- revel package my/testapp prod #- revel package my/testapp prod
# Ensure the new-app flow works (plus the other commands). # Ensure the new-app flow works (plus the other commands).
- revel new --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -a my/testapp2 -v - 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@develop" -a my/testapp2 -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@develop" -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@develop" -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
- revel build --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -a my/testapp2 -v -t build/testapp2 -m prod - 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@develop" -a my/testapp2 -v - 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@develop" -a my/testapp2 -v -m prod - revel package --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 -v -m prod
- revel new --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -v -a my/testapp3 -V - export INITIALWD=$PWD
- revel test --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -v -a my/testapp3 # Check build works with no-vendor flag
- revel clean --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -v -a my/testapp3 - cd $GOPATH
- revel build --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -a my/testapp3 -t build/testapp3 - export GO111MODULE=auto
- revel build --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -a my/testapp3 -t build/testapp3 -m prod - revel new -a my/testapp2 --no-vendor -v
- revel package --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -a my/testapp3 - revel test -a my/testapp2 -v
- revel package --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -a my/testapp3 -m prod
- go test -v github.com/revel/cmd/revel/... # 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: matrix:
allow_failures: allow_failures:

50
.vscode/launch.json vendored Normal file
View 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
View File

@@ -0,0 +1 @@
http_load -rate 5 -seconds 10 load.web

2
.vscode/load.web vendored Normal file
View File

@@ -0,0 +1,2 @@
http://localhost:9000/
http://localhost:9000/miss

20
.vscode/tasks.json vendored Normal file
View 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"
},
]
}

View File

@@ -11,7 +11,7 @@ Provides the `revel` command, used to create and run Revel apps.
Install Install
------------ ------------
```bash ```bash
go get -u github.com/revel/cmd/revel go install github.com/revel/cmd/revel@latest
``` ```
New Application New Application
@@ -19,7 +19,7 @@ New Application
Create a new application Create a new application
```commandline ```commandline
revel new my/app revel new -a my/app
``` ```
## Community ## Community

32
go.mod
View File

@@ -1,29 +1,35 @@
module github.com/revel/cmd module github.com/revel/cmd
go 1.13 go 1.17
require ( require (
github.com/BurntSushi/toml v0.3.1 // indirect github.com/BurntSushi/toml v1.0.0 // indirect
github.com/agtorre/gocolorize v1.0.0 github.com/agtorre/gocolorize v1.0.0
github.com/fsnotify/fsnotify v1.4.7 github.com/davecgh/go-spew v1.1.0 // indirect
github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1 // 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/jessevdk/go-flags v1.4.0
github.com/mattn/go-colorable v0.1.4 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/myesui/uuid v1.0.0 // indirect
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/revel/config v0.21.0 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/log15 v2.11.20+incompatible
github.com/revel/modules v0.21.0 // indirect
github.com/revel/pathtree v0.0.0-20140121041023-41257a1839e9 // indirect github.com/revel/pathtree v0.0.0-20140121041023-41257a1839e9 // indirect
github.com/revel/revel v0.21.0 github.com/revel/revel v1.0.0
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.7.0
github.com/twinj/uuid v1.0.0 // indirect github.com/twinj/uuid v1.0.0 // indirect
github.com/xeonx/timeago v1.0.0-rc4 // indirect github.com/xeonx/timeago v1.0.0-rc4 // indirect
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/tools v0.0.0-20200219054238-753a1d49df85 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 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/natefinch/lumberjack.v2 v2.0.0
gopkg.in/stack.v0 v0.0.0-20141108040640-9b43fcefddd0 gopkg.in/stack.v0 v0.0.0-20141108040640-9b43fcefddd0
gopkg.in/stretchr/testify.v1 v1.2.2 // indirect 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
View 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=

View File

@@ -6,18 +6,28 @@ 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/cmd/model" "github.com/revel/cmd/model"
"github.com/revel/cmd/utils" "github.com/revel/cmd/utils"
"runtime"
) )
// 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 {
@@ -28,9 +38,9 @@ type App struct {
Paths *model.RevelContainer 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, paths *model.RevelContainer, packagePathMap map[string]string) *App { func NewApp(binPath string, paths *model.RevelContainer, packagePathMap map[string]string) *App {
return &App{BinaryPath: binPath, Paths: paths, Port: paths.HTTPPort, PackagePathMap:packagePathMap} 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.
@@ -50,7 +60,7 @@ 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, runMode string, paths *model.RevelContainer) 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),
@@ -64,8 +74,9 @@ func NewAppCmd(binPath string, port int, runMode string, paths *model.RevelConta
func (cmd AppCmd) Start(c *model.CommandConfig) error { func (cmd AppCmd) Start(c *model.CommandConfig) error {
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool), c, &bytes.Buffer{}} listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool), c, &bytes.Buffer{}}
cmd.Stdout = listeningWriter cmd.Stdout = listeningWriter
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args, "dir", cmd.Dir, "env", cmd.Env) cmd.Stderr = listeningWriter
utils.CmdInit(cmd.Cmd, !c.Vendored, c.AppPath) 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 {
utils.Logger.Fatal("Error running:", "error", err) utils.Logger.Fatal("Error running:", "error", err)
} }
@@ -73,26 +84,28 @@ func (cmd AppCmd) Start(c *model.CommandConfig) error {
select { select {
case exitState := <-cmd.waitChan(): case exitState := <-cmd.waitChan():
fmt.Println("Startup failure view previous messages, \n Proxy is listening :", c.Run.Port) 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, "") err := utils.NewError("", "Revel Run Error", "starting your application there was an exception. See terminal output, "+exitState, "")
// TODO pretiffy command line output atomic.SwapInt32(&startupError, 1)
// err.MetaError = listeningWriter.getLastOutput() // TODO pretiffy command line output
err.Stack = listeningWriter.buffer.String()
return err return err
case <-time.After(60 * time.Second): case <-time.After(60 * time.Second):
println("Revel proxy is listening, point your browser to :", c.Run.Port) 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) 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) println("Revel proxy is listening, point your browser to :", c.Run.Port)
return nil return nil
} }
} }
// 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) {
utils.CmdInit(cmd.Cmd, !c.Vendored, c.AppPath)
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args) 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 {
utils.Logger.Fatal("Error running:", "error", err) utils.Logger.Fatal("Error running:", "error", err)
@@ -101,37 +114,21 @@ func (cmd AppCmd) Run() {
// 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()) {
// Windows appears to send the kill to all threads, shutting down the // Windows appears to send the kill to all threads, shutting down the
// server before this can, this check will ensure the process is still running // server before this can, this check will ensure the process is still running
if _, err := os.FindProcess(int(cmd.Process.Pid));err!=nil { if _, err := os.FindProcess(cmd.Process.Pid); err != nil {
// Server has already exited // Server has already exited
utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid) utils.Logger.Info("Server not running revel server pid", "pid", cmd.Process.Pid)
return return
} }
// Send an interrupt signal to allow for a graceful shutdown // Wait for the shutdown channel
utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid) waitMutex := &sync.WaitGroup{}
var err error waitMutex.Add(1)
if runtime.GOOS == "windows" {
// os.Interrupt is not available on windows
err = cmd.Process.Signal(os.Kill)
} else {
err = cmd.Process.Signal(os.Interrupt)
}
if err != nil {
utils.Logger.Error(
"Revel app failed to kill process.",
"processid", cmd.Process.Pid,"error",err,
"killerror", cmd.Process.Kill())
return
}
// Wait for the shutdown
ch := make(chan bool, 1) ch := make(chan bool, 1)
go func() { go func() {
waitMutex.Done()
s, err := cmd.Process.Wait() s, err := cmd.Process.Wait()
defer func() { defer func() {
ch <- true ch <- true
@@ -143,6 +140,21 @@ func (cmd AppCmd) Kill() {
utils.Logger.Info("Revel App exited", "state", s.String()) 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 // Use a timer to ensure that the process exits
utils.Logger.Info("Waiting to exit") utils.Logger.Info("Waiting to exit")
@@ -150,7 +162,7 @@ func (cmd AppCmd) Kill() {
case <-ch: case <-ch:
return return
case <-time.After(60 * time.Second): case <-time.After(60 * time.Second):
// Kill the process // Kill the process
utils.Logger.Error( utils.Logger.Error(
"Revel app failed to exit in 60 seconds - killing.", "Revel app failed to exit in 60 seconds - killing.",
"processid", cmd.Process.Pid, "processid", cmd.Process.Pid,
@@ -187,7 +199,7 @@ type startupListeningWriter struct {
buffer *bytes.Buffer buffer *bytes.Buffer
} }
// Writes to this output stream // Writes to this output stream.
func (w *startupListeningWriter) Write(p []byte) (int, error) { func (w *startupListeningWriter) Write(p []byte) (int, error) {
if w.notifyReady != nil && bytes.Contains(p, []byte("Revel engine is listening on")) { if w.notifyReady != nil && bytes.Contains(p, []byte("Revel engine is listening on")) {
w.notifyReady <- true w.notifyReady <- true
@@ -204,10 +216,3 @@ func (w *startupListeningWriter) Write(p []byte) (int, error) {
} }
return w.dest.Write(p) return w.dest.Write(p)
} }
// Returns the cleaned output from the response
// TODO clean the response more
func (w *startupListeningWriter) getLastOutput() string {
return w.buffer.String()
}

View File

@@ -19,22 +19,25 @@ import (
"time" "time"
"github.com/revel/cmd/model" "github.com/revel/cmd/model"
_ "github.com/revel/cmd/parser"
"github.com/revel/cmd/utils"
"github.com/revel/cmd/parser2"
"github.com/revel/cmd/parser" "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 type ByString []*model.TypeInfo
func (c ByString) Len() int { func (c ByString) Len() int {
return len(c) return len(c)
} }
func (c ByString) Swap(i, j int) { func (c ByString) Swap(i, j int) {
c[i], c[j] = c[j], c[i] c[i], c[j] = c[j], c[i]
} }
func (c ByString) Less(i, j int) bool { func (c ByString) Less(i, j int) bool {
return c[i].String() < c[j].String() return c[i].String() < c[j].String()
} }
@@ -102,28 +105,8 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err
utils.Logger.Fatal("Go executable not found in PATH.") utils.Logger.Fatal("Go executable not found in PATH.")
} }
// Detect if deps tool should be used (is there a vendor folder ?) // Binary path is a combination of target/app directory, app's import path and its name.
useVendor := utils.DirExists(filepath.Join(paths.BasePath, "vendor")) binName := filepath.Join("target", "app", paths.ImportPath, filepath.Base(paths.BasePath))
basePath := paths.BasePath
for !useVendor {
basePath = filepath.Dir(basePath)
found := false
// Check to see if we are still in the GOPATH
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
if strings.HasPrefix(basePath, gopath) {
found = true
break
}
}
if !found {
break
} else {
useVendor = utils.DirExists(filepath.Join(basePath, "vendor"))
}
}
// Binary path is a combination of BasePath/target/app directory, app's import path and its name.
binName := filepath.Join(paths.BasePath, "target", "app", paths.ImportPath, filepath.Base(paths.BasePath))
// Change binary path for Windows build // Change binary path for Windows build
goos := runtime.GOOS goos := runtime.GOOS
@@ -149,7 +132,7 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err
goModCmd := exec.Command(goPath, append([]string{"mod"}, strings.Split(gomod, " ")...)...) goModCmd := exec.Command(goPath, append([]string{"mod"}, strings.Split(gomod, " ")...)...)
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath) utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
output, err := goModCmd.CombinedOutput() output, err := goModCmd.CombinedOutput()
utils.Logger.Infof("Gomod applied ", "output", string(output)) utils.Logger.Info("Gomod applied ", "output", string(output))
// If the build succeeded, we're done. // If the build succeeded, we're done.
if err != nil { if err != nil {
@@ -165,7 +148,7 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err
} }
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'",
paths.ImportPath, appVersion, paths.ImportPath, buildTime) paths.ImportPath, appVersion, paths.ImportPath, buildTime)
// Append any build flags specified, they will override existing flags // Append any build flags specified, they will override existing flags
@@ -175,21 +158,21 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err
"build", "build",
"-ldflags", versionLinkerFlags, "-ldflags", versionLinkerFlags,
"-tags", buildTags, "-tags", buildTags,
"-o", binName} "-o", binName,
}
} else { } else {
if !contains(c.BuildFlags, "build") { if !contains(c.BuildFlags, "build") {
flags = []string{"build"} flags = []string{"build"}
} }
flags = append(flags, c.BuildFlags...)
if !contains(flags, "-ldflags") { if !contains(flags, "-ldflags") {
ldflags := "-ldflags= " + versionLinkerFlags ldflags := "-ldflags= " + versionLinkerFlags
// Add in build flags // Add user defined build flags
for i := range c.BuildFlags { for i := range c.BuildFlags {
ldflags += "-X '" + c.BuildFlags[i] + "'" ldflags += " -X '" + c.BuildFlags[i] + "'"
} }
flags = append(flags, ldflags) flags = append(flags, ldflags)
} }
if !contains(flags, "-tags") { if !contains(flags, "-tags") && buildTags != "" {
flags = append(flags, "-tags", buildTags) flags = append(flags, "-tags", buildTags)
} }
if !contains(flags, "-o") { if !contains(flags, "-o") {
@@ -197,9 +180,6 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err
} }
} }
// Add in build flags
flags = append(flags, c.BuildFlags...)
// Note: It's not applicable for filepath.* usage // Note: It's not applicable for filepath.* usage
flags = append(flags, path.Join(paths.ImportPath, "app", "tmp")) flags = append(flags, path.Join(paths.ImportPath, "app", "tmp"))
@@ -212,7 +192,7 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err
} }
buildCmd.Env = append(os.Environ(), buildCmd.Env = append(os.Environ(),
"GOPATH=" + gopath, "GOPATH="+gopath,
) )
} }
utils.CmdInit(buildCmd, !c.Vendored, c.AppPath) utils.CmdInit(buildCmd, !c.Vendored, c.AppPath)
@@ -232,6 +212,13 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err
// 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.FindAllStringSubmatch(stOutput, -1) matches := importErrorPattern.FindAllStringSubmatch(stOutput, -1)
if matches == nil {
matches = importErrorPattern2.FindAllStringSubmatch(stOutput, -1)
}
if matches == nil {
matches = addPackagePattern.FindAllStringSubmatch(stOutput, -1)
}
utils.Logger.Info("Build failed checking for missing imports", "message", stOutput, "missing_imports", len(matches)) utils.Logger.Info("Build failed checking for missing imports", "message", stOutput, "missing_imports", len(matches))
if matches == nil { if matches == nil {
utils.Logger.Info("Build failed no missing imports", "message", stOutput) utils.Logger.Info("Build failed no missing imports", "message", stOutput)
@@ -248,6 +235,7 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err
} }
gotten[pkgName] = struct{}{} gotten[pkgName] = struct{}{}
if err := c.PackageResolver(pkgName); err != nil { if err := c.PackageResolver(pkgName); err != nil {
panic("failed to resolve")
utils.Logger.Error("Unable to resolve package", "package", pkgName, "error", err) utils.Logger.Error("Unable to resolve package", "package", pkgName, "error", err)
return nil, newCompileError(paths, []byte(err.Error())) return nil, newCompileError(paths, []byte(err.Error()))
} }
@@ -256,9 +244,7 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err
// 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
utils.Logger.Fatal("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
@@ -280,10 +266,9 @@ func getAppVersion(paths *model.RevelContainer) string {
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, "--work-tree=" + paths.BasePath, "describe", "--always", "--dirty") gitCmd := exec.Command(gitPath, "--git-dir="+gitDir, "--work-tree="+paths.BasePath, "describe", "--always", "--dirty")
utils.Logger.Info("Exec:", "args", gitCmd.Args) utils.Logger.Info("Exec:", "args", gitCmd.Args)
output, err := gitCmd.Output() output, err := gitCmd.Output()
if err != nil { if err != nil {
utils.Logger.Error("Cannot determine git repository version:", "error", err) utils.Logger.Error("Cannot determine git repository version:", "error", err)
return "" return ""
@@ -341,7 +326,6 @@ func cleanDir(paths *model.RevelContainer, 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(paths *model.RevelContainer, dir, filename, templateSource string, args map[string]interface{}) error { func genSource(paths *model.RevelContainer, dir, filename, templateSource string, args map[string]interface{}) error {
return utils.GenerateTemplate(filepath.Join(paths.AppPath, dir, filename), templateSource, args) return utils.GenerateTemplate(filepath.Join(paths.AppPath, dir, filename), templateSource, args)
} }
@@ -377,17 +361,16 @@ func calcImportAliases(src *model.SourceInfo) map[string]string {
return aliases return aliases
} }
// Adds an alias to the map of alias names // 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 // 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
@@ -398,7 +381,7 @@ func makePackageAlias(aliases map[string]string, pkgName string) string {
return alias return alias
} }
// Returns true if this value is in the map // 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 {
@@ -445,13 +428,12 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.SourceEr
return newPath 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 = findInPaths(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 = &utils.SourceError{ compileError = &utils.SourceError{
SourceType: "Go code", SourceType: "Go code",
Title: "Go Compilation Error", Title: "Go Compilation Error",
@@ -470,7 +452,7 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.SourceEr
fileStr, err := utils.ReadLines(absFilename) fileStr, err := utils.ReadLines(absFilename)
if err != nil { if err != nil {
compileError.MetaError = absFilename + ": " + err.Error() compileError.MetaError = absFilename + ": " + err.Error()
utils.Logger.Info("Unable to readlines " + compileError.MetaError, "error", err) utils.Logger.Info("Unable to readlines "+compileError.MetaError, "error", err)
return compileError return compileError
} }
@@ -478,7 +460,7 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.SourceEr
return compileError return compileError
} }
// RevelMainTemplate template for app/tmp/main.go // RevelMainTemplate template for app/tmp/run/run.go.
const RevelRunTemplate = `// GENERATED CODE - DO NOT EDIT const RevelRunTemplate = `// GENERATED CODE - DO NOT EDIT
// This file is the run file for Revel. // This file is the run file for Revel.
// It registers all the controllers and provides details for the Revel server engine to // It registers all the controllers and provides details for the Revel server engine to
@@ -533,6 +515,7 @@ func Register() {
} }
} }
` `
const RevelMainTemplate = `// GENERATED CODE - DO NOT EDIT const RevelMainTemplate = `// GENERATED CODE - DO NOT EDIT
// This file is the main file for Revel. // This file is the main file for Revel.
// It registers all the controllers and provides details for the Revel server engine to // It registers all the controllers and provides details for the Revel server engine to
@@ -560,7 +543,7 @@ func main() {
} }
` `
// RevelRoutesTemplate template for app/conf/routes // 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 // This file provides a way of creating URL's based on all the actions
// found in all the controllers. // found in all the controllers.

View File

@@ -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,21 +30,21 @@ import (
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"sync/atomic" "sync/atomic"
"time"
"github.com/revel/cmd/model" "github.com/revel/cmd/model"
"github.com/revel/cmd/utils" "github.com/revel/cmd/utils"
"github.com/revel/cmd/watcher" "github.com/revel/cmd/watcher"
"html/template"
"io/ioutil"
"sync"
"encoding/json"
) )
var ( var (
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.
@@ -56,6 +60,7 @@ type Harness struct {
paths *model.RevelContainer // The Revel container paths *model.RevelContainer // The Revel container
config *model.CommandConfig // The configuration config *model.CommandConfig // The configuration
runMode string // The runmode the harness is running in runMode string // The runmode the harness is running in
ranOnce bool // True app compiled once
} }
func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err error) { func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err error) {
@@ -67,6 +72,7 @@ func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err erro
if err == nil { if err == nil {
utils.Logger.Panic("Caller passed in a nil error") utils.Logger.Panic("Caller passed in a nil error")
} }
templateSet := template.New("__root__") templateSet := template.New("__root__")
seekViewOnPath := func(view string) (path string) { seekViewOnPath := func(view string) (path string) {
path = filepath.Join(h.paths.ViewsPath, "errors", view) path = filepath.Join(h.paths.ViewsPath, "errors", view)
@@ -84,26 +90,27 @@ func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err erro
} }
return return
} }
target := []string{seekViewOnPath("500.html"), seekViewOnPath("500-dev.html")} target := []string{seekViewOnPath("500.html"), seekViewOnPath("500-dev.html")}
if !utils.Exists(target[0]) { if !utils.Exists(target[0]) {
fmt.Fprintf(iw, "Target template not found not found %s<br />\n", target[0]) fmt.Fprintf(iw, "Target template not found not found %s<br />\n", target[0])
fmt.Fprintf(iw, "An error ocurred %s", err.Error()) fmt.Fprintf(iw, "An error occurred %s", err.Error())
return return
} }
var revelError *utils.SourceError var revelError *utils.SourceError
switch e := err.(type) {
case *utils.SourceError: if !errors.As(err, &revelError) {
revelError = e
case error:
revelError = &utils.SourceError{ revelError = &utils.SourceError{
Title: "Server Error", Title: "Server Error",
Description: e.Error(), Description: err.Error(),
} }
} }
if revelError == nil { if revelError == nil {
panic("no error provided") panic("no error provided")
} }
viewArgs := map[string]interface{}{} viewArgs := map[string]interface{}{}
viewArgs["RunMode"] = h.paths.RunMode viewArgs["RunMode"] = h.paths.RunMode
viewArgs["DevMode"] = h.paths.DevMode viewArgs["DevMode"] = h.paths.DevMode
@@ -153,11 +160,11 @@ func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func NewHarness(c *model.CommandConfig, paths *model.RevelContainer, runMode string, noProxy bool) *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.RevelLog.Error("Template loader error", "error", err) // revel.RevelLog.Error("Template loader error", "error", err)
//} // }
addr := paths.HTTPAddr addr := paths.HTTPAddr
port := paths.Config.IntDefault("harness.port", 0) port := paths.Config.IntDefault("harness.port", 0)
@@ -200,8 +207,23 @@ func NewHarness(c *model.CommandConfig, paths *model.RevelContainer, runMode str
} }
// 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.
// called by the watcher // called by the watcher.
func (h *Harness) Refresh() (err *utils.SourceError) { 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 // Allow only one thread to rebuild the process
// If multiple requests to rebuild are queued only the last one is executed on // 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 // So before a build is started we wait for a second to determine if
@@ -219,33 +241,42 @@ func (h *Harness) Refresh() (err *utils.SourceError) {
h.app, newErr = Build(h.config, h.paths) h.app, newErr = Build(h.config, h.paths)
if newErr != nil { if newErr != nil {
utils.Logger.Error("Build detected an error", "error", newErr) utils.Logger.Error("Build detected an error", "error", newErr)
if castErr, ok := newErr.(*utils.SourceError); ok {
var castErr *utils.SourceError
if errors.As(newErr, &castErr) {
return castErr return castErr
} }
err = &utils.SourceError{ err = &utils.SourceError{
Title: "App failed to start up", Title: "App failed to start up",
Description: err.Error(), Description: newErr.Error(),
} }
return return
} }
if h.useProxy { if h.useProxy {
h.app.Port = h.port h.app.Port = h.port
runMode := h.runMode runMode := h.runMode
if !h.config.HistoricMode { if !h.config.HistoricMode {
// Recalulate run mode based on the config // Recalulate run mode based on the config
var paths []byte var paths []byte
if len(h.app.PackagePathMap)>0 { if len(h.app.PackagePathMap) > 0 {
paths, _ = json.Marshal(h.app.PackagePathMap) paths, _ = json.Marshal(h.app.PackagePathMap)
} }
runMode = fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, h.app.Paths.RunMode, h.config.Verbose, string(paths))
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 { if err2 := h.app.Cmd(runMode).Start(h.config); err2 != nil {
utils.Logger.Error("Could not start application", "error", err2) utils.Logger.Error("Could not start application", "error", err2)
if err,k :=err2.(*utils.SourceError);k {
var serr *utils.SourceError
if errors.As(err2, &serr) {
return err return err
} }
return &utils.SourceError{ return &utils.SourceError{
Title: "App failed to start up", Title: "App failed to start up",
Description: err2.Error(), Description: err2.Error(),
@@ -259,13 +290,13 @@ func (h *Harness) Refresh() (err *utils.SourceError) {
} }
// 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 !utils.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 - implements revel.DiscerningListener // 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")
} }
@@ -281,7 +312,12 @@ func (h *Harness) Run() {
paths = append(paths, h.paths.CodePaths...) paths = append(paths, h.paths.CodePaths...)
h.watcher = watcher.NewWatcher(h.paths, false) h.watcher = watcher.NewWatcher(h.paths, false)
h.watcher.Listen(h, paths...) h.watcher.Listen(h, paths...)
h.watcher.Notify()
go func() {
if err := h.Refresh(); err != nil {
utils.Logger.Error("Failed to refresh", "error", err)
}
}()
if h.useProxy { if h.useProxy {
go func() { go func() {
@@ -291,7 +327,6 @@ func (h *Harness) Run() {
} }
addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort) addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort)
utils.Logger.Infof("Proxy server is listening on %s", addr) utils.Logger.Infof("Proxy server is listening on %s", addr)
var err error var err error
if h.paths.HTTPSsl { if h.paths.HTTPSsl {
err = http.ListenAndServeTLS( err = http.ListenAndServeTLS(
@@ -306,19 +341,21 @@ func (h *Harness) Run() {
utils.Logger.Error("Failed to start reverse proxy:", "error", err) utils.Logger.Error("Failed to start reverse proxy:", "error", err)
} }
}() }()
} }
// Kill the app on signal.
// 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 {

View File

@@ -1,10 +1,11 @@
package logger package logger
import ( import (
"github.com/mattn/go-colorable"
"gopkg.in/natefinch/lumberjack.v2"
"io" "io"
"os" "os"
"github.com/mattn/go-colorable"
"gopkg.in/natefinch/lumberjack.v2"
) )
type CompositeMultiHandler struct { type CompositeMultiHandler struct {
@@ -19,8 +20,8 @@ func NewCompositeMultiHandler() (*CompositeMultiHandler, LogHandler) {
cw := &CompositeMultiHandler{} cw := &CompositeMultiHandler{}
return cw, cw return cw, cw
} }
func (h *CompositeMultiHandler) Log(r *Record) (err error) {
func (h *CompositeMultiHandler) Log(r *Record) (err error) {
var handler LogHandler var handler LogHandler
switch r.Level { switch r.Level {
@@ -38,8 +39,11 @@ func (h *CompositeMultiHandler) Log(r *Record) (err error) {
// Embed the caller function in the context // Embed the caller function in the context
if handler != nil { if handler != nil {
handler.Log(r) if err := handler.Log(r); err != nil {
panic(err)
}
} }
return return
} }
@@ -78,7 +82,7 @@ func (h *CompositeMultiHandler) SetHandler(handler LogHandler, replace bool, lev
} }
} }
// For the multi handler set the handler, using the LogOptions defined // For the multi handler set the handler, using the LogOptions defined.
func (h *CompositeMultiHandler) SetHandlers(handler LogHandler, options *LogOptions) { func (h *CompositeMultiHandler) SetHandlers(handler LogHandler, options *LogOptions) {
if len(options.Levels) == 0 { if len(options.Levels) == 0 {
options.Levels = LvlAllList options.Levels = LvlAllList
@@ -88,10 +92,10 @@ func (h *CompositeMultiHandler) SetHandlers(handler LogHandler, options *LogOpti
for _, lvl := range options.Levels { for _, lvl := range options.Levels {
h.SetHandler(handler, options.ReplaceExistingHandler, lvl) h.SetHandler(handler, options.ReplaceExistingHandler, lvl)
} }
} }
func (h *CompositeMultiHandler) SetJson(writer io.Writer, options *LogOptions) {
handler := CallerFileHandler(StreamHandler(writer, JsonFormatEx( func (h *CompositeMultiHandler) SetJSON(writer io.Writer, options *LogOptions) {
handler := CallerFileHandler(StreamHandler(writer, JSONFormatEx(
options.GetBoolDefault("pretty", false), options.GetBoolDefault("pretty", false),
options.GetBoolDefault("lineSeparated", true), options.GetBoolDefault("lineSeparated", true),
))) )))
@@ -101,16 +105,16 @@ func (h *CompositeMultiHandler) SetJson(writer io.Writer, options *LogOptions) {
h.SetHandlers(handler, options) h.SetHandlers(handler, options)
} }
// Use built in rolling function // Use built in rolling function.
func (h *CompositeMultiHandler) SetJsonFile(filePath string, options *LogOptions) { func (h *CompositeMultiHandler) SetJSONFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{ writer := &lumberjack.Logger{
Filename: filePath, Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days MaxAge: options.GetIntDefault("maxAgeDays", 7), // days
MaxBackups: options.GetIntDefault("maxBackups", 7), MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true), Compress: options.GetBoolDefault("compress", true),
} }
h.SetJson(writer, options) h.SetJSON(writer, options)
} }
func (h *CompositeMultiHandler) SetTerminal(writer io.Writer, options *LogOptions) { func (h *CompositeMultiHandler) SetTerminal(writer io.Writer, options *LogOptions) {
@@ -141,12 +145,12 @@ func (h *CompositeMultiHandler) SetTerminal(writer io.Writer, options *LogOption
h.SetHandlers(handler, options) h.SetHandlers(handler, options)
} }
// Use built in rolling function // Use built in rolling function.
func (h *CompositeMultiHandler) SetTerminalFile(filePath string, options *LogOptions) { func (h *CompositeMultiHandler) SetTerminalFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{ writer := &lumberjack.Logger{
Filename: filePath, Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days MaxAge: options.GetIntDefault("maxAgeDays", 7), // days
MaxBackups: options.GetIntDefault("maxBackups", 7), MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true), Compress: options.GetBoolDefault("compress", true),
} }

View File

@@ -1,15 +1,12 @@
/* /*
Package logger contains filters and handles for the logging utilities in Revel. Package logger contains filters and handles for the logging utilities in Revel.
These facilities all currently use the logging library called log15 at These facilities all currently use the logging library called log15 at
https://github.com/inconshreveable/log15 https://github.com/inconshreveable/log15
Defining handlers happens as follows Defining handlers happens as follows
1) ALL handlers (log.all.output) replace any existing handlers 1) ALL handlers (log.all.output) replace any existing handlers
2) Output handlers (log.error.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, 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 note log.all.filter is treated as a filter handler, so it will NOT replace existing ones
*/ */
package logger package logger

View File

@@ -11,12 +11,12 @@ type LevelFilterHandler struct {
} }
// Filters out records which do not match the level // Filters out records which do not match the level
// Uses the `log15.FilterHandler` to perform this task // Uses the `log15.FilterHandler` to perform this task.
func LevelHandler(lvl LogLevel, h LogHandler) LogHandler { func LevelHandler(lvl LogLevel, h LogHandler) LogHandler {
return &LevelFilterHandler{lvl, h} return &LevelFilterHandler{lvl, h}
} }
// The implementation of the Log // The implementation of the Log.
func (h LevelFilterHandler) Log(r *Record) error { func (h LevelFilterHandler) Log(r *Record) error {
if r.Level == h.Level { if r.Level == h.Level {
return h.h.Log(r) return h.h.Log(r)
@@ -25,7 +25,7 @@ func (h LevelFilterHandler) Log(r *Record) error {
} }
// Filters out records which do not match the level // Filters out records which do not match the level
// Uses the `log15.FilterHandler` to perform this task // Uses the `log15.FilterHandler` to perform this task.
func MinLevelHandler(lvl LogLevel, h LogHandler) LogHandler { func MinLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
return FilterHandler(func(r *Record) (pass bool) { return FilterHandler(func(r *Record) (pass bool) {
return r.Level <= lvl return r.Level <= lvl
@@ -33,7 +33,7 @@ func MinLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
} }
// Filters out records which match the level // Filters out records which match the level
// Uses the `log15.FilterHandler` to perform this task // Uses the `log15.FilterHandler` to perform this task.
func NotLevelHandler(lvl LogLevel, h LogHandler) LogHandler { func NotLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
return FilterHandler(func(r *Record) (pass bool) { return FilterHandler(func(r *Record) (pass bool) {
return r.Level != lvl return r.Level != lvl
@@ -48,13 +48,14 @@ func CallerFileHandler(h LogHandler) LogHandler {
} }
// Adds in a context called `caller` to the record (contains file name and line number like `foo.go:12`) // 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 // Uses the `log15.CallerFuncHandler` to perform this task.
func CallerFuncHandler(h LogHandler) LogHandler { func CallerFuncHandler(h LogHandler) LogHandler {
// TODO: infinite recursion
return CallerFuncHandler(h) return CallerFuncHandler(h)
} }
// Filters out records which match the key value pair // Filters out records which match the key value pair
// Uses the `log15.MatchFilterHandler` to perform this task // Uses the `log15.MatchFilterHandler` to perform this task.
func MatchHandler(key string, value interface{}, h LogHandler) LogHandler { func MatchHandler(key string, value interface{}, h LogHandler) LogHandler {
return MatchFilterHandler(key, value, h) return MatchFilterHandler(key, value, h)
} }
@@ -72,7 +73,7 @@ func MatchFilterHandler(key string, value interface{}, h LogHandler) LogHandler
}, h) }, h)
} }
// If match then A handler is called otherwise B handler is called // If match then A handler is called otherwise B handler is called.
func MatchAbHandler(key string, value interface{}, a, b LogHandler) LogHandler { func MatchAbHandler(key string, value interface{}, a, b LogHandler) LogHandler {
return FuncHandler(func(r *Record) error { return FuncHandler(func(r *Record) error {
if r.Context[key] == value { if r.Context[key] == value {
@@ -85,24 +86,24 @@ func MatchAbHandler(key string, value interface{}, a, b LogHandler) LogHandler {
}) })
} }
// The nil handler is used if logging for a specific request needs to be turned off // The nil handler is used if logging for a specific request needs to be turned off.
func NilHandler() LogHandler { func NilHandler() LogHandler {
return FuncHandler(func(r *Record) error { return FuncHandler(func(r *Record) error {
return nil return nil
}) })
} }
// Match all values in map to log // Match all values in map to log.
func MatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler { func MatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler {
return matchMapHandler(matchMap, false, a) return matchMapHandler(matchMap, false, a)
} }
// Match !(Match all values in map to log) The inverse of MatchMapHandler // Match !(Match all values in map to log) The inverse of MatchMapHandler.
func NotMatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler { func NotMatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler {
return matchMapHandler(matchMap, true, a) return matchMapHandler(matchMap, true, a)
} }
// Rather then chaining multiple filter handlers, process all here // Rather then chaining multiple filter handlers, process all here.
func matchMapHandler(matchMap map[string]interface{}, inverse bool, a LogHandler) LogHandler { func matchMapHandler(matchMap map[string]interface{}, inverse bool, a LogHandler) LogHandler {
return FuncHandler(func(r *Record) error { return FuncHandler(func(r *Record) error {
matchCount := 0 matchCount := 0
@@ -114,10 +115,11 @@ func matchMapHandler(matchMap map[string]interface{}, inverse bool, a LogHandler
// Test for two failure cases // Test for two failure cases
if value == v && inverse || value != v && !inverse { if value == v && inverse || value != v && !inverse {
return nil return nil
} else {
matchCount++
} }
matchCount++
} }
if matchCount != len(matchMap) { if matchCount != len(matchMap) {
return nil return nil
} }
@@ -126,7 +128,7 @@ func matchMapHandler(matchMap map[string]interface{}, inverse bool, a LogHandler
} }
// Filters out records which do not match the key value pair // Filters out records which do not match the key value pair
// Uses the `log15.FilterHandler` to perform this task // Uses the `log15.FilterHandler` to perform this task.
func NotMatchHandler(key string, value interface{}, h LogHandler) LogHandler { func NotMatchHandler(key string, value interface{}, h LogHandler) LogHandler {
return FilterHandler(func(r *Record) (pass bool) { return FilterHandler(func(r *Record) (pass bool) {
return r.Context[key] != value return r.Context[key] != value
@@ -136,8 +138,9 @@ func NotMatchHandler(key string, value interface{}, h LogHandler) LogHandler {
func MultiHandler(hs ...LogHandler) LogHandler { func MultiHandler(hs ...LogHandler) LogHandler {
return FuncHandler(func(r *Record) error { return FuncHandler(func(r *Record) error {
for _, h := range hs { for _, h := range hs {
// what to do about failures? if err := h.Log(r); err != nil {
h.Log(r) panic(err)
}
} }
return nil return nil
}) })
@@ -158,7 +161,7 @@ func StreamHandler(wr io.Writer, fmtr LogFormat) LogHandler {
return LazyHandler(SyncHandler(h)) return LazyHandler(SyncHandler(h))
} }
// Filter handler // Filter handler.
func FilterHandler(fn func(r *Record) bool, h LogHandler) LogHandler { func FilterHandler(fn func(r *Record) bool, h LogHandler) LogHandler {
return FuncHandler(func(r *Record) error { return FuncHandler(func(r *Record) error {
if fn(r) { if fn(r) {
@@ -168,18 +171,18 @@ func FilterHandler(fn func(r *Record) bool, h LogHandler) LogHandler {
}) })
} }
// List log handler handles a list of LogHandlers // List log handler handles a list of LogHandlers.
type ListLogHandler struct { type ListLogHandler struct {
handlers []LogHandler handlers []LogHandler
} }
// Create a new list of log handlers // Create a new list of log handlers.
func NewListLogHandler(h1, h2 LogHandler) *ListLogHandler { func NewListLogHandler(h1, h2 LogHandler) *ListLogHandler {
ll := &ListLogHandler{handlers: []LogHandler{h1, h2}} ll := &ListLogHandler{handlers: []LogHandler{h1, h2}}
return ll return ll
} }
// Log the record // Log the record.
func (ll *ListLogHandler) Log(r *Record) (err error) { func (ll *ListLogHandler) Log(r *Record) (err error) {
for _, handler := range ll.handlers { for _, handler := range ll.handlers {
if err == nil { if err == nil {
@@ -188,17 +191,18 @@ func (ll *ListLogHandler) Log(r *Record) (err error) {
handler.Log(r) handler.Log(r)
} }
} }
return return
} }
// Add another log handler // Add another log handler.
func (ll *ListLogHandler) Add(h LogHandler) { func (ll *ListLogHandler) Add(h LogHandler) {
if h != nil { if h != nil {
ll.handlers = append(ll.handlers, h) ll.handlers = append(ll.handlers, h)
} }
} }
// Remove a log handler // Remove a log handler.
func (ll *ListLogHandler) Del(h LogHandler) { func (ll *ListLogHandler) Del(h LogHandler) {
if h != nil { if h != nil {
for i, handler := range ll.handlers { for i, handler := range ll.handlers {

View File

@@ -1,18 +1,19 @@
package logger package logger
// Get all handlers based on the Config (if available) // Get all handlers based on the Config (if available).
import ( import (
"fmt" "fmt"
"github.com/revel/config"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/revel/config"
) )
func InitializeFromConfig(basePath string, config *config.Context) (c *CompositeMultiHandler) { func InitializeFromConfig(basePath string, config *config.Context) (c *CompositeMultiHandler) {
// If running in test mode suppress anything that is not an error // If running in test mode suppress anything that is not an error
if config != nil && config.BoolDefault(TEST_MODE_FLAG, false) { if config != nil && config.BoolDefault(TestModeFlag, false) {
// Preconfigure all the options // Preconfigure all the options
config.SetOption("log.info.output", "none") config.SetOption("log.info.output", "none")
config.SetOption("log.debug.output", "none") config.SetOption("log.debug.output", "none")
@@ -25,14 +26,14 @@ func InitializeFromConfig(basePath string, config *config.Context) (c *Composite
c, _ = NewCompositeMultiHandler() c, _ = NewCompositeMultiHandler()
// Filters are assigned first, non filtered items override filters // Filters are assigned first, non filtered items override filters
if config != nil && !config.BoolDefault(TEST_MODE_FLAG, false) { if config != nil && !config.BoolDefault(TestModeFlag, false) {
initAllLog(c, basePath, config) initAllLog(c, basePath, config)
} }
initLogLevels(c, basePath, config) initLogLevels(c, basePath, config)
if c.CriticalHandler == nil && c.ErrorHandler != nil { if c.CriticalHandler == nil && c.ErrorHandler != nil {
c.CriticalHandler = c.ErrorHandler c.CriticalHandler = c.ErrorHandler
} }
if config != nil && !config.BoolDefault(TEST_MODE_FLAG, false) { if config != nil && !config.BoolDefault(TestModeFlag, false) {
initFilterLog(c, basePath, config) initFilterLog(c, basePath, config)
if c.CriticalHandler == nil && c.ErrorHandler != nil { if c.CriticalHandler == nil && c.ErrorHandler != nil {
c.CriticalHandler = c.ErrorHandler c.CriticalHandler = c.ErrorHandler
@@ -43,10 +44,10 @@ func InitializeFromConfig(basePath string, config *config.Context) (c *Composite
return c return c
} }
// Init the log.all configuration options // Init the log.all configuration options.
func initAllLog(c *CompositeMultiHandler, basePath string, config *config.Context) { func initAllLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
if config != nil { if config != nil {
extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false) extraLogFlag := config.BoolDefault(SpecialUseFlag, false)
if output, found := config.String("log.all.output"); found { if output, found := config.String("log.all.output"); found {
// Set all output for the specified handler // Set all output for the specified handler
if extraLogFlag { if extraLogFlag {
@@ -61,13 +62,13 @@ func initAllLog(c *CompositeMultiHandler, basePath string, config *config.Contex
// log.all.filter .... // log.all.filter ....
// log.error.filter .... // log.error.filter ....
func initFilterLog(c *CompositeMultiHandler, basePath string, config *config.Context) { func initFilterLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
if config != nil { if config != nil {
extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false) extraLogFlag := config.BoolDefault(SpecialUseFlag, false)
for _, logFilter := range logFilterList { for _, logFilter := range logFilterList {
// Init for all filters // Init for all filters
for _, name := range []string{"all", "debug", "info", "warn", "error", "crit", for _, name := range []string{
"all", "debug", "info", "warn", "error", "crit",
"trace", // TODO trace is deprecated "trace", // TODO trace is deprecated
} { } {
optionList := config.Options(logFilter.LogPrefix + name + logFilter.LogSuffix) optionList := config.Options(logFilter.LogPrefix + name + logFilter.LogSuffix)
@@ -94,13 +95,14 @@ func initFilterLog(c *CompositeMultiHandler, basePath string, config *config.Con
} }
} }
// Init the log.error, log.warn etc configuration options // Init the log.error, log.warn etc configuration options.
func initLogLevels(c *CompositeMultiHandler, basePath string, config *config.Context) { func initLogLevels(c *CompositeMultiHandler, basePath string, config *config.Context) {
for _, name := range []string{"debug", "info", "warn", "error", "crit", for _, name := range []string{
"debug", "info", "warn", "error", "crit",
"trace", // TODO trace is deprecated "trace", // TODO trace is deprecated
} { } {
if config != nil { if config != nil {
extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false) extraLogFlag := config.BoolDefault(SpecialUseFlag, false)
output, found := config.String("log." + name + ".output") output, found := config.String("log." + name + ".output")
if found { if found {
if extraLogFlag { if extraLogFlag {
@@ -115,7 +117,7 @@ func initLogLevels(c *CompositeMultiHandler, basePath string, config *config.Con
} }
} }
// Init the request log options // Init the request log options.
func initRequestLog(c *CompositeMultiHandler, basePath string, config *config.Context) { func initRequestLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
// Request logging to a separate output handler // Request logging to a separate output handler
// This takes the InfoHandlers and adds a MatchAbHandler handler to it to direct // This takes the InfoHandlers and adds a MatchAbHandler handler to it to direct
@@ -143,7 +145,7 @@ func initRequestLog(c *CompositeMultiHandler, basePath string, config *config.Co
// Returns a handler for the level using the output string // Returns a handler for the level using the output string
// Accept formats for output string are // Accept formats for output string are
// LogFunctionMap[value] callback function // LogFunctionMap[value] callback function
// `stdout` `stderr` `full/file/path/to/location/app.log` `full/file/path/to/location/app.json` // `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) { func initHandlerFor(c *CompositeMultiHandler, output, basePath string, options *LogOptions) {
if options.Ctx != nil { if options.Ctx != nil {
options.SetExtendedOptions( options.SetExtendedOptions(
@@ -176,7 +178,7 @@ func initHandlerFor(c *CompositeMultiHandler, output, basePath string, options *
} }
if strings.HasSuffix(output, "json") { if strings.HasSuffix(output, "json") {
c.SetJsonFile(output, options) c.SetJSONFile(output, options)
} else { } else {
// Override defaults for a terminal file // Override defaults for a terminal file
options.SetExtendedOptions("noColor", true) options.SetExtendedOptions("noColor", true)
@@ -185,5 +187,4 @@ func initHandlerFor(c *CompositeMultiHandler, output, basePath string, options *
} }
} }
} }
return
} }

View File

@@ -5,197 +5,265 @@
package logger_test package logger_test
import ( import (
"github.com/revel/config"
"github.com/revel/revel/logger"
"github.com/stretchr/testify/assert"
"os" "os"
"strings" "strings"
"testing" "testing"
"github.com/revel/cmd/logger"
"github.com/revel/config"
"github.com/stretchr/testify/assert"
) )
type ( type (
// A counter for the tester // A counter for the tester.
testCounter struct { testCounter struct {
debug, info, warn, error, critical int debug, info, warn, error, critical int
} }
// The data to tes // The data to tes.
testData struct { testData struct {
config []string config []string
result testResult result testResult
tc *testCounter tc *testCounter
} }
// The test result // The test result.
testResult struct { testResult struct {
debug, info, warn, error, critical int debug, info, warn, error, critical int
} }
) )
// Single test cases // Single test cases.
var singleCases = []testData{ var singleCases = []testData{
{config: []string{"log.crit.output"}, {
result: testResult{0, 0, 0, 0, 1}}, config: []string{"log.crit.output"},
{config: []string{"log.error.output"}, result: testResult{0, 0, 0, 0, 1},
result: testResult{0, 0, 0, 1, 1}}, },
{config: []string{"log.warn.output"}, {
result: testResult{0, 0, 1, 0, 0}}, config: []string{"log.error.output"},
{config: []string{"log.info.output"}, result: testResult{0, 0, 0, 1, 1},
result: testResult{0, 1, 0, 0, 0}}, },
{config: []string{"log.debug.output"}, {
result: testResult{1, 0, 0, 0, 0}}, 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 // Test singles.
func TestSingleCases(t *testing.T) { func TestSingleCases(t *testing.T) {
rootLog := logger.New() rootLog := logger.New()
for _, testCase := range singleCases { for _, testCase := range singleCases {
testCase.logTest(rootLog, t) testCase.logTest(t, rootLog)
testCase.validate(t) testCase.validate(t)
} }
} }
// Filter test cases // Filter test cases.
var filterCases = []testData{ var filterCases = []testData{
{config: []string{"log.crit.filter.module.app"}, {
result: testResult{0, 0, 0, 0, 1}}, config: []string{"log.crit.filter.module.app"},
{config: []string{"log.crit.filter.module.appa"}, result: testResult{0, 0, 0, 0, 1},
result: testResult{0, 0, 0, 0, 0}}, },
{config: []string{"log.error.filter.module.app"}, {
result: testResult{0, 0, 0, 1, 1}}, config: []string{"log.crit.filter.module.appa"},
{config: []string{"log.error.filter.module.appa"}, result: testResult{0, 0, 0, 0, 0},
result: testResult{0, 0, 0, 0, 0}}, },
{config: []string{"log.warn.filter.module.app"}, {
result: testResult{0, 0, 1, 0, 0}}, config: []string{"log.error.filter.module.app"},
{config: []string{"log.warn.filter.module.appa"}, result: testResult{0, 0, 0, 1, 1},
result: testResult{0, 0, 0, 0, 0}}, },
{config: []string{"log.info.filter.module.app"}, {
result: testResult{0, 1, 0, 0, 0}}, config: []string{"log.error.filter.module.appa"},
{config: []string{"log.info.filter.module.appa"}, result: testResult{0, 0, 0, 0, 0},
result: testResult{0, 0, 0, 0, 0}}, },
{config: []string{"log.debug.filter.module.app"}, {
result: testResult{1, 0, 0, 0, 0}}, config: []string{"log.warn.filter.module.app"},
{config: []string{"log.debug.filter.module.appa"}, result: testResult{0, 0, 1, 0, 0},
result: testResult{0, 0, 0, 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 // Filter test.
func TestFilterCases(t *testing.T) { func TestFilterCases(t *testing.T) {
rootLog := logger.New("module", "app") rootLog := logger.New("module", "app")
for _, testCase := range filterCases { for _, testCase := range filterCases {
testCase.logTest(rootLog, t) testCase.logTest(t, rootLog)
testCase.validate(t) testCase.validate(t)
} }
} }
// Inverse test cases // Inverse test cases.
var nfilterCases = []testData{ var nfilterCases = []testData{
{config: []string{"log.crit.nfilter.module.appa"}, {
result: testResult{0, 0, 0, 0, 1}}, config: []string{"log.crit.nfilter.module.appa"},
{config: []string{"log.crit.nfilter.modules.appa"}, result: testResult{0, 0, 0, 0, 1},
result: testResult{0, 0, 0, 0, 0}}, },
{config: []string{"log.crit.nfilter.module.app"}, {
result: testResult{0, 0, 0, 0, 0}}, config: []string{"log.crit.nfilter.modules.appa"},
{config: []string{"log.error.nfilter.module.appa"}, // Special case, when error is not nill critical inherits from error result: testResult{0, 0, 0, 0, 0},
result: testResult{0, 0, 0, 1, 1}}, },
{config: []string{"log.error.nfilter.module.app"}, {
result: testResult{0, 0, 0, 0, 0}}, config: []string{"log.crit.nfilter.module.app"},
{config: []string{"log.warn.nfilter.module.appa"}, result: testResult{0, 0, 0, 0, 0},
result: testResult{0, 0, 1, 0, 0}}, },
{config: []string{"log.warn.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
{config: []string{"log.info.nfilter.module.appa"}, result: testResult{0, 0, 0, 1, 1},
result: testResult{0, 1, 0, 0, 0}}, },
{config: []string{"log.info.nfilter.module.app"}, {
result: testResult{0, 0, 0, 0, 0}}, config: []string{"log.error.nfilter.module.app"},
{config: []string{"log.debug.nfilter.module.appa"}, result: testResult{0, 0, 0, 0, 0},
result: testResult{1, 0, 0, 0, 0}}, },
{config: []string{"log.debug.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 // Inverse test.
func TestNotFilterCases(t *testing.T) { func TestNotFilterCases(t *testing.T) {
rootLog := logger.New("module", "app") rootLog := logger.New("module", "app")
for _, testCase := range nfilterCases { for _, testCase := range nfilterCases {
testCase.logTest(rootLog, t) testCase.logTest(t, rootLog)
testCase.validate(t) testCase.validate(t)
} }
} }
// off test cases // off test cases.
var offCases = []testData{ var offCases = []testData{
{config: []string{"log.all.output", "log.error.output=off"}, {
result: testResult{1, 1, 1, 0, 1}}, config: []string{"log.all.output", "log.error.output=off"},
result: testResult{1, 1, 1, 0, 1},
},
} }
// Off test // Off test.
func TestOffCases(t *testing.T) { func TestOffCases(t *testing.T) {
rootLog := logger.New("module", "app") rootLog := logger.New("module", "app")
for _, testCase := range offCases { for _, testCase := range offCases {
testCase.logTest(rootLog, t) testCase.logTest(t, rootLog)
testCase.validate(t) testCase.validate(t)
} }
} }
// Duplicate test cases // Duplicate test cases.
var duplicateCases = []testData{ var duplicateCases = []testData{
{config: []string{"log.all.output", "log.error.output", "log.error.filter.module.app"}, {
result: testResult{1, 1, 1, 2, 1}}, config: []string{"log.all.output", "log.error.output", "log.error.filter.module.app"},
result: testResult{1, 1, 1, 2, 1},
},
} }
// test duplicate cases // test duplicate cases.
func TestDuplicateCases(t *testing.T) { func TestDuplicateCases(t *testing.T) {
rootLog := logger.New("module", "app") rootLog := logger.New("module", "app")
for _, testCase := range duplicateCases { for _, testCase := range duplicateCases {
testCase.logTest(rootLog, t) testCase.logTest(t, rootLog)
testCase.validate(t) testCase.validate(t)
} }
} }
// Contradicting cases // Contradicting cases.
var contradictCases = []testData{ 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.all.output"},
{config: []string{"log.all.output", "log.error.output=off", "log.debug.filter.module.app"}, result: testResult{1, 1, 1, 0, 1},
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.error.output=off", "log.debug.filter.module.app"},
{config: []string{"log.all.output", "log.info.output=off", "log.info.filter.module.app"}, result: testResult{2, 1, 1, 0, 1},
result: testResult{1, 1, 1, 1, 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 // Contradiction test.
func TestContradictCases(t *testing.T) { func TestContradictCases(t *testing.T) {
rootLog := logger.New("module", "app") rootLog := logger.New("module", "app")
for _, testCase := range contradictCases { for _, testCase := range contradictCases {
testCase.logTest(rootLog, t) testCase.logTest(t, rootLog)
testCase.validate(t) testCase.validate(t)
} }
} }
// All test cases // All test cases.
var allCases = []testData{ var allCases = []testData{
{config: []string{"log.all.filter.module.app"}, {
result: testResult{1, 1, 1, 1, 1}}, config: []string{"log.all.filter.module.app"},
{config: []string{"log.all.output"}, result: testResult{1, 1, 1, 1, 1},
result: testResult{2, 2, 2, 2, 2}}, },
{
config: []string{"log.all.output"},
result: testResult{2, 2, 2, 2, 2},
},
} }
// All tests // All tests.
func TestAllCases(t *testing.T) { func TestAllCases(t *testing.T) {
rootLog := logger.New("module", "app") rootLog := logger.New("module", "app")
for i, testCase := range allCases { for i, testCase := range allCases {
testCase.logTest(rootLog, t) testCase.logTest(t, rootLog)
allCases[i] = testCase allCases[i] = testCase
} }
rootLog = logger.New() rootLog = logger.New()
for i, testCase := range allCases { for i, testCase := range allCases {
testCase.logTest(rootLog, t) testCase.logTest(t, rootLog)
allCases[i] = testCase allCases[i] = testCase
} }
for _, testCase := range allCases { for _, testCase := range allCases {
testCase.validate(t) testCase.validate(t)
} }
} }
func (c *testCounter) Log(r *logger.Record) error { func (c *testCounter) Log(r *logger.Record) error {
@@ -215,7 +283,10 @@ func (c *testCounter) Log(r *logger.Record) error {
} }
return nil return nil
} }
func (td *testData) logTest(rootLog logger.MultiLogger, t *testing.T) {
func (td *testData) logTest(t *testing.T, rootLog logger.MultiLogger) {
t.Helper()
if td.tc == nil { if td.tc == nil {
td.tc = &testCounter{} td.tc = &testCounter{}
counterInit(td.tc) counterInit(td.tc)
@@ -248,6 +319,8 @@ func (td *testData) runLogTest(log logger.MultiLogger) {
} }
func (td *testData) validate(t *testing.T) { func (td *testData) validate(t *testing.T) {
t.Helper()
t.Logf("Test %#v expected %#v", td.tc, td.result) 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.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.info, td.tc.info, "Info failed "+strings.Join(td.config, " "))
@@ -256,7 +329,7 @@ func (td *testData) validate(t *testing.T) {
assert.Equal(t, td.result.critical, td.tc.critical, "Critical 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 // Add test to the function map.
func counterInit(tc *testCounter) { func counterInit(tc *testCounter) {
logger.LogFunctionMap["test"] = func(c *logger.CompositeMultiHandler, logOptions *logger.LogOptions) { logger.LogFunctionMap["test"] = func(c *logger.CompositeMultiHandler, logOptions *logger.LogOptions) {
// Output to the test log and the stdout // Output to the test log and the stdout

View File

@@ -4,8 +4,8 @@ import (
"os" "os"
) )
// The log function map can be added to, so that you can specify your own logging mechanism // LogFunctionMap can be added to, so that you can specify your own logging mechanism
// it has defaults for off, stdout, stderr // it has defaults for off, stdout, stderr.
var LogFunctionMap = map[string]func(*CompositeMultiHandler, *LogOptions){ var LogFunctionMap = map[string]func(*CompositeMultiHandler, *LogOptions){
// Do nothing - set the logger off // Do nothing - set the logger off
"off": func(c *CompositeMultiHandler, logOptions *LogOptions) { "off": func(c *CompositeMultiHandler, logOptions *LogOptions) {

View File

@@ -2,14 +2,15 @@ package logger
import ( import (
"fmt" "fmt"
"github.com/revel/config"
"time" "time"
"github.com/revel/config"
) )
// The LogHandler defines the interface to handle the log records // The LogHandler defines the interface to handle the log records.
type ( type (
// The Multilogger reduces the number of exposed defined logging variables, // The Multilogger reduces the number of exposed defined logging variables,
// and allows the output to be easily refined // and allows the output to be easily refined.
MultiLogger interface { MultiLogger interface {
// New returns a new Logger that has this logger's context plus the given context // New returns a new Logger that has this logger's context plus the given context
New(ctx ...interface{}) MultiLogger New(ctx ...interface{}) MultiLogger
@@ -63,32 +64,32 @@ type (
Panicf(msg string, params ...interface{}) Panicf(msg string, params ...interface{})
} }
// The log handler interface // The log handler interface.
LogHandler interface { LogHandler interface {
Log(*Record) error Log(*Record) error
//log15.Handler // log15.Handler
} }
// The log stack handler interface // The log stack handler interface.
LogStackHandler interface { LogStackHandler interface {
LogHandler LogHandler
GetStack() int GetStack() int
} }
// The log handler interface which has child logs // The log handler interface which has child logs.
ParentLogHandler interface { ParentLogHandler interface {
SetChild(handler LogHandler) LogHandler SetChild(handler LogHandler) LogHandler
} }
// The log format interface // The log format interface.
LogFormat interface { LogFormat interface {
Format(r *Record) []byte Format(r *Record) []byte
} }
// The log level type // The log level type.
LogLevel int LogLevel int
// Used for the callback to LogFunctionMap // Used for the callback to LogFunctionMap.
LogOptions struct { LogOptions struct {
Ctx *config.Context Ctx *config.Context
ReplaceExistingHandler bool ReplaceExistingHandler bool
@@ -97,22 +98,22 @@ type (
ExtendedOptions map[string]interface{} ExtendedOptions map[string]interface{}
} }
// The log record // The log record.
Record struct { Record struct {
Message string // The message Message string // The message
Time time.Time // The time Time time.Time // The time
Level LogLevel //The level Level LogLevel // The level
Call CallStack // The call stack if built Call CallStack // The call stack if built
Context ContextMap // The context Context ContextMap // The context
} }
// The lazy structure to implement a function to be invoked only if needed // The lazy structure to implement a function to be invoked only if needed.
Lazy struct { Lazy struct {
Fn interface{} // the function Fn interface{} // the function
} }
// Currently the only requirement for the callstack is to support the Formatter method // Currently the only requirement for the callstack is to support the Formatter method
// which stack.Call does so we use that // which stack.Call does so we use that.
CallStack interface { CallStack interface {
fmt.Formatter // Requirement fmt.Formatter // Requirement
} }
@@ -129,6 +130,7 @@ type formatFunc func(*Record) []byte
func (f formatFunc) Format(r *Record) []byte { func (f formatFunc) Format(r *Record) []byte {
return f(r) return f(r)
} }
func NewRecord(message string, level LogLevel) *Record { func NewRecord(message string, level LogLevel) *Record {
return &Record{Message: message, Context: ContextMap{}, Level: level} return &Record{Message: message, Context: ContextMap{}, Level: level}
} }
@@ -141,28 +143,28 @@ const (
LvlDebug // Debug LvlDebug // Debug
) )
// A list of all the log levels // LvlAllList is a list of all the log levels.
var LvlAllList = []LogLevel{LvlDebug, LvlInfo, LvlWarn, LvlError, LvlCrit} var LvlAllList = []LogLevel{LvlDebug, LvlInfo, LvlWarn, LvlError, LvlCrit}
// Implements the ParentLogHandler // Implements the ParentLogHandler.
type parentLogHandler struct { type parentLogHandler struct {
setChild func(handler LogHandler) LogHandler setChild func(handler LogHandler) LogHandler
} }
// Create a new parent log handler // Create a new parent log handler.
func NewParentLogHandler(callBack func(child LogHandler) LogHandler) ParentLogHandler { func NewParentLogHandler(callBack func(child LogHandler) LogHandler) ParentLogHandler {
return &parentLogHandler{callBack} return &parentLogHandler{callBack}
} }
// Sets the child of the log handler // Sets the child of the log handler.
func (p *parentLogHandler) SetChild(child LogHandler) LogHandler { func (p *parentLogHandler) SetChild(child LogHandler) LogHandler {
return p.setChild(child) return p.setChild(child)
} }
// Create a new log options // Create a new log options.
func NewLogOptions(cfg *config.Context, replaceHandler bool, phandler ParentLogHandler, lvl ...LogLevel) (logOptions *LogOptions) { func NewLogOptions(cfg *config.Context, replaceHandler bool, phandler ParentLogHandler, lvl ...LogLevel) (logOptions *LogOptions) {
logOptions = &LogOptions{ logOptions = &LogOptions{
Ctx: cfg, Ctx: cfg,
ReplaceExistingHandler: replaceHandler, ReplaceExistingHandler: replaceHandler,
HandlerWrap: phandler, HandlerWrap: phandler,
Levels: lvl, Levels: lvl,
@@ -171,14 +173,14 @@ func NewLogOptions(cfg *config.Context, replaceHandler bool, phandler ParentLogH
return return
} }
// Assumes options will be an even number and have a string, value syntax // Assumes options will be an even number and have a string, value syntax.
func (l *LogOptions) SetExtendedOptions(options ...interface{}) { func (l *LogOptions) SetExtendedOptions(options ...interface{}) {
for x := 0; x < len(options); x += 2 { for x := 0; x < len(options); x += 2 {
l.ExtendedOptions[options[x].(string)] = options[x+1] l.ExtendedOptions[options[x].(string)] = options[x+1]
} }
} }
// Gets a string option with default // Gets a string option with default.
func (l *LogOptions) GetStringDefault(option, value string) string { func (l *LogOptions) GetStringDefault(option, value string) string {
if v, found := l.ExtendedOptions[option]; found { if v, found := l.ExtendedOptions[option]; found {
return v.(string) return v.(string)
@@ -186,7 +188,7 @@ func (l *LogOptions) GetStringDefault(option, value string) string {
return value return value
} }
// Gets an int option with default // Gets an int option with default.
func (l *LogOptions) GetIntDefault(option string, value int) int { func (l *LogOptions) GetIntDefault(option string, value int) int {
if v, found := l.ExtendedOptions[option]; found { if v, found := l.ExtendedOptions[option]; found {
return v.(int) return v.(int)
@@ -194,7 +196,7 @@ func (l *LogOptions) GetIntDefault(option string, value int) int {
return value return value
} }
// Gets a boolean option with default // Gets a boolean option with default.
func (l *LogOptions) GetBoolDefault(option string, value bool) bool { func (l *LogOptions) GetBoolDefault(option string, value bool) bool {
if v, found := l.ExtendedOptions[option]; found { if v, found := l.ExtendedOptions[option]; found {
return v.(bool) return v.(bool)

View File

@@ -2,18 +2,19 @@ package logger
import ( import (
"fmt" "fmt"
"github.com/revel/log15"
"log" "log"
"os" "os"
"github.com/revel/log15"
) )
// This type implements the MultiLogger // This type implements the MultiLogger.
type RevelLogger struct { type RevelLogger struct {
log15.Logger log15.Logger
} }
// Set the systems default logger // Set the systems default logger
// Default logs will be captured and handled by revel at level info // Default logs will be captured and handled by revel at level info.
func SetDefaultLog(fromLog MultiLogger) { func SetDefaultLog(fromLog MultiLogger) {
log.SetOutput(loggerRewrite{Logger: fromLog, Level: log15.LvlInfo, hideDeprecated: true}) log.SetOutput(loggerRewrite{Logger: fromLog, Level: log15.LvlInfo, hideDeprecated: true})
// No need to show date and time, that will be logged with revel // No need to show date and time, that will be logged with revel
@@ -24,73 +25,73 @@ func (rl *RevelLogger) Debugf(msg string, param ...interface{}) {
rl.Debug(fmt.Sprintf(msg, param...)) rl.Debug(fmt.Sprintf(msg, param...))
} }
// Print a formatted info message // Print a formatted info message.
func (rl *RevelLogger) Infof(msg string, param ...interface{}) { func (rl *RevelLogger) Infof(msg string, param ...interface{}) {
rl.Info(fmt.Sprintf(msg, param...)) rl.Info(fmt.Sprintf(msg, param...))
} }
// Print a formatted warn message // Print a formatted warn message.
func (rl *RevelLogger) Warnf(msg string, param ...interface{}) { func (rl *RevelLogger) Warnf(msg string, param ...interface{}) {
rl.Warn(fmt.Sprintf(msg, param...)) rl.Warn(fmt.Sprintf(msg, param...))
} }
// Print a formatted error message // Print a formatted error message.
func (rl *RevelLogger) Errorf(msg string, param ...interface{}) { func (rl *RevelLogger) Errorf(msg string, param ...interface{}) {
rl.Error(fmt.Sprintf(msg, param...)) rl.Error(fmt.Sprintf(msg, param...))
} }
// Print a formatted critical message // Print a formatted critical message.
func (rl *RevelLogger) Critf(msg string, param ...interface{}) { func (rl *RevelLogger) Critf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...)) rl.Crit(fmt.Sprintf(msg, param...))
} }
// Print a formatted fatal message // Print a formatted fatal message.
func (rl *RevelLogger) Fatalf(msg string, param ...interface{}) { func (rl *RevelLogger) Fatalf(msg string, param ...interface{}) {
rl.Fatal(fmt.Sprintf(msg, param...)) rl.Fatal(fmt.Sprintf(msg, param...))
} }
// Print a formatted panic message // Print a formatted panic message.
func (rl *RevelLogger) Panicf(msg string, param ...interface{}) { func (rl *RevelLogger) Panicf(msg string, param ...interface{}) {
rl.Panic(fmt.Sprintf(msg, param...)) rl.Panic(fmt.Sprintf(msg, param...))
} }
// Print a critical message and call os.Exit(1) // Print a critical message and call os.Exit(1).
func (rl *RevelLogger) Fatal(msg string, ctx ...interface{}) { func (rl *RevelLogger) Fatal(msg string, ctx ...interface{}) {
rl.Crit(msg, ctx...) rl.Crit(msg, ctx...)
os.Exit(1) os.Exit(1)
} }
// Print a critical message and panic // Print a critical message and panic.
func (rl *RevelLogger) Panic(msg string, ctx ...interface{}) { func (rl *RevelLogger) Panic(msg string, ctx ...interface{}) {
rl.Crit(msg, ctx...) rl.Crit(msg, ctx...)
panic(msg) panic(msg)
} }
// Override log15 method // Override log15 method.
func (rl *RevelLogger) New(ctx ...interface{}) MultiLogger { func (rl *RevelLogger) New(ctx ...interface{}) MultiLogger {
old := &RevelLogger{Logger: rl.Logger.New(ctx...)} old := &RevelLogger{Logger: rl.Logger.New(ctx...)}
return old return old
} }
// Set the stack level to check for the caller // Set the stack level to check for the caller.
func (rl *RevelLogger) SetStackDepth(amount int) MultiLogger { func (rl *RevelLogger) SetStackDepth(amount int) MultiLogger {
rl.Logger.SetStackDepth(amount) // Ignore the logger returned rl.Logger.SetStackDepth(amount) // Ignore the logger returned
return rl return rl
} }
// Create a new logger // Create a new logger.
func New(ctx ...interface{}) MultiLogger { func New(ctx ...interface{}) MultiLogger {
r := &RevelLogger{Logger: log15.New(ctx...)} r := &RevelLogger{Logger: log15.New(ctx...)}
r.SetStackDepth(0) r.SetStackDepth(0)
return r return r
} }
// Set the handler in the Logger // Set the handler in the Logger.
func (rl *RevelLogger) SetHandler(h LogHandler) { func (rl *RevelLogger) SetHandler(h LogHandler) {
rl.Logger.SetHandler(callHandler(h.Log)) rl.Logger.SetHandler(callHandler(h.Log))
} }
// The function wrapper to implement the callback // The function wrapper to implement the callback.
type callHandler func(r *Record) error type callHandler func(r *Record) error
// Log implementation, reads the record and extracts the details from the log record // Log implementation, reads the record and extracts the details from the log record
@@ -99,7 +100,7 @@ func (c callHandler) Log(log *log15.Record) error {
ctx := log.Ctx ctx := log.Ctx
var ctxMap ContextMap var ctxMap ContextMap
if len(ctx) > 0 { if len(ctx) > 0 {
ctxMap = make(ContextMap, len(ctx) / 2) ctxMap = make(ContextMap, len(ctx)/2)
for i := 0; i < len(ctx); i += 2 { for i := 0; i < len(ctx); i += 2 {
v := ctx[i] v := ctx[i]
@@ -108,24 +109,24 @@ func (c callHandler) Log(log *log15.Record) error {
key = fmt.Sprintf("LOGGER_INVALID_KEY %v", v) key = fmt.Sprintf("LOGGER_INVALID_KEY %v", v)
} }
var value interface{} var value interface{}
if len(ctx) > i + 1 { if len(ctx) > i+1 {
value = ctx[i + 1] value = ctx[i+1]
} else { } else {
value = "LOGGER_VALUE_MISSING" value = "LOGGER_VALUE_MISSING"
} }
ctxMap[key] = value ctxMap[key] = value
} }
} else { } else {
ctxMap = make(ContextMap, 0) ctxMap = make(ContextMap)
} }
r := &Record{Message: log.Msg, Context: ctxMap, Time: log.Time, Level: LogLevel(log.Lvl), Call: CallStack(log.Call)} r := &Record{Message: log.Msg, Context: ctxMap, Time: log.Time, Level: LogLevel(log.Lvl), Call: CallStack(log.Call)}
return c(r) return c(r)
} }
// Internally used contextMap, allows conversion of map to map[string]string // Internally used contextMap, allows conversion of map to map[string]string.
type ContextMap map[string]interface{} type ContextMap map[string]interface{}
// Convert the context map to be string only values, any non string values are ignored // Convert the context map to be string only values, any non string values are ignored.
func (m ContextMap) StringMap() (newMap map[string]string) { func (m ContextMap) StringMap() (newMap map[string]string) {
if m != nil { if m != nil {
newMap = map[string]string{} newMap = map[string]string{}
@@ -137,6 +138,7 @@ func (m ContextMap) StringMap() (newMap map[string]string) {
} }
return return
} }
func (m ContextMap) Add(key string, value interface{}) { func (m ContextMap) Add(key string, value interface{}) {
m[key] = value m[key] = value
} }

View File

@@ -18,13 +18,13 @@ const (
errorKey = "REVEL_ERROR" errorKey = "REVEL_ERROR"
) )
var ( var levelString = map[LogLevel]string{
levelString = map[LogLevel]string{LvlDebug: "DEBUG", LvlDebug: "DEBUG",
LvlInfo: "INFO", LvlWarn: "WARN", LvlError: "ERROR", LvlCrit: "CRIT"} LvlInfo: "INFO", LvlWarn: "WARN", LvlError: "ERROR", LvlCrit: "CRIT",
) }
// Outputs to the terminal in a format like below // Outputs to the terminal in a format like below
// INFO 09:11:32 server-engine.go:169: Request Stats // INFO 09:11:32 server-engine.go:169: Request Stats.
func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat { func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
dateFormat := termTimeFormat dateFormat := termTimeFormat
if smallDate { if smallDate {
@@ -32,7 +32,7 @@ func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
} }
return FormatFunc(func(r *Record) []byte { return FormatFunc(func(r *Record) []byte {
// Bash coloring http://misc.flogisoft.com/bash/tip_colors_and_formatting // Bash coloring http://misc.flogisoft.com/bash/tip_colors_and_formatting
var color = 0 color := 0
switch r.Level { switch r.Level {
case LvlCrit: case LvlCrit:
// Magenta // Magenta
@@ -54,7 +54,7 @@ func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
b := &bytes.Buffer{} b := &bytes.Buffer{}
caller, _ := r.Context["caller"].(string) caller, _ := r.Context["caller"].(string)
module, _ := r.Context["module"].(string) module, _ := r.Context["module"].(string)
if noColor == false && color > 0 { if !noColor && color > 0 {
if len(module) > 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) 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 { } else {
@@ -77,7 +77,7 @@ func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
v := formatLogfmtValue(v) v := formatLogfmtValue(v)
// TODO: we should probably check that all of your key bytes aren't invalid // TODO: we should probably check that all of your key bytes aren't invalid
if noColor == false && color > 0 { if !noColor && color > 0 {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m=%s", color, k, v) fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m=%s", color, k, v)
} else { } else {
b.WriteString(k) b.WriteString(k)
@@ -92,7 +92,7 @@ func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
}) })
} }
// formatValue formats a value for serialization // formatValue formats a value for serialization.
func formatLogfmtValue(value interface{}) string { func formatLogfmtValue(value interface{}) string {
if value == nil { if value == nil {
return "nil" return "nil"
@@ -121,7 +121,7 @@ func formatLogfmtValue(value interface{}) string {
} }
} }
// Format the value in json format // Format the value in json format.
func formatShared(value interface{}) (result interface{}) { func formatShared(value interface{}) (result interface{}) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
@@ -148,12 +148,12 @@ func formatShared(value interface{}) (result interface{}) {
} }
} }
// A reusuable buffer for outputting data // A reusuable buffer for outputting data.
var stringBufPool = sync.Pool{ var stringBufPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) }, New: func() interface{} { return new(bytes.Buffer) },
} }
// Escape the string when needed // Escape the string when needed.
func escapeString(s string) string { func escapeString(s string) string {
needsQuotes := false needsQuotes := false
needsEscape := false needsEscape := false
@@ -165,7 +165,7 @@ func escapeString(s string) string {
needsEscape = true needsEscape = true
} }
} }
if needsEscape == false && needsQuotes == false { if !needsEscape && !needsQuotes {
return s return s
} }
e := stringBufPool.Get().(*bytes.Buffer) e := stringBufPool.Get().(*bytes.Buffer)
@@ -197,10 +197,10 @@ func escapeString(s string) string {
return ret return ret
} }
// JsonFormatEx formats log records as JSON objects. If pretty is true, // JSONFormatEx formats log records as JSON objects. If pretty is true,
// records will be pretty-printed. If lineSeparated is true, records // records will be pretty-printed. If lineSeparated is true, records
// will be logged with a new line between each record. // will be logged with a new line between each record.
func JsonFormatEx(pretty, lineSeparated bool) LogFormat { func JSONFormatEx(pretty, lineSeparated bool) LogFormat {
jsonMarshal := json.Marshal jsonMarshal := json.Marshal
if pretty { if pretty {
jsonMarshal = func(v interface{}) ([]byte, error) { jsonMarshal = func(v interface{}) ([]byte, error) {
@@ -215,7 +215,7 @@ func JsonFormatEx(pretty, lineSeparated bool) LogFormat {
props["lvl"] = levelString[r.Level] props["lvl"] = levelString[r.Level]
props["msg"] = r.Message props["msg"] = r.Message
for k, v := range r.Context { for k, v := range r.Context {
props[k] = formatJsonValue(v) props[k] = formatJSONValue(v)
} }
b, err := jsonMarshal(props) b, err := jsonMarshal(props)
@@ -234,7 +234,7 @@ func JsonFormatEx(pretty, lineSeparated bool) LogFormat {
}) })
} }
func formatJsonValue(value interface{}) interface{} { func formatJSONValue(value interface{}) interface{} {
value = formatShared(value) value = formatShared(value)
switch value.(type) { switch value.(type) {
case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string: case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string:

View File

@@ -1,29 +1,31 @@
package logger package logger
import ( import (
"log"
"github.com/revel/log15" "github.com/revel/log15"
"gopkg.in/stack.v0" "gopkg.in/stack.v0"
"log"
) )
// Utility package to make existing logging backwards compatible // Utility package to make existing logging backwards compatible.
var ( var (
// Convert the string to LogLevel // Convert the string to LogLevel.
toLevel = map[string]LogLevel{"debug": LogLevel(log15.LvlDebug), toLevel = map[string]LogLevel{
"info": LogLevel(log15.LvlInfo), "request": LogLevel(log15.LvlInfo), "warn": LogLevel(log15.LvlWarn), "debug": LogLevel(log15.LvlDebug),
"info": LogLevel(log15.LvlInfo), "request": LogLevel(log15.LvlInfo), "warn": LogLevel(log15.LvlWarn),
"error": LogLevel(log15.LvlError), "crit": LogLevel(log15.LvlCrit), "error": LogLevel(log15.LvlError), "crit": LogLevel(log15.LvlCrit),
"trace": LogLevel(log15.LvlDebug), // TODO trace is deprecated, replaced by debug "trace": LogLevel(log15.LvlDebug), // TODO trace is deprecated, replaced by debug
} }
) )
const ( const (
// The test mode flag overrides the default log level and shows only errors // The test mode flag overrides the default log level and shows only errors.
TEST_MODE_FLAG = "testModeFlag" TestModeFlag = "testModeFlag"
// The special use flag enables showing messages when the logger is setup // The special use flag enables showing messages when the logger is setup.
SPECIAL_USE_FLAG = "specialUseFlag" SpecialUseFlag = "specialUseFlag"
) )
// Returns the logger for the name // Returns the logger for the name.
func GetLogger(name string, logger MultiLogger) (l *log.Logger) { func GetLogger(name string, logger MultiLogger) (l *log.Logger) {
switch name { switch name {
case "trace": // TODO trace is deprecated, replaced by debug case "trace": // TODO trace is deprecated, replaced by debug
@@ -41,10 +43,9 @@ func GetLogger(name string, logger MultiLogger) (l *log.Logger) {
} }
return l return l
} }
// Used by the initFilterLog to handle the filters // Used by the initFilterLog to handle the filters.
var logFilterList = []struct { var logFilterList = []struct {
LogPrefix, LogSuffix string LogPrefix, LogSuffix string
parentHandler func(map[string]interface{}) ParentLogHandler parentHandler func(map[string]interface{}) ParentLogHandler
@@ -54,7 +55,6 @@ var logFilterList = []struct {
return NewParentLogHandler(func(child LogHandler) LogHandler { return NewParentLogHandler(func(child LogHandler) LogHandler {
return MatchMapHandler(keyMap, child) return MatchMapHandler(keyMap, child)
}) })
}, },
}, { }, {
"log.", ".nfilter", "log.", ".nfilter",
@@ -65,20 +65,20 @@ var logFilterList = []struct {
}, },
}} }}
// This structure and method will handle the old output format and log it to the new format // This structure and method will handle the old output format and log it to the new format.
type loggerRewrite struct { type loggerRewrite struct {
Logger MultiLogger Logger MultiLogger
Level log15.Lvl Level log15.Lvl
hideDeprecated bool hideDeprecated bool
} }
// The message indicating that a logger is using a deprecated log mechanism // The message indicating that a logger is using a deprecated log mechanism.
var log_deprecated = []byte("* LOG DEPRECATED * ") var logDeprecated = []byte("* LOG DEPRECATED * ")
// Implements the Write of the logger // Implements the Write of the logger.
func (lr loggerRewrite) Write(p []byte) (n int, err error) { func (lr loggerRewrite) Write(p []byte) (n int, err error) {
if !lr.hideDeprecated { if !lr.hideDeprecated {
p = append(log_deprecated, p...) p = append(logDeprecated, p...)
} }
n = len(p) n = len(p)
if len(p) > 0 && p[n-1] == '\n' { if len(p) > 0 && p[n-1] == '\n' {
@@ -104,7 +104,7 @@ func (lr loggerRewrite) Write(p []byte) (n int, err error) {
// For logging purposes the call stack can be used to record the stack trace of a bad error // 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 // simply pass it as a context field in your log statement like
// `controller.Log.Crit("This should not occur","stack",revel.NewCallStack())` // `controller.Log.Crit("This should not occur","stack",revel.NewCallStack())`.
func NewCallStack() interface{} { func NewCallStack() interface{} {
return stack.Trace() return stack.Trace()
} }

View File

@@ -9,29 +9,43 @@ import (
"time" "time"
) )
// Function handler wraps the declared function and returns the handler for it // 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 { func FuncHandler(fn func(r *Record) error) LogHandler {
return funcHandler(fn) return funcHandler(fn)
} }
// The type decleration for the function // The type declaration for the function.
type funcHandler func(r *Record) error type funcHandler func(r *Record) error
// The implementation of the Log // The implementation of the Log.
func (h funcHandler) Log(r *Record) error { func (h funcHandler) Log(r *Record) error {
return h(r) return h(r)
} }
// This function allows you to do a full declaration for the log, // This function allows you to do a full declaration for the log,
// it is recommended you use FuncHandler instead // it is recommended you use FuncHandler instead.
func HandlerFunc(log func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error) LogHandler { func HandlerFunc(log func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error) LogHandler {
return remoteHandler(log) return remoteHandler(log)
} }
// The type used for the HandlerFunc // The type used for the HandlerFunc.
type remoteHandler func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error type remoteHandler func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error
// The Log implementation // The Log implementation.
func (c remoteHandler) Log(record *Record) error { func (c remoteHandler) Log(record *Record) error {
return c(record.Message, record.Time, record.Level, record.Call, record.Context) return c(record.Message, record.Time, record.Level, record.Call, record.Context)
} }
@@ -56,11 +70,9 @@ func LazyHandler(h LogHandler) LogHandler {
return FuncHandler(func(r *Record) error { return FuncHandler(func(r *Record) error {
for k, v := range r.Context { for k, v := range r.Context {
if lz, ok := v.(Lazy); ok { if lz, ok := v.(Lazy); ok {
value, err := evaluateLazy(lz) _, err := evaluateLazy(lz)
if err != nil { if err != nil {
r.Context[errorKey] = "bad lazy " + k r.Context[errorKey] = "bad lazy " + k
} else {
v = value
} }
} }
} }
@@ -73,26 +85,27 @@ func evaluateLazy(lz Lazy) (interface{}, error) {
t := reflect.TypeOf(lz.Fn) t := reflect.TypeOf(lz.Fn)
if t.Kind() != reflect.Func { if t.Kind() != reflect.Func {
return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn) return nil, fmt.Errorf("%w %+v", ErrNotFunc, lz.Fn)
} }
if t.NumIn() > 0 { if t.NumIn() > 0 {
return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn) return nil, fmt.Errorf("%w %+v", ErrTakesArgs, lz.Fn)
} }
if t.NumOut() == 0 { if t.NumOut() == 0 {
return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn) return nil, fmt.Errorf("%w %+v", ErrNoReturn, lz.Fn)
} }
value := reflect.ValueOf(lz.Fn) value := reflect.ValueOf(lz.Fn)
results := value.Call([]reflect.Value{}) results := value.Call([]reflect.Value{})
if len(results) == 1 { if len(results) == 1 {
return results[0].Interface(), nil return results[0].Interface(), nil
} else {
values := make([]interface{}, len(results))
for i, v := range results {
values[i] = v.Interface()
}
return values, nil
} }
values := make([]interface{}, len(results))
for i, v := range results {
values[i] = v.Interface()
}
return values, nil
} }

View File

@@ -1,4 +1,5 @@
package command package command
type ( type (
Build struct { Build struct {
ImportCommand ImportCommand
@@ -6,5 +7,4 @@ type (
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` 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"` CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"`
} }
) )

View File

@@ -1,4 +1,5 @@
package command package command
type ( type (
Clean struct { Clean struct {
ImportCommand ImportCommand

View File

@@ -1,14 +1,12 @@
package command package command
type ( type (
New struct { New struct {
ImportCommand ImportCommand
SkeletonPath string `short:"s" long:"skeleton" description:"Path to skeleton folder (Must exist on GO PATH)" required:"false"` 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"` 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 `short:"V" long:"vendor" description:"True if project should not be configured with a go.mod"` 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"` Run bool `short:"r" long:"run" description:"True if you want to run the application right away"`
Callback func() error Callback func() error
} }
)
)

View File

@@ -1,4 +1,5 @@
package command package command
type ( type (
Package struct { Package struct {
ImportCommand ImportCommand
@@ -6,4 +7,4 @@ type (
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` 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"` CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"`
} }
) )

View File

@@ -1,9 +1,10 @@
package command package command
type ( type (
Run struct { Run struct {
ImportCommand ImportCommand
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` 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" ` 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"` 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"`
} }
) )

View File

@@ -3,7 +3,7 @@ package command
type ( type (
Test struct { Test struct {
ImportCommand ImportCommand
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` 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"` Function string `short:"f" long:"suite-function" description:"The suite.function"`
} }
) )

View File

@@ -1,8 +1,7 @@
package command package command
type ( type (
Version struct { Version struct {
ImportCommand ImportCommand
Update bool `short:"u" long:"update" description:"Update the framework and modules" required:"false"`
UpdateVersion string `long:"update-version" description:"Specify the version the revel and app will be switched to" required:"false"`
} }
) )

View File

@@ -2,8 +2,6 @@ package model
import ( import (
"fmt" "fmt"
"github.com/revel/cmd"
"github.com/revel/cmd/utils"
"go/ast" "go/ast"
"go/build" "go/build"
"go/parser" "go/parser"
@@ -13,10 +11,13 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/revel/cmd"
"github.com/revel/cmd/model/command" "github.com/revel/cmd/model/command"
"github.com/revel/cmd/utils"
) )
// The constants // The constants.
const ( const (
NEW COMMAND = iota + 1 NEW COMMAND = iota + 1
RUN RUN
@@ -27,39 +28,44 @@ const (
VERSION VERSION
) )
const (
ErrImportInvalid Error = "invalid import path, working dir is in GOPATH root"
ErrUnableToImport Error = "unable to determine import path from"
)
type ( type (
// The Revel command type // The Revel command type.
COMMAND int COMMAND int
// The Command config for the line input // The Command config for the line input.
CommandConfig struct { CommandConfig struct {
Index COMMAND // The index Index COMMAND // The index
Verbose []bool `short:"v" long:"debug" description:"If set the logger is set to verbose"` // True if debug is active 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 FrameworkVersion *Version // The framework version
CommandVersion *Version // The command 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 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) ImportPath string // The import path (relative to a GOPATH)
GoPath string // The GoPath GoPath string // The GoPath
GoCmd string // The full path to the go executable GoCmd string // The full path to the go executable
SrcRoot string // The source root // SrcRoot string // The source root
AppPath string // The application path (absolute) AppPath string // The application path (absolute)
AppName string // The application name 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 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 Vendored bool // True if the application is vendored
PackageResolver func(pkgName string) error // a package resolver for the config 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"` 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 execut go mod commands for each flag, this happens during the build process"` 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"` New command.New `command:"new"`
Build command.Build `command:"build"` Build command.Build `command:"build"`
Run command.Run `command:"run"` Run command.Run `command:"run"`
Package command.Package `command:"package"` Package command.Package `command:"package"`
Clean command.Clean `command:"clean"` Clean command.Clean `command:"clean"`
Test command.Test `command:"test"` Test command.Test `command:"test"`
Version command.Version `command:"version"` Version command.Version `command:"version"`
} }
) )
// Updates the import path depending on the command // Updates the import path depending on the command.
func (c *CommandConfig) UpdateImportPath() error { func (c *CommandConfig) UpdateImportPath() error {
var importPath string var importPath string
required := true required := true
@@ -102,14 +108,14 @@ func (c *CommandConfig) UpdateImportPath() error {
if err == nil { if err == nil {
for _, path := range strings.Split(build.Default.GOPATH, string(filepath.ListSeparator)) { for _, path := range strings.Split(build.Default.GOPATH, string(filepath.ListSeparator)) {
utils.Logger.Infof("Checking import path %s with %s", currentPath, path) utils.Logger.Infof("Checking import path %s with %s", currentPath, path)
if strings.HasPrefix(currentPath, path) && len(currentPath) > len(path) + 1 { if strings.HasPrefix(currentPath, path) && len(currentPath) > len(path)+1 {
importPath = currentPath[len(path) + 1:] importPath = currentPath[len(path)+1:]
// Remove the source from the path if it is there // 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\\") { if len(importPath) > 4 && (strings.ToLower(importPath[0:4]) == "src/" || strings.ToLower(importPath[0:4]) == "src\\") {
importPath = importPath[4:] importPath = importPath[4:]
} else if importPath == "src" { } else if importPath == "src" {
if c.Index != VERSION { if c.Index != VERSION {
return fmt.Errorf("Invlaid import path, working dir is in GOPATH root") return ErrImportInvalid
} }
importPath = "" importPath = ""
} }
@@ -121,7 +127,10 @@ func (c *CommandConfig) UpdateImportPath() error {
c.ImportPath = importPath c.ImportPath = importPath
// We need the source root determined at this point to check the setversions // We need the source root determined at this point to check the setversions
c.initAppFolder() if err := c.initAppFolder(); err != nil {
utils.Logger.Error("Error initing app folder", "error", err)
}
utils.Logger.Info("Returned import path", "path", importPath) utils.Logger.Info("Returned import path", "path", importPath)
if required && c.Index != NEW { if required && c.Index != NEW {
if err := c.SetVersions(); err != nil { if err := c.SetVersions(); err != nil {
@@ -137,13 +146,13 @@ func (c *CommandConfig) UpdateImportPath() error {
return nil return nil
} }
if len(importPath) == 0 { if len(importPath) == 0 {
return fmt.Errorf("Unable to determine import path from : %s", importPath) return fmt.Errorf("%w: %s", ErrUnableToImport, importPath)
} }
return nil return nil
} }
func (c *CommandConfig) initAppFolder() (err error) { func (c *CommandConfig) initAppFolder() (err error) {
utils.Logger.Info("initAppFolder", "vendored", c.Vendored) utils.Logger.Info("initAppFolder", "vendored", c.Vendored, "build-gopath", build.Default.GOPATH, "gopath-env", os.Getenv("GOPATH"))
// check for go executable // check for go executable
c.GoCmd, err = exec.LookPath("go") c.GoCmd, err = exec.LookPath("go")
@@ -153,19 +162,17 @@ func (c *CommandConfig) initAppFolder() (err error) {
// First try to determine where the application is located - this should be the import value // First try to determine where the application is located - this should be the import value
appFolder := c.ImportPath appFolder := c.ImportPath
wd, err := os.Getwd() wd, _ := os.Getwd()
if len(appFolder) == 0 { if len(appFolder) == 0 {
// We will assume the working directory is the appFolder // We will assume the working directory is the appFolder
appFolder = wd appFolder = wd
} else if strings.LastIndex(wd, appFolder) == len(wd) - len(appFolder) { } else if strings.LastIndex(wd, appFolder) == len(wd)-len(appFolder) {
// Check for existence of an /app folder // Check for existence of an /app folder
if utils.Exists(filepath.Join(wd, "app")) { if utils.Exists(filepath.Join(wd, "app")) {
appFolder = wd appFolder = wd
} else { } else {
appFolder = filepath.Join(wd, appFolder) appFolder = filepath.Join(wd, appFolder)
} }
} else if strings.Contains(appFolder, ".") {
appFolder = filepath.Join(wd, filepath.Base(c.ImportPath))
} else if !filepath.IsAbs(appFolder) { } else if !filepath.IsAbs(appFolder) {
appFolder = filepath.Join(wd, appFolder) appFolder = filepath.Join(wd, appFolder)
} }
@@ -174,8 +181,10 @@ func (c *CommandConfig) initAppFolder() (err error) {
// Use app folder to read the go.mod if it exists and extract the package information // Use app folder to read the go.mod if it exists and extract the package information
goModFile := filepath.Join(appFolder, "go.mod") goModFile := filepath.Join(appFolder, "go.mod")
utils.Logger.Info("Checking gomod, extracting from file", "path", goModFile, "exists", utils.Exists(goModFile))
if utils.Exists(goModFile) { if utils.Exists(goModFile) {
c.Vendored = true c.Vendored = true
utils.Logger.Info("Found go mod, extracting from file", "path", goModFile)
file, err := ioutil.ReadFile(goModFile) file, err := ioutil.ReadFile(goModFile)
if err != nil { if err != nil {
return err return err
@@ -184,30 +193,17 @@ func (c *CommandConfig) initAppFolder() (err error) {
if strings.Index(line, "module ") == 0 { if strings.Index(line, "module ") == 0 {
c.ImportPath = strings.TrimSpace(strings.Split(line, "module")[1]) c.ImportPath = strings.TrimSpace(strings.Split(line, "module")[1])
c.AppPath = appFolder c.AppPath = appFolder
c.SrcRoot = appFolder // c.SrcRoot = appFolder
utils.Logger.Info("Set application path and package based on go mod", "path", c.AppPath, "sourceroot", c.SrcRoot) utils.Logger.Info("Set application path and package based on go mod", "path", c.AppPath)
return nil return nil
} }
} }
} // c.SrcRoot = appFolder
c.AppPath = appFolder
utils.Logger.Debug("Trying to set path based on gopath") } else if c.Index != NEW || (c.Index == NEW && c.New.NotVendored) {
// lookup go path workingDir, _ := os.Getwd()
c.GoPath = build.Default.GOPATH goPathList := filepath.SplitList(c.GoPath)
if c.GoPath == "" { bestpath := ""
utils.Logger.Fatal("Abort: GOPATH environment variable is not set. " +
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
}
// revel/revel#1004 choose go path relative to current working directory
// What we want to do is to add the import to the end of the
// gopath, and discover which import exists - If none exist this is an error except in the case
// where we are dealing with new which is a special case where we will attempt to target the working directory first
workingDir, _ := os.Getwd()
goPathList := filepath.SplitList(c.GoPath)
bestpath := ""
if !c.Vendored {
for _, path := range goPathList { for _, path := range goPathList {
if c.Index == NEW { if c.Index == NEW {
// If the GOPATH is part of the working dir this is the most likely target // If the GOPATH is part of the working dir this is the most likely target
@@ -216,77 +212,58 @@ func (c *CommandConfig) initAppFolder() (err error) {
} }
} else { } else {
if utils.Exists(filepath.Join(path, "src", c.ImportPath)) { if utils.Exists(filepath.Join(path, "src", c.ImportPath)) {
c.SrcRoot = path bestpath = path
break break
} }
} }
} }
if len(c.SrcRoot) == 0 && len(bestpath) > 0 {
c.SrcRoot = bestpath 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 { } else {
c.SrcRoot = appFolder // This is new and not vendored, so the app path is the appFolder
c.AppPath = appFolder
} }
utils.Logger.Info("Source root", "path", c.SrcRoot, "cwd", workingDir, "gopath", c.GoPath, "bestpath", bestpath) utils.Logger.Info("Set application path", "path", c.AppPath, "vendored", c.Vendored, "importpath", c.ImportPath)
// 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 nil
}
}
// set go src path
if c.Vendored {
c.AppPath = c.SrcRoot
} else {
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)
return nil return nil
} }
// Used to initialize the package resolver // Used to initialize the package resolver.
func (c *CommandConfig) InitPackageResolver() { func (c *CommandConfig) InitPackageResolver() {
c.initGoPaths()
utils.Logger.Info("InitPackageResolver", "useVendor", c.Vendored, "path", c.AppPath) utils.Logger.Info("InitPackageResolver", "useVendor", c.Vendored, "path", c.AppPath)
// This should get called when needed // This should get called when needed
c.PackageResolver = func(pkgName string) error { c.PackageResolver = func(pkgName string) error {
//useVendor := utils.DirExists(filepath.Join(c.AppPath, "vendor"))
//var getCmd *exec.Cmd
utils.Logger.Info("Request for package ", "package", pkgName, "use vendor", c.Vendored) utils.Logger.Info("Request for package ", "package", pkgName, "use vendor", c.Vendored)
var getCmd *exec.Cmd
print("Downloading related packages ...")
if c.Vendored { if c.Vendored {
goModCmd := exec.Command("go", "mod", "tidy") getCmd = exec.Command(c.GoCmd, "mod", "tidy", "-v")
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath) } else {
goModCmd.Run() utils.Logger.Info("No vendor folder detected, not using dependency manager to import package", "package", pkgName)
return nil 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 return nil
} }
} }
// lookup and set Go related variables // lookup and set Go related variables.
func (c *CommandConfig) InitGoPathsOld() { func (c *CommandConfig) initGoPaths() {
utils.Logger.Info("InitGoPaths") utils.Logger.Info("InitGoPaths", "vendored", c.Vendored)
// lookup go path
c.GoPath = build.Default.GOPATH
if c.GoPath == "" {
utils.Logger.Fatal("Abort: GOPATH environment variable is not set. " +
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
}
// check for go executable // check for go executable
var err error var err error
c.GoCmd, err = exec.LookPath("go") c.GoCmd, err = exec.LookPath("go")
@@ -294,53 +271,53 @@ func (c *CommandConfig) InitGoPathsOld() {
utils.Logger.Fatal("Go executable not found in PATH.") 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 // 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 // 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 // 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 // where we are dealing with new which is a special case where we will attempt to target the working directory first
workingDir, _ := os.Getwd() /*
goPathList := filepath.SplitList(c.GoPath) // If source root is empty and this isn't a version then skip it
bestpath := "" if len(c.SrcRoot) == 0 {
for _, path := range goPathList { if c.Index == NEW {
if c.Index == NEW { c.SrcRoot = c.New.ImportPath
// If the GOPATH is part of the working dir this is the most likely target } else {
if strings.HasPrefix(workingDir, path) { if c.Index != VERSION {
bestpath = path utils.Logger.Fatal("Abort: could not create a Revel application outside of GOPATH.")
} }
} else { return
if utils.Exists(filepath.Join(path, "src", c.ImportPath)) {
c.SrcRoot = path
break
} }
} }
}
utils.Logger.Info("Source root", "path", c.SrcRoot, "cwd", workingDir, "gopath", c.GoPath, "bestpath", bestpath) // set go src path
if len(c.SrcRoot) == 0 && len(bestpath) > 0 { c.SrcRoot = filepath.Join(c.SrcRoot, "src")
c.SrcRoot = bestpath
}
// If source root is empty and this isn't a version then skip it c.AppPath = filepath.Join(c.SrcRoot, filepath.FromSlash(c.ImportPath))
if len(c.SrcRoot) == 0 { utils.Logger.Info("Set application path", "path", c.AppPath)
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 // 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) { func (c *CommandConfig) SetVersions() (err error) {
c.CommandVersion, _ = ParseVersion(cmd.Version) c.CommandVersion, _ = ParseVersion(cmd.Version)
pathMap, err := utils.FindSrcPaths(c.AppPath, []string{RevelImportPath}, c.PackageResolver) pathMap, err := utils.FindSrcPaths(c.AppPath, []string{RevelImportPath}, c.PackageResolver)
@@ -372,7 +349,7 @@ func (c *CommandConfig) SetVersions() (err error) {
spec := a.(*ast.ValueSpec) spec := a.(*ast.ValueSpec)
r := spec.Values[0].(*ast.BasicLit) r := spec.Values[0].(*ast.BasicLit)
if spec.Names[0].Name == "Version" { if spec.Names[0].Name == "Version" {
c.FrameworkVersion, err = ParseVersion(strings.Replace(r.Value, `"`, ``, -1)) c.FrameworkVersion, err = ParseVersion(strings.ReplaceAll(r.Value, `"`, ``))
if err != nil { if err != nil {
utils.Logger.Errorf("Failed to parse version") utils.Logger.Errorf("Failed to parse version")
} else { } else {

View File

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

View File

@@ -1,11 +1,11 @@
package model package model
type ( type (
// The event type // The event type.
Event int Event int
// The event response // The event response.
EventResponse int EventResponse int
// The handler signature // The handler signature.
EventHandler func(typeOf Event, value interface{}) (responseOf EventResponse) EventHandler func(typeOf Event, value interface{}) (responseOf EventResponse)
RevelCallback interface { RevelCallback interface {
FireEvent(key Event, value interface{}) (response EventResponse) FireEvent(key Event, value interface{}) (response EventResponse)
@@ -14,40 +14,40 @@ type (
) )
const ( const (
// Event type when templates are going to be refreshed (receivers are registered template engines added to the template.engine conf option) // 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 TEMPLATE_REFRESH_REQUESTED Event = iota
// Event type when templates are refreshed (receivers are registered template engines added to the template.engine conf option) // Event type when templates are refreshed (receivers are registered template engines added to the template.engine conf option).
TEMPLATE_REFRESH_COMPLETED 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.
// 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 REVEL_BEFORE_MODULES_LOADED
// Event type before module loads, events thrown to handlers added to AddInitEventHandler // Event type before module loads, events thrown to handlers added to AddInitEventHandler.
REVEL_BEFORE_MODULE_LOADED REVEL_BEFORE_MODULE_LOADED
// Event type after module loads, events thrown to handlers added to AddInitEventHandler // Event type after module loads, events thrown to handlers added to AddInitEventHandler.
REVEL_AFTER_MODULE_LOADED REVEL_AFTER_MODULE_LOADED
// Event type after all module loads, events thrown to handlers added to AddInitEventHandler // Event type after all module loads, events thrown to handlers added to AddInitEventHandler.
REVEL_AFTER_MODULES_LOADED REVEL_AFTER_MODULES_LOADED
// Event type before server engine is initialized, receivers are active server engine and handlers added to AddInitEventHandler // Event type before server engine is initialized, receivers are active server engine and handlers added to AddInitEventHandler.
ENGINE_BEFORE_INITIALIZED ENGINE_BEFORE_INITIALIZED
// Event type before server engine is started, receivers are active server engine and handlers added to AddInitEventHandler // Event type before server engine is started, receivers are active server engine and handlers added to AddInitEventHandler.
ENGINE_STARTED ENGINE_STARTED
// Event type after server engine is stopped, receivers are active server engine and handlers added to AddInitEventHandler // Event type after server engine is stopped, receivers are active server engine and handlers added to AddInitEventHandler.
ENGINE_SHUTDOWN ENGINE_SHUTDOWN
// Called before routes are refreshed // Called before routes are refreshed.
ROUTE_REFRESH_REQUESTED ROUTE_REFRESH_REQUESTED
// Called after routes have been refreshed // Called after routes have been refreshed.
ROUTE_REFRESH_COMPLETED ROUTE_REFRESH_COMPLETED
// Fired when a panic is caught during the startup process // Fired when a panic is caught during the startup process.
REVEL_FAILURE REVEL_FAILURE
) )
var initEventList = []EventHandler{} // Event handler list for receiving events var initEventList = []EventHandler{} // Event handler list for receiving events
// Fires system events from revel // Fires system events from revel.
func RaiseEvent(key Event, value interface{}) (response EventResponse) { func RaiseEvent(key Event, value interface{}) (response EventResponse) {
for _, handler := range initEventList { for _, handler := range initEventList {
response |= handler(key, value) response |= handler(key, value)
@@ -55,8 +55,7 @@ func RaiseEvent(key Event, value interface{}) (response EventResponse) {
return return
} }
// Add event handler to listen for all system events // Add event handler to listen for all system events.
func AddInitEventHandler(handler EventHandler) { func AddInitEventHandler(handler EventHandler) {
initEventList = append(initEventList, handler) initEventList = append(initEventList, handler)
return
} }

View File

@@ -1,12 +1,13 @@
package model_test package model_test
import ( import (
"testing"
"github.com/revel/revel" "github.com/revel/revel"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing"
) )
// Test that the event handler can be attached and it dispatches the event received // Test that the event handler can be attached and it dispatches the event received.
func TestEventHandler(t *testing.T) { func TestEventHandler(t *testing.T) {
counter := 0 counter := 0
newListener := func(typeOf revel.Event, value interface{}) (responseOf revel.EventResponse) { newListener := func(typeOf revel.Event, value interface{}) (responseOf revel.EventResponse) {
@@ -21,4 +22,3 @@ func TestEventHandler(t *testing.T) {
revel.StopServer(1) revel.StopServer(1)
assert.Equal(t, counter, 2, "Expected event handler to have been called") assert.Equal(t, counter, 2, "Expected event handler to have been called")
} }

View File

@@ -8,14 +8,14 @@ type MethodCall struct {
Names []string Names []string
} }
// MethodSpec holds the information of one Method // MethodSpec holds the information of one Method.
type MethodSpec struct { type MethodSpec struct {
Name string // Name of the method, e.g. "Index" Name string // Name of the method, e.g. "Index"
Args []*MethodArg // Argument descriptors Args []*MethodArg // Argument descriptors
RenderCalls []*MethodCall // Descriptions of Render() invocations from this Method. RenderCalls []*MethodCall // Descriptions of Render() invocations from this Method.
} }
// MethodArg holds the information of one argument // MethodArg holds the information of one argument.
type MethodArg struct { type MethodArg struct {
Name string // Name of the argument. Name string // Name of the argument.
TypeExpr TypeExpr // The name of the type, e.g. "int", "*pkg.UserType" TypeExpr TypeExpr // The name of the type, e.g. "int", "*pkg.UserType"

View File

@@ -2,18 +2,36 @@
package model package model
import ( import (
"github.com/revel/cmd/utils"
"github.com/revel/config"
"errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
"github.com/revel/cmd/utils"
"github.com/revel/config"
"golang.org/x/tools/go/packages" "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 ( type (
// The container object for describing all Revels variables // The container object for describing all Revels variables.
RevelContainer struct { RevelContainer struct {
BuildPaths struct { BuildPaths struct {
Revel string Revel string
@@ -39,36 +57,36 @@ type (
Root string Root string
} }
ImportPath string // The import path ImportPath string // The import path
SourcePath string // The full source path SourcePath string // The full source path
RunMode string // The current run mode RunMode string // The current run mode
RevelPath string // The path to the Revel source code RevelPath string // The path to the Revel source code
BasePath string // The base path to the application BasePath string // The base path to the application
AppPath string // The application path (BasePath + "/app") AppPath string // The application path (BasePath + "/app")
ViewsPath string // The application views path ViewsPath string // The application views path
CodePaths []string // All the code paths CodePaths []string // All the code paths
TemplatePaths []string // All the template paths TemplatePaths []string // All the template paths
ConfPaths []string // All the configuration paths ConfPaths []string // All the configuration paths
Config *config.Context // The global config object Config *config.Context // The global config object
Packaged bool // True if packaged Packaged bool // True if packaged
DevMode bool // True if running in dev mode DevMode bool // True if running in dev mode
HTTPPort int // The http port HTTPPort int // The http port
HTTPAddr string // The http address HTTPAddr string // The http address
HTTPSsl bool // True if running https HTTPSsl bool // True if running https
HTTPSslCert string // The SSL certificate HTTPSslCert string // The SSL certificate
HTTPSslKey string // The SSL key HTTPSslKey string // The SSL key
AppName string // The application name AppName string // The application name
AppRoot string // The application root from the config `app.root` AppRoot string // The application root from the config `app.root`
CookiePrefix string // The cookie prefix CookiePrefix string // The cookie prefix
CookieDomain string // The cookie domain CookieDomain string // The cookie domain
CookieSecure bool // True if cookie is secure CookieSecure bool // True if cookie is secure
SecretStr string // The secret string SecretStr string // The secret string
MimeConfig *config.Context // The mime configuration MimeConfig *config.Context // The mime configuration
ModulePathMap map[string]*ModuleInfo // The module path map ModulePathMap map[string]*ModuleInfo // The module path map
} }
ModuleInfo struct { ModuleInfo struct {
ImportPath string ImportPath string
Path string Path string
} }
WrappedRevelCallback struct { WrappedRevelCallback struct {
@@ -77,25 +95,28 @@ type (
} }
) )
// Simple Wrapped RevelCallback // Simple Wrapped RevelCallback.
func NewWrappedRevelCallback(fe func(key Event, value interface{}) (response EventResponse), ie func(pkgName string) error) RevelCallback { func NewWrappedRevelCallback(fe func(key Event, value interface{}) (response EventResponse), ie func(pkgName string) error) RevelCallback {
return &WrappedRevelCallback{fe, ie} return &WrappedRevelCallback{fe, ie}
} }
// Function to implement the FireEvent // Function to implement the FireEvent.
func (w *WrappedRevelCallback) FireEvent(key Event, value interface{}) (response EventResponse) { func (w *WrappedRevelCallback) FireEvent(key Event, value interface{}) (response EventResponse) {
if w.FireEventFunction != nil { if w.FireEventFunction != nil {
response = w.FireEventFunction(key, value) response = w.FireEventFunction(key, value)
} }
return return
} }
func (w *WrappedRevelCallback) PackageResolver(pkgName string) error { func (w *WrappedRevelCallback) PackageResolver(pkgName string) error {
return w.ImportFunction(pkgName) return w.ImportFunction(pkgName)
} }
// RevelImportPath Revel framework import path // RevelImportPath Revel framework import path.
var RevelImportPath = "github.com/revel/revel" var (
var RevelModulesImportPath = "github.com/revel/modules" RevelImportPath = "github.com/revel/revel"
RevelModulesImportPath = "github.com/revel/modules"
)
// This function returns a container object describing the revel application // This function returns a container object describing the revel application
// eventually this type of function will replace the global variables. // eventually this type of function will replace the global variables.
@@ -107,7 +128,7 @@ func NewRevelPaths(mode, importPath, appSrcPath string, callback RevelCallback)
rp.RunMode = mode rp.RunMode = mode
// We always need to determine the paths for files // We always need to determine the paths for files
pathMap, err := utils.FindSrcPaths(appSrcPath, []string{importPath+"/app", RevelImportPath}, callback.PackageResolver) pathMap, err := utils.FindSrcPaths(appSrcPath, []string{importPath + "/app", RevelImportPath}, callback.PackageResolver)
if err != nil { if err != nil {
return return
} }
@@ -118,11 +139,11 @@ func NewRevelPaths(mode, importPath, appSrcPath string, callback RevelCallback)
rp.AppPath = filepath.Join(rp.BasePath, "app") rp.AppPath = filepath.Join(rp.BasePath, "app")
// Sanity check , ensure app and conf paths exist // Sanity check , ensure app and conf paths exist
if !utils.DirExists(rp.AppPath) { if !utils.DirExists(rp.AppPath) {
return rp, fmt.Errorf("No application found at path %s", rp.AppPath) return rp, fmt.Errorf("%w: %s", ErrNoApp, rp.AppPath)
} }
if !utils.DirExists(filepath.Join(rp.BasePath, "conf")) { if !utils.DirExists(filepath.Join(rp.BasePath, "conf")) {
return rp, fmt.Errorf("No configuration found at path %s", 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.ViewsPath = filepath.Join(rp.AppPath, "views")
@@ -146,7 +167,7 @@ func NewRevelPaths(mode, importPath, appSrcPath string, callback RevelCallback)
rp.Config, err = config.LoadContext("app.conf", rp.ConfPaths) rp.Config, err = config.LoadContext("app.conf", rp.ConfPaths)
if err != nil { if err != nil {
return rp, fmt.Errorf("Unable to load configuartion file %s", err) return rp, fmt.Errorf("unable to load configuration file %w", err)
} }
// Ensure that the selected runmode appears in app.conf. // Ensure that the selected runmode appears in app.conf.
@@ -155,7 +176,7 @@ func NewRevelPaths(mode, importPath, appSrcPath string, callback RevelCallback)
mode = config.DefaultSection mode = config.DefaultSection
} }
if !rp.Config.HasSection(mode) { if !rp.Config.HasSection(mode) {
return rp, fmt.Errorf("app.conf: No mode found: %s %s", "run-mode", mode) return rp, fmt.Errorf("app.conf: %w %s %s", ErrNotFound, "run-mode", mode)
} }
rp.Config.SetSection(mode) rp.Config.SetSection(mode)
@@ -168,13 +189,14 @@ func NewRevelPaths(mode, importPath, appSrcPath string, callback RevelCallback)
rp.HTTPSslKey = rp.Config.StringDefault("http.sslkey", "") rp.HTTPSslKey = rp.Config.StringDefault("http.sslkey", "")
if rp.HTTPSsl { if rp.HTTPSsl {
if rp.HTTPSslCert == "" { if rp.HTTPSslCert == "" {
return rp, errors.New("No http.sslcert provided.") return rp, ErrMissingCert
} }
if rp.HTTPSslKey == "" { if rp.HTTPSslKey == "" {
return rp, errors.New("No http.sslkey provided.") return rp, ErrMissingKey
} }
} }
//
rp.AppName = rp.Config.StringDefault("app.name", "(not set)") rp.AppName = rp.Config.StringDefault("app.name", "(not set)")
rp.AppRoot = rp.Config.StringDefault("app.root", "") rp.AppRoot = rp.Config.StringDefault("app.root", "")
rp.CookiePrefix = rp.Config.StringDefault("cookie.prefix", "REVEL") rp.CookiePrefix = rp.Config.StringDefault("cookie.prefix", "REVEL")
@@ -197,7 +219,7 @@ func NewRevelPaths(mode, importPath, appSrcPath string, callback RevelCallback)
func (rp *RevelContainer) LoadMimeConfig() (err error) { func (rp *RevelContainer) LoadMimeConfig() (err error) {
rp.MimeConfig, err = config.LoadContext("mime-types.conf", rp.ConfPaths) rp.MimeConfig, err = config.LoadContext("mime-types.conf", rp.ConfPaths)
if err != nil { if err != nil {
return fmt.Errorf("Failed to load mime type config: %s %s", "error", err) return fmt.Errorf("failed to load mime type config: %s %w", "error", err)
} }
return return
} }
@@ -206,12 +228,10 @@ func (rp *RevelContainer) LoadMimeConfig() (err error) {
// This will fire the REVEL_BEFORE_MODULE_LOADED, REVEL_AFTER_MODULE_LOADED // 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 // 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 // It will automatically add in the code paths for the module to the
// container object // container object.
func (rp *RevelContainer) loadModules(callback RevelCallback) (err error) { func (rp *RevelContainer) loadModules(callback RevelCallback) (err error) {
keys := []string{} keys := []string{}
for _, key := range rp.Config.Options("module.") { keys = append(keys, rp.Config.Options("module.")...)
keys = append(keys, key)
}
// Reorder module order by key name, a poor mans sort but at least it is consistent // Reorder module order by key name, a poor mans sort but at least it is consistent
sort.Strings(keys) sort.Strings(keys)
@@ -223,11 +243,15 @@ func (rp *RevelContainer) loadModules(callback RevelCallback) (err error) {
modulePath, err := rp.ResolveImportPath(moduleImportPath) modulePath, err := rp.ResolveImportPath(moduleImportPath)
if err != nil { if err != nil {
utils.Logger.Info("Missing module ", "module_import_path", moduleImportPath, "error",err) utils.Logger.Info("Missing module ", "module_import_path", moduleImportPath, "error", err)
callback.PackageResolver(moduleImportPath)
if err := callback.PackageResolver(moduleImportPath); err != nil {
return fmt.Errorf("failed to resolve package %w", err)
}
modulePath, err = rp.ResolveImportPath(moduleImportPath) modulePath, err = rp.ResolveImportPath(moduleImportPath)
if err != nil { if err != nil {
return fmt.Errorf("Failed to load module. Import of path failed %s:%s %s:%s ", "modulePath", moduleImportPath, "error", err) 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> // Drop anything between module.???.<name of module>
@@ -242,9 +266,9 @@ func (rp *RevelContainer) loadModules(callback RevelCallback) (err error) {
return return
} }
// Adds a module paths to the container object // Adds a module paths to the container object.
func (rp *RevelContainer) addModulePaths(name, importPath, modulePath string) { func (rp *RevelContainer) addModulePaths(name, importPath, modulePath string) {
utils.Logger.Info("Adding module path","name", name,"import path", importPath,"system path", modulePath) utils.Logger.Info("Adding module path", "name", name, "import path", importPath, "system path", modulePath)
if codePath := filepath.Join(modulePath, "app"); utils.DirExists(codePath) { if codePath := filepath.Join(modulePath, "app"); utils.DirExists(codePath) {
rp.CodePaths = append(rp.CodePaths, codePath) rp.CodePaths = append(rp.CodePaths, codePath)
rp.ModulePathMap[name] = &ModuleInfo{importPath, modulePath} rp.ModulePathMap[name] = &ModuleInfo{importPath, modulePath}
@@ -271,20 +295,21 @@ func (rp *RevelContainer) ResolveImportPath(importPath string) (string, error) {
return filepath.Join(rp.SourcePath, importPath), nil return filepath.Join(rp.SourcePath, importPath), nil
} }
config := &packages.Config{ config := &packages.Config{
Mode: packages.LoadSyntax, Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports |
Dir:rp.AppPath, packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo,
Dir: rp.AppPath,
} }
config.Env = utils.ReducedEnv(false)
pkgs, err := packages.Load(config, importPath) pkgs, err := packages.Load(config, importPath)
if len(pkgs)==0 { if len(pkgs) == 0 {
return "", errors.New("No packages found for import " + importPath +" using app path "+ rp.AppPath) return "", fmt.Errorf("%w %s using app path %s", ErrNoPackages, importPath, rp.AppPath)
} }
// modPkg, err := build.Import(importPath, rp.AppPath, build.FindOnly) // modPkg, err := build.Import(importPath, rp.AppPath, build.FindOnly)
if err != nil { if err != nil {
return "", err return "", err
} }
if len(pkgs[0].GoFiles)>0 { if len(pkgs[0].GoFiles) > 0 {
return filepath.Dir(pkgs[0].GoFiles[0]), nil return filepath.Dir(pkgs[0].GoFiles[0]), nil
} }
return pkgs[0].PkgPath, errors.New("No files found in import path " + importPath) return pkgs[0].PkgPath, fmt.Errorf("%w: %s", ErrNoFiles, importPath)
} }

View File

@@ -3,23 +3,24 @@ package model
// SourceInfo is the top-level struct containing all extracted information // SourceInfo is the top-level struct containing all extracted information
// about the app source code, used to generate main.go. // about the app source code, used to generate main.go.
import ( import (
"github.com/revel/cmd/utils"
"path/filepath" "path/filepath"
"strings" "strings"
"unicode" "unicode"
"github.com/revel/cmd/utils"
) )
type SourceInfo struct { type SourceInfo struct {
// StructSpecs lists type info for all structs found under the code paths. // StructSpecs lists type info for all structs found under the code paths.
// They may be queried to determine which ones (transitively) embed certain types. // They may be queried to determine which ones (transitively) embed certain types.
StructSpecs []*TypeInfo StructSpecs []*TypeInfo
// ValidationKeys provides a two-level lookup. The keys are: // ValidationKeys provides a two-level lookup. The keys are:
// 1. The fully-qualified function name, // 1. The fully-qualified function name,
// e.g. "github.com/revel/examples/chat/app/controllers.(*Application).Action" // e.g. "github.com/revel/examples/chat/app/controllers.(*Application).Action"
// 2. Within that func's file, the line number of the (overall) expression statement. // 2. Within that func's file, the line number of the (overall) expression statement.
// e.g. the line returned from runtime.Caller() // e.g. the line returned from runtime.Caller()
// The result of the lookup the name of variable being validated. // The result of the lookup the name of variable being validated.
ValidationKeys map[string]map[int]string ValidationKeys map[string]map[int]string
// A list of import paths. // A list of import paths.
// Revel notices files with an init() function and imports that package. // Revel notices files with an init() function and imports that package.
InitImportPaths []string InitImportPaths []string
@@ -28,14 +29,14 @@ type SourceInfo struct {
// app/controllers/... that embed (directly or indirectly) revel.Controller // app/controllers/... that embed (directly or indirectly) revel.Controller
controllerSpecs []*TypeInfo controllerSpecs []*TypeInfo
// testSuites list the types that constitute the set of application tests. // testSuites list the types that constitute the set of application tests.
testSuites []*TypeInfo testSuites []*TypeInfo
// packageMap a map of import to system directory (if available) // packageMap a map of import to system directory (if available)
PackageMap map[string]string PackageMap map[string]string
} }
// TypesThatEmbed returns all types that (directly or indirectly) embed the // TypesThatEmbed returns all types that (directly or indirectly) embed the
// target type, which must be a fully qualified type name, // target type, which must be a fully qualified type name,
// e.g. "github.com/revel/revel.Controller" // e.g. "github.com/revel/revel.Controller".
func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered []*TypeInfo) { func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered []*TypeInfo) {
// Do a search in the "embedded type graph", starting with the target type. // Do a search in the "embedded type graph", starting with the target type.
var ( var (
@@ -57,7 +58,6 @@ func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered
// Look through the embedded types to see if the current type is among them. // Look through the embedded types to see if the current type is among them.
for _, embeddedType := range spec.EmbeddedTypes { for _, embeddedType := range spec.EmbeddedTypes {
// If so, add this type's simple name to the nodeQueue, and its spec to // If so, add this type's simple name to the nodeQueue, and its spec to
// the filtered list. // the filtered list.
if typeSimpleName == embeddedType.String() { if typeSimpleName == embeddedType.String() {
@@ -76,7 +76,8 @@ func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered
utils.Logger.Info("Debug: Skipping adding spec for unexported type", utils.Logger.Info("Debug: Skipping adding spec for unexported type",
"type", filteredItem.StructName, "type", filteredItem.StructName,
"package", filteredItem.ImportPath) "package", filteredItem.ImportPath)
filtered = append(filtered[:i], filtered[i + 1:]...) filtered = append(filtered[:i], filtered[i+1:]...)
//nolint:ineffassign // huh?
exit = false exit = false
break break
} }
@@ -99,8 +100,8 @@ func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered
// Report non controller structures in controller folder. // Report non controller structures in controller folder.
if !found && !strings.HasPrefix(spec.StructName, "Test") { if !found && !strings.HasPrefix(spec.StructName, "Test") {
utils.Logger.Warn("Type found in package: " + packageFilter + utils.Logger.Warn("Type found in package: "+packageFilter+
", but did not embed from: " + filepath.Base(targetType), ", but did not embed from: "+filepath.Base(targetType),
"name", spec.StructName, "importpath", spec.ImportPath, "foundstructures", unfoundNames) "name", spec.StructName, "importpath", spec.ImportPath, "foundstructures", unfoundNames)
} }
} }
@@ -109,19 +110,20 @@ func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered
} }
// ControllerSpecs returns the all the controllers that embeds // ControllerSpecs returns the all the controllers that embeds
// `revel.Controller` // `revel.Controller`.
func (s *SourceInfo) ControllerSpecs() []*TypeInfo { func (s *SourceInfo) ControllerSpecs() []*TypeInfo {
utils.Logger.Info("Scanning controller specifications for types ", "typePath", RevelImportPath+".Controller", "speclen", len(s.controllerSpecs))
if s.controllerSpecs == nil { if s.controllerSpecs == nil {
s.controllerSpecs = s.TypesThatEmbed(RevelImportPath + ".Controller", "controllers") s.controllerSpecs = s.TypesThatEmbed(RevelImportPath+".Controller", "controllers")
} }
return s.controllerSpecs return s.controllerSpecs
} }
// TestSuites returns the all the Application tests that embeds // TestSuites returns the all the Application tests that embeds
// `testing.TestSuite` // `testing.TestSuite`.
func (s *SourceInfo) TestSuites() []*TypeInfo { func (s *SourceInfo) TestSuites() []*TypeInfo {
if s.testSuites == nil { if s.testSuites == nil {
s.testSuites = s.TypesThatEmbed(RevelImportPath + "/testing.TestSuite", "testsuite") s.testSuites = s.TypesThatEmbed(RevelImportPath+"/testing.TestSuite", "testsuite")
} }
return s.testSuites return s.testSuites
} }
@@ -136,4 +138,4 @@ func (s *SourceInfo) Merge(srcInfo2 *SourceInfo) {
} }
s.ValidationKeys[k] = v s.ValidationKeys[k] = v
} }
} }

View File

@@ -13,14 +13,14 @@ type TypeExpr struct {
Valid bool Valid bool
} }
// Returns a new type from the data // Returns a new type from the data.
func NewTypeExprFromData(expr, pkgName string, pkgIndex int, valid bool) TypeExpr { func NewTypeExprFromData(expr, pkgName string, pkgIndex int, valid bool) TypeExpr {
return TypeExpr{expr, pkgName, pkgIndex, valid} return TypeExpr{expr, pkgName, pkgIndex, valid}
} }
// NewTypeExpr returns the syntactic expression for referencing this type in Go. // NewTypeExpr returns the syntactic expression for referencing this type in Go.
func NewTypeExprFromAst(pkgName string, expr ast.Expr) TypeExpr { func NewTypeExprFromAst(pkgName string, expr ast.Expr) TypeExpr {
error := "" err := ""
switch t := expr.(type) { switch t := expr.(type) {
case *ast.Ident: case *ast.Ident:
if IsBuiltinType(t.Name) { if IsBuiltinType(t.Name) {
@@ -41,15 +41,14 @@ func NewTypeExprFromAst(pkgName string, expr ast.Expr) TypeExpr {
e := NewTypeExprFromAst(pkgName, t.Value) e := NewTypeExprFromAst(pkgName, t.Value)
return NewTypeExprFromData("map["+identKey.Name+"]"+e.Expr, e.PkgName, e.pkgIndex+len("map["+identKey.Name+"]"), e.Valid) return NewTypeExprFromData("map["+identKey.Name+"]"+e.Expr, e.PkgName, e.pkgIndex+len("map["+identKey.Name+"]"), e.Valid)
} }
error = fmt.Sprintf("Failed to generate name for Map field :%v. Make sure the field name is valid.", t.Key) err = fmt.Sprintf("Failed to generate name for Map field :%v. Make sure the field name is valid.", t.Key)
case *ast.Ellipsis: case *ast.Ellipsis:
e := NewTypeExprFromAst(pkgName, t.Elt) e := NewTypeExprFromAst(pkgName, t.Elt)
return NewTypeExprFromData("[]"+e.Expr, e.PkgName, e.pkgIndex+2, e.Valid) return NewTypeExprFromData("[]"+e.Expr, e.PkgName, e.pkgIndex+2, e.Valid)
default: default:
error = fmt.Sprintf("Failed to generate name for field: %v Package: %v. Make sure the field name is valid.", expr, pkgName) err = fmt.Sprintf("Failed to generate name for field: %v Package: %v. Make sure the field name is valid.", expr, pkgName)
} }
return NewTypeExprFromData(error, "", 0, false) return NewTypeExprFromData(err, "", 0, false)
} }
// TypeName returns the fully-qualified type name for this expression. // TypeName returns the fully-qualified type name for this expression.
@@ -62,7 +61,7 @@ func (e TypeExpr) TypeName(pkgOverride string) string {
return e.Expr[:e.pkgIndex] + pkgName + "." + e.Expr[e.pkgIndex:] return e.Expr[:e.pkgIndex] + pkgName + "." + e.Expr[e.pkgIndex:]
} }
var builtInTypes = map[string]struct{}{ var builtInTypes = map[string]struct{}{ //nolint:gochecknoglobals
"bool": {}, "bool": {},
"byte": {}, "byte": {},
"complex128": {}, "complex128": {},
@@ -85,13 +84,13 @@ var builtInTypes = map[string]struct{}{
"uintptr": {}, "uintptr": {},
} }
// IsBuiltinType checks the given type is built-in types of Go // IsBuiltinType checks the given type is built-in types of Go.
func IsBuiltinType(name string) bool { func IsBuiltinType(name string) bool {
_, ok := builtInTypes[name] _, ok := builtInTypes[name]
return ok return ok
} }
// Returns the first non empty string from a list of arguements // Returns the first non empty string from a list of arguments.
func FirstNonEmpty(strs ...string) string { func FirstNonEmpty(strs ...string) string {
for _, str := range strs { for _, str := range strs {
if len(str) > 0 { if len(str) > 0 {

View File

@@ -9,7 +9,7 @@ type TypeInfo struct {
EmbeddedTypes []*EmbeddedTypeName // Used internally to identify controllers that indirectly embed *revel.Controller. EmbeddedTypes []*EmbeddedTypeName // Used internally to identify controllers that indirectly embed *revel.Controller.
} }
// Return the type information as a properly formatted import string // Return the type information as a properly formatted import string.
func (s *TypeInfo) String() string { func (s *TypeInfo) String() string {
return s.ImportPath + "." + s.StructName return s.ImportPath + "." + s.StructName
} }

View File

@@ -2,9 +2,10 @@ package model
import ( import (
"fmt" "fmt"
"github.com/pkg/errors"
"regexp" "regexp"
"strconv" "strconv"
"github.com/pkg/errors"
) )
type Version struct { type Version struct {
@@ -17,26 +18,24 @@ type Version struct {
MinGoVersion string MinGoVersion string
} }
// The compatibility list // The compatibility list.
var frameworkCompatibleRangeList = [][]string{ var frameworkCompatibleRangeList = [][]string{
{"0.0.0", "0.20.0"}, // minimum Revel version to use with this version of the tool {"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 {"0.19.99", "0.30.0"}, // Compatible with Framework V 0.19.99 - 0.30.0
{"1.0.0", "1.1.0"}, // Compatible with Framework V 1.0 - 1.1 {"1.0.0", "1.9.0"}, // Compatible with Framework V 1.0 - 1.9
} }
// Parses a version like v1.2.3a or 1.2 // Parses a version like v1.2.3a or 1.2.
var versionRegExp = regexp.MustCompile(`([^\d]*)?([0-9]*)\.([0-9]*)(\.([0-9]*))?(.*)`) var versionRegExp = regexp.MustCompile(`([^\d]*)?([0-9]*)\.([0-9]*)(\.([0-9]*))?(.*)`)
// Parse the version and return it as a Version object // Parse the version and return it as a Version object.
func ParseVersion(version string) (v *Version, err error) { func ParseVersion(version string) (v *Version, err error) {
v = &Version{} v = &Version{}
return v, v.ParseVersion(version) return v, v.ParseVersion(version)
} }
// Parse the version and return it as a Version object // Parse the version and return it as a Version object.
func (v *Version)ParseVersion(version string) (err error) { func (v *Version) ParseVersion(version string) (err error) {
parsedResult := versionRegExp.FindAllStringSubmatch(version, -1) parsedResult := versionRegExp.FindAllStringSubmatch(version, -1)
if len(parsedResult) != 1 { if len(parsedResult) != 1 {
err = errors.Errorf("Invalid version %s", version) err = errors.Errorf("Invalid version %s", version)
@@ -55,7 +54,8 @@ func (v *Version)ParseVersion(version string) (err error) {
return return
} }
// Returns 0 or an int value for the string, errors are returned as 0
// Returns 0 or an int value for the string, errors are returned as 0.
func (v *Version) intOrZero(input string) (value int) { func (v *Version) intOrZero(input string) (value int) {
if input != "" { if input != "" {
value, _ = strconv.Atoi(input) value, _ = strconv.Atoi(input)
@@ -63,7 +63,7 @@ func (v *Version) intOrZero(input string) (value int) {
return value return value
} }
// Returns true if this major revision is compatible // Returns true if this major revision is compatible.
func (v *Version) CompatibleFramework(c *CommandConfig) error { func (v *Version) CompatibleFramework(c *CommandConfig) error {
for i, rv := range frameworkCompatibleRangeList { for i, rv := range frameworkCompatibleRangeList {
start, _ := ParseVersion(rv[0]) start, _ := ParseVersion(rv[0])
@@ -81,7 +81,7 @@ func (v *Version) CompatibleFramework(c *CommandConfig) error {
return errors.New("Tool out of date - do a 'go get -u github.com/revel/cmd/revel'") 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 // Returns true if this major revision is newer then the passed in.
func (v *Version) MajorNewer(o *Version) bool { func (v *Version) MajorNewer(o *Version) bool {
if v.Major != o.Major { if v.Major != o.Major {
return v.Major > o.Major return v.Major > o.Major
@@ -89,7 +89,7 @@ func (v *Version) MajorNewer(o *Version) bool {
return false return false
} }
// Returns true if this major or major and minor revision is newer then the value passed in // Returns true if this major or major and minor revision is newer then the value passed in.
func (v *Version) MinorNewer(o *Version) bool { func (v *Version) MinorNewer(o *Version) bool {
if v.Major != o.Major { if v.Major != o.Major {
return v.Major > o.Major return v.Major > o.Major
@@ -100,7 +100,7 @@ func (v *Version) MinorNewer(o *Version) bool {
return false return false
} }
// Returns true if the version is newer then the current on // Returns true if the version is newer then the current on.
func (v *Version) Newer(o *Version) bool { func (v *Version) Newer(o *Version) bool {
if v.Major != o.Major { if v.Major != o.Major {
return v.Major > o.Major return v.Major > o.Major
@@ -114,13 +114,13 @@ func (v *Version) Newer(o *Version) bool {
return true return true
} }
// Convert the version to a string // Convert the version to a string.
func (v *Version) VersionString() string { func (v *Version) VersionString() string {
return fmt.Sprintf("%s%d.%d.%d%s", v.Prefix, v.Major, v.Minor, v.Maintenance, v.Suffix) 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 // Convert the version build date and go version to a string.
func (v *Version) String() string { func (v *Version) String() string {
return fmt.Sprintf("Version: %s%d.%d.%d%s\nBuild Date: %s\n Minimium Go Version: %s", 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) v.Prefix, v.Major, v.Minor, v.Maintenance, v.Suffix, v.BuildDate, v.MinGoVersion)
} }

View File

@@ -1,9 +1,10 @@
package model_test package model_test
import ( import (
"github.com/stretchr/testify/assert"
"testing" "testing"
"github.com/revel/cmd/model" "github.com/revel/cmd/model"
"github.com/stretchr/testify/assert"
) )
var versionTests = [][]string{ var versionTests = [][]string{
@@ -12,22 +13,23 @@ var versionTests = [][]string{
{"v0.20.", "v0.20.0"}, {"v0.20.", "v0.20.0"},
{"2.0", "2.0.0"}, {"2.0", "2.0.0"},
} }
// Test that the event handler can be attached and it dispatches the event received
// Test that the event handler can be attached and it dispatches the event received.
func TestVersion(t *testing.T) { func TestVersion(t *testing.T) {
for _, v:= range versionTests { for _, v := range versionTests {
p,e:=model.ParseVersion(v[0]) p, e := model.ParseVersion(v[0])
assert.Nil(t,e,"Should have parsed %s",v) assert.Nil(t, e, "Should have parsed %s", v)
assert.Equal(t,p.String(),v[1], "Should be equal %s==%s",p.String(),v) assert.Equal(t, p.String(), v[1], "Should be equal %s==%s", p.String(), v)
} }
} }
// test the ranges // test the ranges.
func TestVersionRange(t *testing.T) { func TestVersionRange(t *testing.T) {
a,_ := model.ParseVersion("0.1.2") a, _ := model.ParseVersion("0.1.2")
b,_ := model.ParseVersion("0.2.1") b, _ := model.ParseVersion("0.2.1")
c,_ := model.ParseVersion("1.0.1") c, _ := model.ParseVersion("1.0.1")
assert.True(t, b.MinorNewer(a), "B is newer then A") 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(a), "B is not major newer then A")
assert.False(t, b.MajorNewer(c), "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") assert.True(t, c.MajorNewer(b), "C is major newer then b")
} }

View File

@@ -2,9 +2,10 @@ package parser
import ( import (
"go/ast" "go/ast"
"github.com/revel/cmd/utils"
"github.com/revel/cmd/model"
"go/token" "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. // If this Decl is a struct type definition, it is summarized and added to specs.
@@ -91,7 +92,7 @@ func appendStruct(fileName string, specs []*model.TypeInfo, pkgImportPath string
// If decl is a Method declaration, it is summarized and added to the array // If decl is a Method declaration, it is summarized and added to the array
// underneath its receiver type. // underneath its receiver type.
// e.g. "Login" => {MethodSpec, MethodSpec, ..} // e.g. "Login" => {MethodSpec, MethodSpec, ..}.
func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPath, pkgName string, imports map[string]string) { func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPath, pkgName string, imports map[string]string) {
// Func declaration? // Func declaration?
funcDecl, ok := decl.(*ast.FuncDecl) funcDecl, ok := decl.(*ast.FuncDecl)
@@ -194,7 +195,7 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
}) })
var recvTypeName string var recvTypeName string
var recvType = funcDecl.Recv.List[0].Type recvType := funcDecl.Recv.List[0].Type
if recvStarType, ok := recvType.(*ast.StarExpr); ok { if recvStarType, ok := recvType.(*ast.StarExpr); ok {
recvTypeName = recvStarType.X.(*ast.Ident).Name recvTypeName = recvStarType.X.(*ast.Ident).Name
} else { } else {
@@ -204,7 +205,7 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
mm[recvTypeName] = append(mm[recvTypeName], method) mm[recvTypeName] = append(mm[recvTypeName], method)
} }
// Combine the 2 source info models into one // Combine the 2 source info models into one.
func appendSourceInfo(srcInfo1, srcInfo2 *model.SourceInfo) *model.SourceInfo { func appendSourceInfo(srcInfo1, srcInfo2 *model.SourceInfo) *model.SourceInfo {
if srcInfo1 == nil { if srcInfo1 == nil {
return srcInfo2 return srcInfo2

View File

@@ -1,15 +1,16 @@
package parser package parser
import ( import (
"github.com/revel/cmd/utils"
"go/ast" "go/ast"
"go/build" "go/build"
"go/token" "go/token"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/revel/cmd/utils"
) )
// Add imports to the map from the source dir // Add imports to the map from the source dir.
func addImports(imports map[string]string, decl ast.Decl, srcDir string) { func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
genDecl, ok := decl.(*ast.GenDecl) genDecl, ok := decl.(*ast.GenDecl)
if !ok { if !ok {
@@ -42,7 +43,6 @@ func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
// 2. Exempt the standard library; their directories always match the package name. // 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 // 3. Can use build.FindOnly and then use parser.ParseDir with mode PackageClauseOnly
if pkgAlias == "" { if pkgAlias == "" {
utils.Logger.Debug("Reading from build", "path", fullPath, "srcPath", srcDir, "gopath", build.Default.GOPATH) utils.Logger.Debug("Reading from build", "path", fullPath, "srcPath", srcDir, "gopath", build.Default.GOPATH)
pkg, err := build.Import(fullPath, srcDir, 0) pkg, err := build.Import(fullPath, srcDir, 0)
if err != nil { if err != nil {
@@ -63,7 +63,7 @@ func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
} }
// Returns a valid import string from the path // Returns a valid import string from the path
// using the build.Defaul.GOPATH to determine the root // using the build.Defaul.GOPATH to determine the root.
func importPathFromPath(root, basePath string) string { func importPathFromPath(root, basePath string) string {
vendorTest := filepath.Join(basePath, "vendor") vendorTest := filepath.Join(basePath, "vendor")
if len(root) > len(vendorTest) && root[:len(vendorTest)] == vendorTest { if len(root) > len(vendorTest) && root[:len(vendorTest)] == vendorTest {

View File

@@ -8,6 +8,7 @@ package parser
// It catalogs the controllers, their methods, and their arguments. // It catalogs the controllers, their methods, and their arguments.
import ( import (
"errors"
"go/ast" "go/ast"
"go/parser" "go/parser"
"go/scanner" "go/scanner"
@@ -20,7 +21,7 @@ import (
"github.com/revel/cmd/utils" "github.com/revel/cmd/utils"
) )
// A container used to support the reflection package // A container used to support the reflection package.
type processContainer struct { type processContainer struct {
root, rootImportPath string // The paths root, rootImportPath string // The paths
paths *model.RevelContainer // The Revel paths paths *model.RevelContainer // The Revel paths
@@ -54,7 +55,7 @@ func ProcessSource(paths *model.RevelContainer) (_ *model.SourceInfo, compileErr
return pc.srcInfo, compileError return pc.srcInfo, compileError
} }
// Called during the "walk process" // Called during the "walk process".
func (pc *processContainer) processPath(path string, info os.FileInfo, err error) error { func (pc *processContainer) processPath(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
utils.Logger.Error("Error scanning app source:", "error", err) utils.Logger.Error("Error scanning app source:", "error", err)
@@ -83,8 +84,9 @@ func (pc *processContainer) processPath(path string, info os.FileInfo, err error
0) 0)
if err != nil { if err != nil {
if errList, ok := err.(scanner.ErrorList); ok { var errList scanner.ErrorList
var pos = errList[0].Pos if errors.As(err, &errList) {
pos := errList[0].Pos
newError := &utils.SourceError{ newError := &utils.SourceError{
SourceType: ".go source", SourceType: ".go source",
Title: "Go Compilation Error", Title: "Go Compilation Error",
@@ -114,7 +116,7 @@ func (pc *processContainer) processPath(path string, info os.FileInfo, err error
// These cannot be included in source code that is not generated specifically as a test // These cannot be included in source code that is not generated specifically as a test
for i := range pkgs { for i := range pkgs {
if len(i) > 6 { if len(i) > 6 {
if string(i[len(i)-5:]) == "_test" { if i[len(i)-5:] == "_test" {
delete(pkgs, i) delete(pkgs, i)
} }
} }
@@ -146,7 +148,7 @@ func (pc *processContainer) processPath(path string, info os.FileInfo, err error
return nil return nil
} }
// Process a single package within a file // Process a single package within a file.
func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast.Package) *model.SourceInfo { func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast.Package) *model.SourceInfo {
var ( var (
structSpecs []*model.TypeInfo structSpecs []*model.TypeInfo

View File

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

View File

@@ -1,10 +1,11 @@
package parser package parser
import ( import (
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"go/ast" "go/ast"
"go/token" "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. // Scan app source code for calls to X.Y(), where X is of type *Validation.

View File

@@ -1,13 +1,15 @@
package parser2 package parser2
import ( import (
"github.com/revel/cmd/utils"
"golang.org/x/tools/go/packages"
"github.com/revel/cmd/model"
"go/ast" "go/ast"
"go/token" "go/token"
"strings"
"path/filepath" "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 ( type (
@@ -17,7 +19,7 @@ type (
) )
func NewSourceInfoProcessor(sourceProcessor *SourceProcessor) *SourceInfoProcessor { func NewSourceInfoProcessor(sourceProcessor *SourceProcessor) *SourceInfoProcessor {
return &SourceInfoProcessor{sourceProcessor:sourceProcessor} return &SourceInfoProcessor{sourceProcessor: sourceProcessor}
} }
func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *model.SourceInfo) { func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *model.SourceInfo) {
@@ -31,14 +33,20 @@ func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *m
strings.Contains(p.PkgPath, "/tests/") strings.Contains(p.PkgPath, "/tests/")
methodMap = map[string][]*model.MethodSpec{} 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 _, tree := range p.Syntax {
for _, decl := range tree.Decls { for _, decl := range tree.Decls {
s.sourceProcessor.packageMap[p.PkgPath] = filepath.Dir(p.Fset.Position(decl.Pos()).Filename) s.sourceProcessor.packageMap[p.PkgPath] = filepath.Dir(p.Fset.Position(decl.Pos()).Filename)
//println("*** checking", p.Fset.Position(decl.Pos()).Filename) if !s.addImport(decl, p, localImportMap, log) {
continue
}
spec, found := s.getStructTypeDecl(decl, p.Fset) spec, found := s.getStructTypeDecl(decl, p.Fset)
// log.Info("Checking file","filename", p.Fset.Position(decl.Pos()).Filename,"found",found)
if found { if found {
if isController || isTest { if isController || isTest {
controllerSpec := s.getControllerSpec(spec, p) controllerSpec := s.getControllerSpec(spec, p, localImportMap)
sourceInfo.StructSpecs = append(sourceInfo.StructSpecs, controllerSpec) sourceInfo.StructSpecs = append(sourceInfo.StructSpecs, controllerSpec)
} }
} else { } else {
@@ -54,14 +62,14 @@ func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *m
funcDecl.Name.IsExported() && // be public funcDecl.Name.IsExported() && // be public
funcDecl.Type.Results != nil && len(funcDecl.Type.Results.List) == 1 { funcDecl.Type.Results != nil && len(funcDecl.Type.Results.List) == 1 {
// return one result // return one result
if m, receiver := s.getControllerFunc(funcDecl, p); m != nil { if m, receiver := s.getControllerFunc(funcDecl, p, localImportMap); m != nil {
methodMap[receiver] = append(methodMap[receiver], m) methodMap[receiver] = append(methodMap[receiver], m)
s.sourceProcessor.log.Info("Added method map to ", "receiver", receiver, "method", m.Name) log.Info("Added method map to ", "receiver", receiver, "method", m.Name)
} }
} }
// Check for validation // Check for validation
if lineKeyMap := s.getValidation(funcDecl, p); len(lineKeyMap) > 1 { if lineKeyMap := s.getValidation(funcDecl, p); len(lineKeyMap) > 1 {
sourceInfo.ValidationKeys[p.PkgPath + "." + s.getFuncName(funcDecl)] = lineKeyMap sourceInfo.ValidationKeys[p.PkgPath+"."+s.getFuncName(funcDecl)] = lineKeyMap
} }
if funcDecl.Name.Name == "init" { if funcDecl.Name.Name == "init" {
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, p.PkgPath) sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, p.PkgPath)
@@ -77,6 +85,7 @@ func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *m
return return
} }
// Scan app source code for calls to X.Y(), where X is of type *Validation. // Scan app source code for calls to X.Y(), where X is of type *Validation.
// //
// Recognize these scenarios: // Recognize these scenarios:
@@ -91,7 +100,7 @@ func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *m
// //
// The end result is that we can set the default validation key for each call to // The end result is that we can set the default validation key for each call to
// be the same as the local variable. // be the same as the local variable.
func (s *SourceInfoProcessor) getValidation(funcDecl *ast.FuncDecl, p *packages.Package) (map[int]string) { func (s *SourceInfoProcessor) getValidation(funcDecl *ast.FuncDecl, p *packages.Package) map[int]string {
var ( var (
lineKeys = make(map[int]string) lineKeys = make(map[int]string)
@@ -157,10 +166,10 @@ func (s *SourceInfoProcessor) getValidation(funcDecl *ast.FuncDecl, p *packages.
}) })
return lineKeys return lineKeys
} }
// Check to see if there is a *revel.Validation as an argument. // Check to see if there is a *revel.Validation as an argument.
func (s *SourceInfoProcessor) getValidationParameter(funcDecl *ast.FuncDecl) *ast.Object { func (s *SourceInfoProcessor) getValidationParameter(funcDecl *ast.FuncDecl) *ast.Object {
for _, field := range funcDecl.Type.Params.List { for _, field := range funcDecl.Type.Params.List {
starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation
if !ok { if !ok {
@@ -183,7 +192,8 @@ func (s *SourceInfoProcessor) getValidationParameter(funcDecl *ast.FuncDecl) *a
} }
return nil return nil
} }
func (s *SourceInfoProcessor) getControllerFunc(funcDecl *ast.FuncDecl, p *packages.Package) (method *model.MethodSpec, recvTypeName string) {
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) selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
if !ok { if !ok {
return return
@@ -212,8 +222,11 @@ func (s *SourceInfoProcessor) getControllerFunc(funcDecl *ast.FuncDecl, p *packa
importPath = p.PkgPath importPath = p.PkgPath
} else if typeExpr.PkgName != "" { } else if typeExpr.PkgName != "" {
var ok bool var ok bool
if importPath, ok = s.sourceProcessor.importMap[typeExpr.PkgName]; !ok { if importPath, ok = localImportMap[typeExpr.PkgName]; !ok {
utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName("")) 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{ method.Args = append(method.Args, &model.MethodArg{
@@ -263,7 +276,7 @@ func (s *SourceInfoProcessor) getControllerFunc(funcDecl *ast.FuncDecl, p *packa
return true return true
}) })
var recvType = funcDecl.Recv.List[0].Type recvType := funcDecl.Recv.List[0].Type
if recvStarType, ok := recvType.(*ast.StarExpr); ok { if recvStarType, ok := recvType.(*ast.StarExpr); ok {
recvTypeName = recvStarType.X.(*ast.Ident).Name recvTypeName = recvStarType.X.(*ast.Ident).Name
} else { } else {
@@ -271,7 +284,8 @@ func (s *SourceInfoProcessor) getControllerFunc(funcDecl *ast.FuncDecl, p *packa
} }
return return
} }
func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages.Package) (controllerSpec *model.TypeInfo) {
func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages.Package, localImportMap map[string]string) (controllerSpec *model.TypeInfo) {
structType := spec.Type.(*ast.StructType) structType := spec.Type.(*ast.StructType)
// At this point we know it's a type declaration for a struct. // At this point we know it's a type declaration for a struct.
@@ -282,6 +296,7 @@ func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages.
ImportPath: p.PkgPath, ImportPath: p.PkgPath,
PackageName: p.Name, 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 { for _, field := range structType.Fields.List {
// If field.Names is set, it's not an embedded type. // If field.Names is set, it's not an embedded type.
if field.Names != nil { if field.Names != nil {
@@ -329,9 +344,12 @@ func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages.
importPath = p.PkgPath importPath = p.PkgPath
} else { } else {
var ok bool var ok bool
if importPath, ok = s.sourceProcessor.importMap[pkgName]; !ok { if importPath, ok = localImportMap[pkgName]; !ok {
s.sourceProcessor.log.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap) log.Debug("Debug: Unusual, failed to find package locally ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin")
continue 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
}
} }
} }
@@ -343,6 +361,7 @@ func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages.
s.sourceProcessor.log.Info("Added controller spec", "name", controllerSpec.StructName, "package", controllerSpec.ImportPath) s.sourceProcessor.log.Info("Added controller spec", "name", controllerSpec.StructName, "package", controllerSpec.ImportPath)
return return
} }
func (s *SourceInfoProcessor) getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) { func (s *SourceInfoProcessor) getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) {
genDecl, ok := decl.(*ast.GenDecl) genDecl, ok := decl.(*ast.GenDecl)
if !ok { if !ok {
@@ -362,8 +381,8 @@ func (s *SourceInfoProcessor) getStructTypeDecl(decl ast.Decl, fset *token.FileS
_, found = spec.Type.(*ast.StructType) _, found = spec.Type.(*ast.StructType)
return return
} }
func (s *SourceInfoProcessor) getFuncName(funcDecl *ast.FuncDecl) string { func (s *SourceInfoProcessor) getFuncName(funcDecl *ast.FuncDecl) string {
prefix := "" prefix := ""
if funcDecl.Recv != nil { if funcDecl.Recv != nil {
@@ -376,4 +395,37 @@ func (s *SourceInfoProcessor) getFuncName(funcDecl *ast.FuncDecl) string {
prefix += "." prefix += "."
} }
return prefix + funcDecl.Name.Name 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
}

View File

@@ -1,15 +1,19 @@
package parser2 package parser2
import ( import (
"go/ast"
"go/token"
"github.com/revel/cmd/model"
"golang.org/x/tools/go/packages"
"github.com/revel/cmd/utils"
"errors" "errors"
"go/ast"
"go/parser"
"go/scanner"
"go/token"
"os"
"path/filepath"
"strings" "strings"
"github.com/revel/cmd/logger" "github.com/revel/cmd/logger"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"golang.org/x/tools/go/packages"
) )
type ( type (
@@ -33,28 +37,28 @@ func ProcessSource(revelContainer *model.RevelContainer) (sourceInfo *model.Sour
processor.log.Infof("From parsers : Structures:%d InitImports:%d ValidationKeys:%d %v", len(sourceInfo.StructSpecs), len(sourceInfo.InitImportPaths), len(sourceInfo.ValidationKeys), sourceInfo.PackageMap) processor.log.Infof("From parsers : Structures:%d InitImports:%d ValidationKeys:%d %v", len(sourceInfo.StructSpecs), len(sourceInfo.InitImportPaths), len(sourceInfo.ValidationKeys), sourceInfo.PackageMap)
} }
if false {
compileError = errors.New("Incompleted")
utils.Logger.Panic("Not implemented")
}
return return
} }
func NewSourceProcessor(revelContainer *model.RevelContainer) *SourceProcessor { func NewSourceProcessor(revelContainer *model.RevelContainer) *SourceProcessor {
s := &SourceProcessor{revelContainer:revelContainer, log:utils.Logger.New("parser", "SourceProcessor")} s := &SourceProcessor{revelContainer: revelContainer, log: utils.Logger.New("parser", "SourceProcessor")}
s.sourceInfoProcessor = NewSourceInfoProcessor(s) s.sourceInfoProcessor = NewSourceInfoProcessor(s)
return s return s
} }
func (s *SourceProcessor) parse() (compileError error) { func (s *SourceProcessor) parse() (compileError error) {
print("Parsing packages, (may require download if not cached)...")
if compileError = s.addPackages(); compileError != nil { if compileError = s.addPackages(); compileError != nil {
return return
} }
println(" Completed")
if compileError = s.addImportMap(); compileError != nil { if compileError = s.addImportMap(); compileError != nil {
return return
} }
if compileError = s.addSourceInfo(); compileError != nil { if compileError = s.addSourceInfo(); compileError != nil {
return return
} }
s.sourceInfo.PackageMap = map[string]string{} s.sourceInfo.PackageMap = map[string]string{}
getImportFromMap := func(packagePath string) string { getImportFromMap := func(packagePath string) string {
for path := range s.packageMap { for path := range s.packageMap {
@@ -73,81 +77,163 @@ func (s *SourceProcessor) parse() (compileError error) {
return 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) { func (s *SourceProcessor) addPackages() (err error) {
allPackages := []string{s.revelContainer.ImportPath + "/...", model.RevelImportPath} allPackages := []string{model.RevelImportPath + "/..."}
for _, module := range s.revelContainer.ModulePathMap { for _, module := range s.revelContainer.ModulePathMap {
allPackages = append(allPackages, module.ImportPath + "/...") // +"/app/controllers/...") allPackages = append(allPackages, module.ImportPath+"/...") // +"/app/controllers/...")
} }
//allPackages = []string{s.revelContainer.ImportPath + "/..."} //+"/app/controllers/..."} s.log.Info("Reading packages", "packageList", allPackages)
// allPackages = []string{s.revelContainer.ImportPath + "/..."} //+"/app/controllers/..."}
config := &packages.Config{ config := &packages.Config{
// ode: packages.NeedSyntax | packages.NeedCompiledGoFiles, // ode: packages.NeedSyntax | packages.NeedCompiledGoFiles,
Mode: Mode: packages.NeedTypes | // For compile error
packages.NeedTypes | // For compile error
packages.NeedDeps | // To load dependent files packages.NeedDeps | // To load dependent files
packages.NeedName | // Loads the full package name packages.NeedName | // Loads the full package name
packages.NeedSyntax, // To load ast tree (for end points) packages.NeedSyntax, // To load ast tree (for end points)
//Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | // Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles |
// packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | // packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile |
// packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | // packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo |
// packages.NeedTypesSizes, // packages.NeedTypesSizes,
//Mode: packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedFiles | // Mode: packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedFiles |
// packages.NeedCompiledGoFiles | packages.NeedTypesSizes | // packages.NeedCompiledGoFiles | packages.NeedTypesSizes |
// packages.NeedSyntax | packages.NeedCompiledGoFiles , // packages.NeedSyntax | packages.NeedCompiledGoFiles ,
//Mode: packages.NeedSyntax | packages.NeedCompiledGoFiles | packages.NeedName | packages.NeedFiles | // Mode: packages.NeedSyntax | packages.NeedCompiledGoFiles | packages.NeedName | packages.NeedFiles |
// packages.LoadTypes | packages.NeedTypes | packages.NeedDeps, //, // | // packages.LoadTypes | packages.NeedTypes | packages.NeedDeps, //, // |
// packages.NeedTypes, // packages.LoadTypes | packages.NeedSyntax | packages.NeedTypesInfo, // packages.NeedTypes, // packages.LoadTypes | packages.NeedSyntax | packages.NeedTypesInfo,
//packages.LoadSyntax | packages.NeedDeps, // packages.LoadSyntax | packages.NeedDeps,
Dir:s.revelContainer.AppPath, Dir: s.revelContainer.AppPath,
} }
config.Env = utils.ReducedEnv(false)
s.packageList, err = packages.Load(config, allPackages...) s.packageList, err = packages.Load(config, allPackages...)
s.log.Info("Loaded packages ", "len results", len(s.packageList), "error", err) 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 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) { func (s *SourceProcessor) addImportMap() (err error) {
s.importMap = map[string]string{} s.importMap = map[string]string{}
s.packageMap = map[string]string{} s.packageMap = map[string]string{}
for _, p := range s.packageList { for _, p := range s.packageList {
if len(p.Errors) > 0 { if len(p.Errors) > 0 {
// Generate a compile error // Generate a compile error
for _, e := range p.Errors { for _, e := range p.Errors {
if !strings.Contains(e.Msg, "fsnotify") { s.log.Info("While reading packages encountered import error ignoring ", "PkgPath", p.PkgPath, "error", e)
err = utils.NewCompileError("", "", e)
}
} }
} }
for _, tree := range p.Syntax { for _, tree := range p.Syntax {
for _, decl := range tree.Decls { s.importMap[tree.Name.Name] = p.PkgPath
genDecl, ok := decl.(*ast.GenDecl)
if !ok {
continue
}
if genDecl.Tok == token.IMPORT {
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:]
}
}
s.importMap[pkgAlias] = fullPath
}
}
}
} }
} }
return return
@@ -165,53 +251,3 @@ func (s *SourceProcessor) addSourceInfo() (err error) {
} }
return return
} }
// Add imports to the map from the source dir
//func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
// genDecl, ok := decl.(*ast.GenDecl)
// if !ok {
// return
// }
//
// if genDecl.Tok != token.IMPORT {
// return
// }
//
// for _, spec := range genDecl.Specs {
// importSpec := spec.(*ast.ImportSpec)
// var pkgAlias string
// if importSpec.Name != nil {
// pkgAlias = importSpec.Name.Name
// if pkgAlias == "_" {
// continue
// }
// }
// quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\""
// fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes
//
// // If the package was not aliased (common case), we have to import it
// // to see what the package name is.
// // TODO: Can improve performance here a lot:
// // 1. Do not import everything over and over again. Keep a cache.
// // 2. Exempt the standard library; their directories always match the package name.
// // 3. Can use build.FindOnly and then use parser.ParseDir with mode PackageClauseOnly
// if pkgAlias == "" {
//
// utils.Logger.Debug("Reading from build", "path", fullPath, "srcPath", srcDir, "gopath", build.Default.GOPATH)
// pkg, err := build.Import(fullPath, srcDir, 0)
// if err != nil {
// // We expect this to happen for apps using reverse routing (since we
// // have not yet generated the routes). Don't log that.
// if !strings.HasSuffix(fullPath, "/app/routes") {
// utils.Logger.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
// }
//}

View File

@@ -9,5 +9,4 @@
// 2. Monitor the user source and restart the program when necessary. // 2. Monitor the user source and restart the program when necessary.
// //
// Source files are generated in the app/tmp directory. // Source files are generated in the app/tmp directory.
package proxy package proxy

View File

@@ -5,11 +5,11 @@
package main package main
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"fmt"
"github.com/revel/cmd/harness" "github.com/revel/cmd/harness"
"github.com/revel/cmd/model" "github.com/revel/cmd/model"
"github.com/revel/cmd/utils" "github.com/revel/cmd/utils"
@@ -34,7 +34,7 @@ func init() {
cmdBuild.UpdateConfig = updateBuildConfig cmdBuild.UpdateConfig = updateBuildConfig
} }
// The update config updates the configuration command so that it can run // The update config updates the configuration command so that it can run.
func updateBuildConfig(c *model.CommandConfig, args []string) bool { func updateBuildConfig(c *model.CommandConfig, args []string) bool {
c.Index = model.BUILD c.Index = model.BUILD
if c.Build.TargetPath == "" { if c.Build.TargetPath == "" {
@@ -57,7 +57,7 @@ func updateBuildConfig(c *model.CommandConfig, args []string) bool {
return true return true
} }
// The main entry point to build application from command line // The main entry point to build application from command line.
func buildApp(c *model.CommandConfig) (err error) { func buildApp(c *model.CommandConfig) (err error) {
appImportPath, destPath, mode := c.ImportPath, c.Build.TargetPath, DefaultRunMode appImportPath, destPath, mode := c.ImportPath, c.Build.TargetPath, DefaultRunMode
if len(c.Build.Mode) > 0 { if len(c.Build.Mode) > 0 {
@@ -69,7 +69,7 @@ func buildApp(c *model.CommandConfig) (err error) {
c.Build.Mode = mode c.Build.Mode = mode
c.Build.ImportPath = appImportPath c.Build.ImportPath = appImportPath
revel_paths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver)) revelPaths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
if err != nil { if err != nil {
return return
} }
@@ -79,7 +79,7 @@ func buildApp(c *model.CommandConfig) (err error) {
} }
// Ensure the application can be built, this generates the main file // Ensure the application can be built, this generates the main file
app, err := harness.Build(c, revel_paths) app, err := harness.Build(c, revelPaths)
if err != nil { if err != nil {
return err return err
} }
@@ -90,11 +90,11 @@ func buildApp(c *model.CommandConfig) (err error) {
// - revel // - revel
// - app // - app
packageFolders, err := buildCopyFiles(c, app, revel_paths) packageFolders, err := buildCopyFiles(c, app, revelPaths)
if err != nil { if err != nil {
return return
} }
err = buildCopyModules(c, revel_paths, packageFolders, app) err = buildCopyModules(c, revelPaths, packageFolders, app)
if err != nil { if err != nil {
return return
} }
@@ -105,36 +105,36 @@ func buildApp(c *model.CommandConfig) (err error) {
return return
} }
// Copy the files to the target // Copy the files to the target.
func buildCopyFiles(c *model.CommandConfig, app *harness.App, revel_paths *model.RevelContainer) (packageFolders []string, err error) { func buildCopyFiles(c *model.CommandConfig, app *harness.App, revelPaths *model.RevelContainer) (packageFolders []string, err error) {
appImportPath, destPath := c.ImportPath, c.Build.TargetPath 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(model.RevelImportPath)) tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(model.RevelImportPath))
if err = utils.CopyFile(destBinaryPath, app.BinaryPath); err != nil { if err = utils.CopyFile(destBinaryPath, filepath.Join(revelPaths.BasePath, app.BinaryPath)); err != nil {
return return
} }
utils.MustChmod(destBinaryPath, 0755) utils.MustChmod(destBinaryPath, 0755)
// Copy the templates from the revel // Copy the templates from the revel
if err = utils.CopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revel_paths.RevelPath, "conf"), nil); err != nil { if err = utils.CopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revelPaths.RevelPath, "conf"), nil); err != nil {
return return
} }
if err = utils.CopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revel_paths.RevelPath, "templates"), nil); err != nil { if err = utils.CopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revelPaths.RevelPath, "templates"), nil); err != nil {
return return
} }
// Get the folders to be packaged // Get the folders to be packaged
packageFolders = strings.Split(revel_paths.Config.StringDefault("package.folders", "conf,public,app/views"), ",") packageFolders = strings.Split(revelPaths.Config.StringDefault("package.folders", "conf,public,app/views"), ",")
for i, p := range packageFolders { for i, p := range packageFolders {
// Clean spaces, reformat slash to filesystem // Clean spaces, reformat slash to filesystem
packageFolders[i] = filepath.FromSlash(strings.TrimSpace(p)) packageFolders[i] = filepath.FromSlash(strings.TrimSpace(p))
} }
if c.Build.CopySource { if c.Build.CopySource {
err = utils.CopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revel_paths.BasePath, nil) err = utils.CopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revelPaths.BasePath, nil)
if err != nil { if err != nil {
return return
} }
@@ -142,7 +142,7 @@ func buildCopyFiles(c *model.CommandConfig, app *harness.App, revel_paths *model
for _, folder := range packageFolders { for _, folder := range packageFolders {
err = utils.CopyDir( err = utils.CopyDir(
filepath.Join(srcPath, filepath.FromSlash(appImportPath), folder), filepath.Join(srcPath, filepath.FromSlash(appImportPath), folder),
filepath.Join(revel_paths.BasePath, folder), filepath.Join(revelPaths.BasePath, folder),
nil) nil)
if err != nil { if err != nil {
return return
@@ -153,11 +153,11 @@ func buildCopyFiles(c *model.CommandConfig, app *harness.App, revel_paths *model
return return
} }
// Based on the section copy over the build modules // Based on the section copy over the build modules.
func buildCopyModules(c *model.CommandConfig, revel_paths *model.RevelContainer, packageFolders []string, app *harness.App) (err error) { func buildCopyModules(c *model.CommandConfig, revelPaths *model.RevelContainer, packageFolders []string, app *harness.App) (err error) {
destPath := filepath.Join(c.Build.TargetPath, "src") 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_paths.Config.Raw() config := revelPaths.Config.Raw()
// We should only copy over the section of options what the build is targeted for // We should only copy over the section of options what the build is targeted for
// We will default to prod // We will default to prod
@@ -177,7 +177,6 @@ func buildCopyModules(c *model.CommandConfig, revel_paths *model.RevelContainer,
continue continue
} }
moduleImportList = append(moduleImportList, moduleImportPath) moduleImportList = append(moduleImportList, moduleImportPath)
} }
} }
@@ -206,7 +205,7 @@ func buildCopyModules(c *model.CommandConfig, revel_paths *model.RevelContainer,
return return
} }
// Write the run scripts for the build // Write the run scripts for the build.
func buildWriteScripts(c *model.CommandConfig, app *harness.App) (err error) { func buildWriteScripts(c *model.CommandConfig, app *harness.App) (err error) {
tmplData := map[string]interface{}{ tmplData := map[string]interface{}{
"BinName": filepath.Base(app.BinaryPath), "BinName": filepath.Base(app.BinaryPath),
@@ -237,9 +236,8 @@ func buildWriteScripts(c *model.CommandConfig, app *harness.App) (err error) {
return return
} }
// Checks to see if the target folder exists and can be created // Checks to see if the target folder exists and can be created.
func buildSafetyCheck(destPath string) error { func buildSafetyCheck(destPath string) error {
// First, verify that it is either already empty or looks like a previous // First, verify that it is either already empty or looks like a previous
// build (to avoid clobbering anything) // build (to avoid clobbering anything)
if utils.Exists(destPath) && !utils.Empty(destPath) && !utils.Exists(filepath.Join(destPath, "run.sh")) { if utils.Exists(destPath) && !utils.Empty(destPath) && !utils.Exists(filepath.Join(destPath, "run.sh")) {
@@ -261,6 +259,7 @@ const PACKAGE_RUN_SH = `#!/bin/sh
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd) SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}} "$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}
` `
const PACKAGE_RUN_BAT = `@echo off const PACKAGE_RUN_BAT = `@echo off
{{.BinName}} -importPath {{.ImportPath}} -srcPath "%CD%\src" -runMode {{.Mode}} {{.BinName}} -importPath {{.ImportPath}} -srcPath "%CD%\src" -runMode {{.Mode}}

View File

@@ -1,24 +1,25 @@
package main_test package main_test
import ( import (
"github.com/revel/cmd/model"
"github.com/revel/cmd/revel"
"github.com/revel/cmd/utils"
"github.com/stretchr/testify/assert"
"os" "os"
"path/filepath" "path/filepath"
"testing" "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 // test the commands.
func TestBuild(t *testing.T) { func TestBuild(t *testing.T) {
a := assert.New(t) a := assert.New(t)
gopath := setup("revel-test-build", a) gopath := setup("revel-test-build", a)
t.Run("Build", func(t *testing.T) { t.Run("Build", func(t *testing.T) {
a := assert.New(t) a := assert.New(t)
c := newApp("build-test", model.NEW, nil, a) c := newApp("build-test", model.NEW, nil, a)
main.Commands[model.NEW].RunWith(c) a.Nil(main.Commands[model.NEW].RunWith(c), "failed to run new")
c.Index = model.BUILD c.Index = model.BUILD
c.Build.TargetPath = filepath.Join(gopath, "build-test", "target") c.Build.TargetPath = filepath.Join(gopath, "build-test", "target")
c.Build.ImportPath = c.ImportPath c.Build.ImportPath = c.ImportPath
@@ -26,6 +27,21 @@ func TestBuild(t *testing.T) {
a.True(utils.Exists(filepath.Join(gopath, "build-test", "target"))) 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 !t.Failed() {
if err := os.RemoveAll(gopath); err != nil { if err := os.RemoveAll(gopath); err != nil {
a.Fail("Failed to remove test path") a.Fail("Failed to remove test path")

View File

@@ -6,11 +6,11 @@ package main
import ( import (
"fmt" "fmt"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"os" "os"
"path/filepath" "path/filepath"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
) )
var cmdClean = &Command{ var cmdClean = &Command{
@@ -34,7 +34,7 @@ func init() {
cmdClean.RunWith = cleanApp cmdClean.RunWith = cleanApp
} }
// Update the clean command configuration, using old method // Update the clean command configuration, using old method.
func updateCleanConfig(c *model.CommandConfig, args []string) bool { func updateCleanConfig(c *model.CommandConfig, args []string) bool {
c.Index = model.CLEAN c.Index = model.CLEAN
if len(args) == 0 && c.Clean.ImportPath != "" { if len(args) == 0 && c.Clean.ImportPath != "" {
@@ -48,9 +48,8 @@ func updateCleanConfig(c *model.CommandConfig, args []string) bool {
return true return true
} }
// Clean the source directory of generated files // Clean the source directory of generated files.
func cleanApp(c *model.CommandConfig) (err error) { func cleanApp(c *model.CommandConfig) (err error) {
purgeDirs := []string{ purgeDirs := []string{
filepath.Join(c.AppPath, "app", "tmp"), filepath.Join(c.AppPath, "app", "tmp"),
filepath.Join(c.AppPath, "app", "routes"), filepath.Join(c.AppPath, "app", "routes"),

View File

@@ -1,27 +1,30 @@
package main_test package main_test
import ( import (
"github.com/revel/cmd/model"
"github.com/revel/cmd/revel"
"github.com/revel/cmd/utils"
"github.com/stretchr/testify/assert"
"os" "os"
"path/filepath" "path/filepath"
"testing" "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 // test the commands.
func TestClean(t *testing.T) { func TestClean(t *testing.T) {
a := assert.New(t) a := assert.New(t)
gopath := setup("revel-test-clean", a) gopath := setup("revel-test-clean", a)
t.Run("Clean", func(t *testing.T) { t.Run("Clean", func(t *testing.T) {
a := assert.New(t) a := assert.New(t)
c := newApp("clean-test", model.NEW, nil, a) c := newApp("clean-test", model.NEW, nil, a)
main.Commands[model.NEW].RunWith(c)
a.Nil(main.Commands[model.NEW].RunWith(c), "failed to run new")
c.Index = model.TEST c.Index = model.TEST
main.Commands[model.TEST].RunWith(c) 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")), 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")) "Missing main from path "+filepath.Join(gopath, "clean-test", "app", "tmp", "main.go"))
c.Clean.ImportPath = c.ImportPath c.Clean.ImportPath = c.ImportPath

View File

@@ -1,23 +1,24 @@
package main_test package main_test
import ( import (
"github.com/revel/cmd/logger" "fmt"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"github.com/stretchr/testify/assert"
"go/build" "go/build"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"fmt"
"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 // Test that the event handler can be attached and it dispatches the event received.
func setup(suffix string, a *assert.Assertions) (string) { func setup(suffix string, a *assert.Assertions) string {
temp := os.TempDir() temp := os.TempDir()
wd, _ := os.Getwd() wd, _ := os.Getwd()
utils.InitLogger(wd, logger.LvlInfo) utils.InitLogger(wd, logger.LvlInfo)
gopath := filepath.Join(temp, "revel-test",suffix) gopath := filepath.Join(temp, "revel-test", suffix)
if utils.Exists(gopath) { if utils.Exists(gopath) {
utils.Logger.Info("Removing test path", "path", gopath) utils.Logger.Info("Removing test path", "path", gopath)
if err := os.RemoveAll(gopath); err != nil { if err := os.RemoveAll(gopath); err != nil {
@@ -32,36 +33,38 @@ func setup(suffix string, a *assert.Assertions) (string) {
// But if you change into that directory and read the current folder it is // 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 // /private/var/folders/nz/vv4_9tw56nv9k3tkvyszvwg80000gn/T/revel-test/revel-test-build
// So to make this work on darwin this code was added // So to make this work on darwin this code was added
os.Chdir(gopath) if err := os.Chdir(gopath); err != nil {
panic(err)
}
newwd, _ := os.Getwd() newwd, _ := os.Getwd()
gopath = newwd gopath = newwd
defaultBuild := build.Default defaultBuild := build.Default
defaultBuild.GOPATH = gopath defaultBuild.GOPATH = gopath
build.Default = defaultBuild 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)) 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 return gopath
} }
// Create a new app for the name // Create a new app for the name.
func newApp(name string, command model.COMMAND, precall func(c *model.CommandConfig), a *assert.Assertions) *model.CommandConfig { func newApp(name string, command model.COMMAND, precall func(c *model.CommandConfig), a *assert.Assertions) *model.CommandConfig {
c := &model.CommandConfig{Vendored:true} c := &model.CommandConfig{Vendored: true}
switch command { switch command {
case model.NEW: case model.NEW:
c.New.ImportPath = name c.New.ImportPath = name
c.New.Callback=func() error { c.New.Callback = func() error {
// On callback we will invoke a specific branch of revel so that it works // On callback we will invoke a specific branch of revel so that it works
goModCmd := exec.Command("go", "mod", "tidy") goModCmd := exec.Command("go", "mod", "tidy")
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath) utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
getOutput, _ := goModCmd.CombinedOutput() getOutput, _ := goModCmd.CombinedOutput()
fmt.Printf("Calling go mod tidy %s",string(getOutput)) 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") goModCmd = exec.Command("go", "mod", "edit", "-replace=github.com/revel/revel=github.com/revel/revel@develop")
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath) utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
getOutput, _ = goModCmd.CombinedOutput() getOutput, _ = goModCmd.CombinedOutput()
fmt.Printf("Calling go mod edit %v",string(getOutput)) fmt.Printf("Calling go mod edit %v", string(getOutput))
return nil return nil
} }
@@ -83,7 +86,7 @@ func newApp(name string, command model.COMMAND, precall func(c *model.CommandCon
if precall != nil { if precall != nil {
precall(c) precall(c)
} }
if c.UpdateImportPath()!=nil { if c.UpdateImportPath() != nil {
a.Fail("Unable to update import path") a.Fail("Unable to update import path")
} }

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"go/build" "go/build"
"math/rand" "math/rand"
"net/url"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -15,11 +16,12 @@ import (
"github.com/revel/cmd/model" "github.com/revel/cmd/model"
"github.com/revel/cmd/utils" "github.com/revel/cmd/utils"
"net/url"
) )
const ErrNoSkeleton Error = "failed to find skeleton in filepath"
var cmdNew = &Command{ var cmdNew = &Command{
UsageLine: "new -i [path] -s [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.
@@ -43,7 +45,7 @@ func init() {
cmdNew.UpdateConfig = updateNewConfig cmdNew.UpdateConfig = updateNewConfig
} }
// Called when unable to parse the command line automatically and assumes an old launch // Called when unable to parse the command line automatically and assumes an old launch.
func updateNewConfig(c *model.CommandConfig, args []string) bool { func updateNewConfig(c *model.CommandConfig, args []string) bool {
c.Index = model.NEW c.Index = model.NEW
if len(c.New.Package) > 0 { if len(c.New.Package) > 0 {
@@ -64,15 +66,14 @@ func updateNewConfig(c *model.CommandConfig, args []string) bool {
} }
return true return true
} }
// Call to create a new application // Call to create a new application.
func newApp(c *model.CommandConfig) (err error) { func newApp(c *model.CommandConfig) (err error) {
// Check for an existing folder so we don't clobber it // Check for an existing folder so we don't clobber it
_, err = build.Import(c.ImportPath, "", build.FindOnly) _, err = build.Import(c.ImportPath, "", build.FindOnly)
if err == nil || !utils.Empty(c.AppPath) { if err == nil || !utils.Empty(c.AppPath) {
return utils.NewBuildError("Abort: Import path already exists.", "path", c.ImportPath) return utils.NewBuildError("Abort: Import path already exists.", "path", c.ImportPath, "apppath", c.AppPath)
} }
// checking and setting skeleton // checking and setting skeleton
@@ -93,7 +94,9 @@ func newApp(c *model.CommandConfig) (err error) {
// This kicked off the download of the revel app, not needed for vendor // This kicked off the download of the revel app, not needed for vendor
if !c.Vendored { if !c.Vendored {
// At this point the versions can be set // At this point the versions can be set
c.SetVersions() if err = c.SetVersions(); err != nil {
return
}
} }
// copy files to new app directory // copy files to new app directory
@@ -112,110 +115,75 @@ func newApp(c *model.CommandConfig) (err error) {
fmt.Fprintln(os.Stdout, "Your application has been created in:\n ", c.AppPath) fmt.Fprintln(os.Stdout, "Your application has been created in:\n ", c.AppPath)
// Check to see if it should be run right off // Check to see if it should be run right off
if c.New.Run { if c.New.Run {
runApp(c) // 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 { } else {
fmt.Fprintln(os.Stdout, "\nYou can run it with:\n revel run -a ", c.ImportPath) fmt.Fprintln(os.Stdout, "\nYou can run it with:\n revel run -a", c.ImportPath)
} }
return return
} }
func createModVendor(c *model.CommandConfig) (err error) { func createModVendor(c *model.CommandConfig) (err error) {
utils.Logger.Info("Creating a new mod app") utils.Logger.Info("Creating a new mod app")
goModCmd := exec.Command("go", "mod", "init", filepath.Join(c.New.Package, c.AppName)) goModCmd := exec.Command("go", "mod", "init", filepath.Join(c.New.Package, c.AppName))
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath) utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
utils.Logger.Info("Exec:", "args", goModCmd.Args, "env", goModCmd.Env, "workingdir", goModCmd.Dir) utils.Logger.Info("Exec:", "args", goModCmd.Args, "env", goModCmd.Env, "workingdir", goModCmd.Dir)
getOutput, err := goModCmd.CombinedOutput() getOutput, err := goModCmd.CombinedOutput()
if c.New.Callback != nil { if c.New.Callback != nil {
err = c.New.Callback() err = c.New.Callback()
} }
if err != nil { if err != nil {
return utils.NewBuildIfError(err, string(getOutput)) return utils.NewBuildIfError(err, string(getOutput))
} }
return return
} }
func createDepVendor(c *model.CommandConfig) (err error) { // Used to generate a new secret key.
utils.Logger.Info("Creating a new vendor app")
vendorPath := filepath.Join(c.AppPath, "vendor")
if !utils.DirExists(vendorPath) {
if err := os.MkdirAll(vendorPath, os.ModePerm); err != nil {
return utils.NewBuildError("Failed to create " + vendorPath, "error", err)
}
}
// In order for dep to run there needs to be a source file in the folder
tempPath := filepath.Join(c.AppPath, "tmp")
utils.Logger.Info("Checking for temp folder for source code", "path", tempPath)
if !utils.DirExists(tempPath) {
if err := os.MkdirAll(tempPath, os.ModePerm); err != nil {
return utils.NewBuildIfError(err, "Failed to create " + vendorPath)
}
if err = utils.GenerateTemplate(filepath.Join(tempPath, "main.go"), NEW_MAIN_FILE, nil); err != nil {
return utils.NewBuildIfError(err, "Failed to create main file " + vendorPath)
}
}
// Create a package template file if it does not exist
packageFile := filepath.Join(c.AppPath, "Gopkg.toml")
utils.Logger.Info("Checking for Gopkg.toml", "path", packageFile)
if !utils.Exists(packageFile) {
utils.Logger.Info("Generating Gopkg.toml", "path", packageFile)
if err := utils.GenerateTemplate(packageFile, VENDOR_GOPKG, nil); err != nil {
return utils.NewBuildIfError(err, "Failed to generate template")
}
} else {
utils.Logger.Info("Package file exists in skeleto, skipping adding")
}
getCmd := exec.Command("dep", "ensure", "-v")
utils.CmdInit(getCmd, !c.Vendored, c.AppPath)
utils.Logger.Info("Exec:", "args", getCmd.Args, "env", getCmd.Env, "workingdir", getCmd.Dir)
getOutput, err := getCmd.CombinedOutput()
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 // 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)
} }
// Sets the applicaiton path // Sets the application path.
func setApplicationPath(c *model.CommandConfig) (err error) { func setApplicationPath(c *model.CommandConfig) (err error) {
// 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(c.ImportPath) || strings.HasPrefix(c.ImportPath, ".") {
utils.Logger.Fatalf("Abort: '%s' looks like a directory. Please provide a Go import path instead.",
c.ImportPath)
}
// If we are running a vendored version of Revel we do not need to check for it. // If we are running a vendored version of Revel we do not need to check for it.
if !c.Vendored { if !c.Vendored {
if filepath.IsAbs(c.ImportPath) || strings.HasPrefix(c.ImportPath, ".") {
utils.Logger.Fatalf("Abort: '%s' looks like a directory. Please provide a Go import path instead.",
c.ImportPath)
}
_, err = build.Import(model.RevelImportPath, "", build.FindOnly) _, err = build.Import(model.RevelImportPath, "", build.FindOnly)
if err != nil { if err != nil {
//// Go get the revel project // Go get the revel project
err = c.PackageResolver(model.RevelImportPath) err = c.PackageResolver(model.RevelImportPath)
if err != nil { if err != nil {
return utils.NewBuildIfError(err, "Failed to fetch revel " + model.RevelImportPath) return utils.NewBuildIfError(err, "Failed to fetch revel "+model.RevelImportPath)
} }
} }
} }
@@ -225,7 +193,7 @@ func setApplicationPath(c *model.CommandConfig) (err error) {
return nil return nil
} }
// Set the skeleton path // Set the skeleton path.
func setSkeletonPath(c *model.CommandConfig) (err error) { func setSkeletonPath(c *model.CommandConfig) (err error) {
if len(c.New.SkeletonPath) == 0 { if len(c.New.SkeletonPath) == 0 {
c.New.SkeletonPath = "https://" + RevelSkeletonsImportPath + ":basic/bootstrap4" c.New.SkeletonPath = "https://" + RevelSkeletonsImportPath + ":basic/bootstrap4"
@@ -238,10 +206,10 @@ func setSkeletonPath(c *model.CommandConfig) (err error) {
switch strings.ToLower(sp.Scheme) { switch strings.ToLower(sp.Scheme) {
// TODO Add support for ftp, sftp, scp ?? // TODO Add support for ftp, sftp, scp ??
case "" : case "":
sp.Scheme = "file" sp.Scheme = "file"
fallthrough fallthrough
case "file" : case "file":
fullpath := sp.String()[7:] fullpath := sp.String()[7:]
if !filepath.IsAbs(fullpath) { if !filepath.IsAbs(fullpath) {
fullpath, err = filepath.Abs(fullpath) fullpath, err = filepath.Abs(fullpath)
@@ -252,7 +220,7 @@ func setSkeletonPath(c *model.CommandConfig) (err error) {
c.New.SkeletonPath = fullpath c.New.SkeletonPath = fullpath
utils.Logger.Info("Set skeleton path to ", fullpath) utils.Logger.Info("Set skeleton path to ", fullpath)
if !utils.DirExists(fullpath) { if !utils.DirExists(fullpath) {
return fmt.Errorf("Failed to find skeleton in filepath %s %s", fullpath, sp.String()) return fmt.Errorf("%w %s %s", ErrNoSkeleton, fullpath, sp.String())
} }
case "git": case "git":
fallthrough fallthrough
@@ -264,7 +232,6 @@ func setSkeletonPath(c *model.CommandConfig) (err error) {
} }
default: default:
utils.Logger.Fatal("Unsupported skeleton schema ", "path", c.New.SkeletonPath) utils.Logger.Fatal("Unsupported skeleton schema ", "path", c.New.SkeletonPath)
} }
// TODO check to see if the path needs to be extracted // TODO check to see if the path needs to be extracted
} else { } else {
@@ -273,14 +240,14 @@ func setSkeletonPath(c *model.CommandConfig) (err error) {
return return
} }
// Load skeleton from git // Load skeleton from git.
func newLoadFromGit(c *model.CommandConfig, sp *url.URL) (err error) { func newLoadFromGit(c *model.CommandConfig, sp *url.URL) (err error) {
// This method indicates we need to fetch from a repository using git // This method indicates we need to fetch from a repository using git
// Execute "git clone get <pkg>" // Execute "git clone get <pkg>"
targetPath := filepath.Join(os.TempDir(), "revel", "skeleton") targetPath := filepath.Join(os.TempDir(), "revel", "skeleton")
os.RemoveAll(targetPath) os.RemoveAll(targetPath)
pathpart := strings.Split(sp.Path, ":") pathpart := strings.Split(sp.Path, ":")
getCmd := exec.Command("git", "clone", sp.Scheme + "://" + sp.Host + pathpart[0], targetPath) getCmd := exec.Command("git", "clone", sp.Scheme+"://"+sp.Host+pathpart[0], targetPath)
utils.Logger.Info("Exec:", "args", getCmd.Args) utils.Logger.Info("Exec:", "args", getCmd.Args)
getOutput, err := getCmd.CombinedOutput() getOutput, err := getCmd.CombinedOutput()
if err != nil { if err != nil {
@@ -319,68 +286,4 @@ func copyNewAppFiles(c *model.CommandConfig) (err error) {
// 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"
return utils.CopyFile(filepath.Join(c.AppPath, gitignore), filepath.Join(c.New.SkeletonPath, gitignore)) return utils.CopyFile(filepath.Join(c.AppPath, gitignore), filepath.Join(c.New.SkeletonPath, gitignore))
} }
const (
VENDOR_GOPKG = `#
# Revel Gopkg.toml
#
# If you want to use a specific version of Revel change the branches below
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
required = ["github.com/revel/revel", "github.com/revel/modules"]
# Note to use a specific version changes this to
#
# [[override]]
# version = "0.20.1"
# name = "github.com/revel/modules"
[[override]]
branch = "master"
name = "github.com/revel/modules"
# Note to use a specific version changes this to
#
# [[override]]
# version = "0.20.0"
# name = "github.com/revel/revel"
[[override]]
branch = "master"
name = "github.com/revel/revel"
[[override]]
branch = "master"
name = "github.com/revel/log15"
[[override]]
branch = "master"
name = "github.com/revel/cron"
[[override]]
branch = "master"
name = "github.com/xeonx/timeago"
`
NEW_MAIN_FILE = `package main
`
)

View File

@@ -1,14 +1,15 @@
package main_test package main_test
import ( import (
"github.com/revel/cmd/model"
"github.com/revel/cmd/revel"
"github.com/stretchr/testify/assert"
"os" "os"
"testing" "testing"
"github.com/revel/cmd/model"
main "github.com/revel/cmd/revel"
"github.com/stretchr/testify/assert"
) )
// test the commands // test the commands.
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
a := assert.New(t) a := assert.New(t)
gopath := setup("revel-test-new", a) gopath := setup("revel-test-new", a)
@@ -18,6 +19,12 @@ func TestNew(t *testing.T) {
c := newApp("new-test", model.NEW, nil, a) c := newApp("new-test", model.NEW, nil, a)
a.Nil(main.Commands[model.NEW].RunWith(c), "New failed") 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) { t.Run("Path", func(t *testing.T) {
a := assert.New(t) a := assert.New(t)
c := newApp("new/test/a", model.NEW, nil, a) c := newApp("new/test/a", model.NEW, nil, a)
@@ -46,4 +53,3 @@ func TestNew(t *testing.T) {
} }
} }
} }

View File

@@ -37,10 +37,10 @@ func init() {
cmdPackage.UpdateConfig = updatePackageConfig cmdPackage.UpdateConfig = updatePackageConfig
} }
// Called when unable to parse the command line automatically and assumes an old launch // Called when unable to parse the command line automatically and assumes an old launch.
func updatePackageConfig(c *model.CommandConfig, args []string) bool { func updatePackageConfig(c *model.CommandConfig, args []string) bool {
c.Index = model.PACKAGE c.Index = model.PACKAGE
if len(args)==0 && c.Package.ImportPath!="" { if len(args) == 0 && c.Package.ImportPath != "" {
return true return true
} }
c.Package.ImportPath = args[0] c.Package.ImportPath = args[0]
@@ -48,26 +48,21 @@ func updatePackageConfig(c *model.CommandConfig, args []string) bool {
c.Package.Mode = args[1] c.Package.Mode = args[1]
} }
return true return true
} }
// Called to package the app // Called to package the app.
func packageApp(c *model.CommandConfig) (err error) { func packageApp(c *model.CommandConfig) (err error) {
// Determine the run mode. // Determine the run mode.
mode := DefaultRunMode mode := c.Package.Mode
if len(c.Package.Mode) >= 0 {
mode = c.Package.Mode
}
appImportPath := c.ImportPath appImportPath := c.ImportPath
revel_paths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver)) revelPaths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
if err != nil { if err != nil {
return return
} }
// Remove the archive if it already exists. // Remove the archive if it already exists.
destFile := filepath.Join(c.AppPath, filepath.Base(revel_paths.BasePath)+".tar.gz") destFile := filepath.Join(c.AppPath, filepath.Base(revelPaths.BasePath)+".tar.gz")
if c.Package.TargetPath != "" { if c.Package.TargetPath != "" {
if filepath.IsAbs(c.Package.TargetPath) { if filepath.IsAbs(c.Package.TargetPath) {
destFile = c.Package.TargetPath destFile = c.Package.TargetPath
@@ -80,13 +75,11 @@ func packageApp(c *model.CommandConfig) (err error) {
} }
// Collect stuff in a temp directory. // Collect stuff in a temp directory.
tmpDir, err := ioutil.TempDir("", filepath.Base(revel_paths.BasePath)) tmpDir, err := ioutil.TempDir("", filepath.Base(revelPaths.BasePath))
utils.PanicOnError(err, "Failed to get temp dir") utils.PanicOnError(err, "Failed to get temp dir")
// Build expects the command the build to contain the proper data // Build expects the command the build to contain the proper data
if len(c.Package.Mode) >= 0 { c.Build.Mode = c.Package.Mode
c.Build.Mode = c.Package.Mode
}
c.Build.TargetPath = tmpDir c.Build.TargetPath = tmpDir
c.Build.CopySource = c.Package.CopySource c.Build.CopySource = c.Package.CopySource
if err = buildApp(c); err != nil { if err = buildApp(c); err != nil {

View File

@@ -1,22 +1,23 @@
package main_test package main_test
import ( import (
"github.com/revel/cmd/model"
"github.com/revel/cmd/revel"
"github.com/stretchr/testify/assert"
"os" "os"
"testing" "testing"
"github.com/revel/cmd/model"
main "github.com/revel/cmd/revel"
"github.com/stretchr/testify/assert"
) )
// test the commands // test the commands.
func TestPackage(t *testing.T) { func TestPackage(t *testing.T) {
a := assert.New(t) a := assert.New(t)
gopath := setup("revel-test-package", a) gopath := setup("revel-test-package", a)
t.Run("Package", func(t *testing.T) { t.Run("Package", func(t *testing.T) {
a := assert.New(t) a := assert.New(t)
c := newApp("package-test", model.NEW, nil, a) c := newApp("package-test", model.NEW, nil, a)
main.Commands[model.NEW].RunWith(c) a.Nil(main.Commands[model.NEW].RunWith(c), "failed to run new")
c.Index = model.PACKAGE c.Index = model.PACKAGE
c.Package.ImportPath = c.ImportPath c.Package.ImportPath = c.ImportPath
a.Nil(main.Commands[model.PACKAGE].RunWith(c), "Failed to run package-test") a.Nil(main.Commands[model.PACKAGE].RunWith(c), "Failed to run package-test")

View File

@@ -6,6 +6,7 @@
package main package main
import ( import (
"bytes"
"flag" "flag"
"fmt" "fmt"
"math/rand" "math/rand"
@@ -14,23 +15,28 @@ import (
"strings" "strings"
"time" "time"
"github.com/jessevdk/go-flags"
"github.com/agtorre/gocolorize" "github.com/agtorre/gocolorize"
"github.com/jessevdk/go-flags"
"github.com/revel/cmd/logger" "github.com/revel/cmd/logger"
"github.com/revel/cmd/model" "github.com/revel/cmd/model"
"github.com/revel/cmd/utils" "github.com/revel/cmd/utils"
"bytes"
) )
const ( // Error is used for constant errors.
// RevelCmdImportPath Revel framework cmd tool import path type Error string
RevelCmdImportPath = "github.com/revel/cmd"
// RevelCmdImportPath Revel framework cmd tool import path // 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" RevelSkeletonsImportPath = "github.com/revel/skeletons"
// DefaultRunMode for revel's application // DefaultRunMode for revel's application.
DefaultRunMode = "dev" DefaultRunMode = "dev"
) )
@@ -41,7 +47,7 @@ type Command struct {
UsageLine, Short, Long string UsageLine, Short, Long string
} }
// Name returns command name from usage line // Name returns command name from usage line.
func (cmd *Command) Name() string { func (cmd *Command) Name() string {
name := cmd.UsageLine name := cmd.UsageLine
i := strings.Index(name, " ") i := strings.Index(name, " ")
@@ -51,7 +57,7 @@ func (cmd *Command) Name() string {
return name return name
} }
// The commands // Commands defines the available commands.
var Commands = []*Command{ var Commands = []*Command{
nil, // Safety net, prevent missing index from running nil, // Safety net, prevent missing index from running
cmdNew, cmdNew,
@@ -71,14 +77,14 @@ func main() {
wd, _ := os.Getwd() wd, _ := os.Getwd()
utils.InitLogger(wd, logger.LvlError) utils.InitLogger(wd, logger.LvlError)
parser := flags.NewParser(c, flags.HelpFlag | flags.PassDoubleDash) parser := flags.NewParser(c, flags.HelpFlag|flags.PassDoubleDash)
if len(os.Args) < 2 { if len(os.Args) < 2 {
parser.WriteHelp(os.Stdout) parser.WriteHelp(os.Stdout)
os.Exit(1) os.Exit(1)
} }
if err := ParseArgs(c, parser, os.Args[1:]); err != nil { if err := ParseArgs(c, parser, os.Args[1:]); err != nil {
fmt.Fprint(os.Stderr, err.Error() + "\n") fmt.Fprint(os.Stderr, err.Error()+"\n")
os.Exit(1) os.Exit(1)
} }
@@ -91,6 +97,9 @@ func main() {
utils.InitLogger(wd, logger.LvlWarn) utils.InitLogger(wd, logger.LvlWarn)
} }
// Setup package resolver
c.InitPackageResolver()
if err := c.UpdateImportPath(); err != nil { if err := c.UpdateImportPath(); err != nil {
utils.Logger.Error(err.Error()) utils.Logger.Error(err.Error())
parser.WriteHelp(os.Stdout) parser.WriteHelp(os.Stdout)
@@ -100,19 +109,13 @@ func main() {
command := Commands[c.Index] command := Commands[c.Index]
println("Revel executing:", command.Short) println("Revel executing:", command.Short)
// Setting go paths
// c.InitGoPaths()
// Setup package resolver
c.InitPackageResolver()
if err := command.RunWith(c); err != nil { if err := command.RunWith(c); err != nil {
utils.Logger.Error("Unable to execute", "error", err) utils.Logger.Error("Unable to execute", "error", err)
os.Exit(1) os.Exit(1)
} }
} }
// Parse the arguments passed into the model.CommandConfig // Parse the arguments passed into the model.CommandConfig.
func ParseArgs(c *model.CommandConfig, parser *flags.Parser, args []string) (err error) { func ParseArgs(c *model.CommandConfig, parser *flags.Parser, args []string) (err error) {
var extraArgs []string var extraArgs []string
if ini := flag.String("ini", "none", ""); *ini != "none" { if ini := flag.String("ini", "none", ""); *ini != "none" {
@@ -122,30 +125,30 @@ func ParseArgs(c *model.CommandConfig, parser *flags.Parser, args []string) (err
} else { } else {
if extraArgs, err = parser.ParseArgs(args); err != nil { if extraArgs, err = parser.ParseArgs(args); err != nil {
return return
} else { }
switch parser.Active.Name {
case "new": switch parser.Active.Name {
c.Index = model.NEW case "new":
case "run": c.Index = model.NEW
c.Index = model.RUN case "run":
case "build": c.Index = model.RUN
c.Index = model.BUILD case "build":
case "package": c.Index = model.BUILD
c.Index = model.PACKAGE case "package":
case "clean": c.Index = model.PACKAGE
c.Index = model.CLEAN case "clean":
case "test": c.Index = model.CLEAN
c.Index = model.TEST case "test":
case "version": c.Index = model.TEST
c.Index = model.VERSION case "version":
} c.Index = model.VERSION
} }
} }
if !Commands[c.Index].UpdateConfig(c, extraArgs) { if !Commands[c.Index].UpdateConfig(c, extraArgs) {
buffer := &bytes.Buffer{} buffer := &bytes.Buffer{}
parser.WriteHelp(buffer) parser.WriteHelp(buffer)
err = fmt.Errorf("Invalid command line arguements %v\n%s", extraArgs, buffer.String()) err = fmt.Errorf("%w %v\n%s", ErrInvalidCommandLine, extraArgs, buffer.String())
} }
return return

View File

@@ -5,15 +5,14 @@
package main package main
import ( import (
"strconv"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"strconv"
"github.com/revel/cmd/harness" "github.com/revel/cmd/harness"
"github.com/revel/cmd/model" "github.com/revel/cmd/model"
"github.com/revel/cmd/utils" "github.com/revel/cmd/utils"
"go/build"
"os"
"path/filepath"
) )
var cmdRun = &Command{ var cmdRun = &Command{
@@ -36,13 +35,6 @@ You can set a port as well. For example:
revel run -m prod -p 8080 github.com/revel/examples/chat `, 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.RunWith = runApp cmdRun.RunWith = runApp
cmdRun.UpdateConfig = updateRunConfig cmdRun.UpdateConfig = updateRunConfig
@@ -114,59 +106,57 @@ func updateRunConfig(c *model.CommandConfig, args []string) bool {
return true return true
} }
// Returns true if this is an absolute path or a relative gopath // Returns true if this is an absolute path or a relative gopath.
func runIsImportPath(pathToCheck string) bool { func runIsImportPath(pathToCheck string) bool {
if _, err := build.Import(pathToCheck, "", build.FindOnly); err == nil { return utils.DirExists(pathToCheck)
return true
}
return filepath.IsAbs(pathToCheck)
} }
// Called to run the app // Called to run the app.
func runApp(c *model.CommandConfig) (err error) { func runApp(c *model.CommandConfig) (err error) {
if c.Run.Mode == "" { if c.Run.Mode == "" {
c.Run.Mode = "dev" c.Run.Mode = "dev"
} }
revel_path, err := model.NewRevelPaths(c.Run.Mode, c.ImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver)) revelPath, err := model.NewRevelPaths(c.Run.Mode, c.ImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
if err != nil { if err != nil {
return utils.NewBuildIfError(err, "Revel paths") return utils.NewBuildIfError(err, "Revel paths")
} }
if c.Run.Port > -1 { if c.Run.Port > -1 {
revel_path.HTTPPort = c.Run.Port revelPath.HTTPPort = c.Run.Port
} else { } else {
c.Run.Port = revel_path.HTTPPort c.Run.Port = revelPath.HTTPPort
} }
utils.Logger.Infof("Running %s (%s) in %s mode\n", revel_path.AppName, revel_path.ImportPath, revel_path.RunMode) utils.Logger.Infof("Running %s (%s) in %s mode\n", revelPath.AppName, revelPath.ImportPath, revelPath.RunMode)
utils.Logger.Debug("Base path:", "path", revel_path.BasePath) 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_path.Config.BoolDefault("watch", true) && revel_path.Config.BoolDefault("watch.code", true) { if revelPath.Config.BoolDefault("watch", true) && revelPath.Config.BoolDefault("watch.code", true) {
utils.Logger.Info("Running in watched mode.") utils.Logger.Info("Running in watched mode.")
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v}`, revel_path.RunMode, c.Verbose)
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v}`, revelPath.RunMode, c.GetVerbose())
if c.HistoricMode { if c.HistoricMode {
runMode = revel_path.RunMode runMode = revelPath.RunMode
} }
// **** Never returns. // **** Never returns.
harness.NewHarness(c, revel_path, runMode, c.Run.NoProxy).Run() harness.NewHarness(c, revelPath, runMode, c.Run.NoProxy).Run()
} }
// Else, just build and run the app. // Else, just build and run the app.
utils.Logger.Debug("Running in live build mode.") utils.Logger.Debug("Running in live build mode.")
app, err := harness.Build(c, revel_path) app, err := harness.Build(c, revelPath)
if err != nil { if err != nil {
utils.Logger.Errorf("Failed to build app: %s", err) utils.Logger.Errorf("Failed to build app: %s", err)
} }
app.Port = revel_path.HTTPPort app.Port = revelPath.HTTPPort
var paths []byte var paths []byte
if len(app.PackagePathMap) > 0 { if len(app.PackagePathMap) > 0 {
paths, _ = json.Marshal(app.PackagePathMap) paths, _ = json.Marshal(app.PackagePathMap)
} }
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, app.Paths.RunMode, c.Verbose, string(paths)) runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, app.Paths.RunMode, c.GetVerbose(), string(paths))
if c.HistoricMode { if c.HistoricMode {
runMode = revel_path.RunMode runMode = revelPath.RunMode
} }
app.Cmd(runMode).Run() app.Cmd(runMode).Run(c)
return return
} }

View File

@@ -1,15 +1,16 @@
package main_test package main_test
import ( import (
"github.com/stretchr/testify/assert"
"os" "os"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
// test the commands // test the commands.
func TestRun(t *testing.T) { func TestRun(t *testing.T) {
a := assert.New(t) a := assert.New(t)
gopath := setup("revel-test-run", a) gopath := setup("revel-test-run", a)
// TODO Testing run // TODO Testing run

View File

@@ -52,7 +52,7 @@ func init() {
cmdTest.UpdateConfig = updateTestConfig cmdTest.UpdateConfig = updateTestConfig
} }
// Called to update the config command with from the older stype // Called to update the config command with from the older stype.
func updateTestConfig(c *model.CommandConfig, args []string) bool { func updateTestConfig(c *model.CommandConfig, args []string) bool {
c.Index = model.TEST c.Index = model.TEST
if len(args) == 0 && c.Test.ImportPath != "" { if len(args) == 0 && c.Test.ImportPath != "" {
@@ -74,7 +74,7 @@ func updateTestConfig(c *model.CommandConfig, args []string) bool {
return true return true
} }
// Called to test the application // Called to test the application.
func testApp(c *model.CommandConfig) (err error) { func testApp(c *model.CommandConfig) (err error) {
mode := DefaultRunMode mode := DefaultRunMode
if c.Test.Mode != "" { if c.Test.Mode != "" {
@@ -82,7 +82,7 @@ func testApp(c *model.CommandConfig) (err error) {
} }
// Find and parse app.conf // Find and parse app.conf
revel_path, err := model.NewRevelPaths(mode, c.ImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver)) revelPath, err := model.NewRevelPaths(mode, c.ImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
if err != nil { if err != nil {
return return
} }
@@ -90,7 +90,7 @@ func testApp(c *model.CommandConfig) (err error) {
// todo Ensure that the testrunner is loaded in this mode. // todo Ensure that the testrunner is loaded in this mode.
// Create a directory to hold the test result files. // Create a directory to hold the test result files.
resultPath := filepath.Join(revel_path.BasePath, "test-results") resultPath := filepath.Join(revelPath.BasePath, "test-results")
if err = os.RemoveAll(resultPath); err != nil { if err = os.RemoveAll(resultPath); err != nil {
return utils.NewBuildError("Failed to remove test result directory ", "path", resultPath, "error", err) return utils.NewBuildError("Failed to remove test result directory ", "path", resultPath, "error", err)
} }
@@ -99,12 +99,12 @@ func testApp(c *model.CommandConfig) (err error) {
} }
// 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 {
return utils.NewBuildError("Failed to create test result log file: ", "error", err) return utils.NewBuildError("Failed to create test result log file: ", "error", err)
} }
app, reverr := harness.Build(c, revel_path) app, reverr := harness.Build(c, revelPath)
if reverr != nil { if reverr != nil {
return utils.NewBuildIfError(reverr, "Error building: ") return utils.NewBuildIfError(reverr, "Error building: ")
} }
@@ -112,7 +112,7 @@ func testApp(c *model.CommandConfig) (err error) {
if len(app.PackagePathMap) > 0 { if len(app.PackagePathMap) > 0 {
paths, _ = json.Marshal(app.PackagePathMap) paths, _ = json.Marshal(app.PackagePathMap)
} }
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, app.Paths.RunMode, c.Verbose, string(paths)) runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, app.Paths.RunMode, c.GetVerbose(), string(paths))
if c.HistoricMode { if c.HistoricMode {
runMode = app.Paths.RunMode runMode = app.Paths.RunMode
} }
@@ -128,20 +128,20 @@ func testApp(c *model.CommandConfig) (err error) {
} }
defer cmd.Kill() defer cmd.Kill()
var httpAddr = revel_path.HTTPAddr httpAddr := revelPath.HTTPAddr
if httpAddr == "" { if httpAddr == "" {
httpAddr = "localhost" httpAddr = "localhost"
} }
var httpProto = "http" httpProto := "http"
if revel_path.HTTPSsl { if revelPath.HTTPSsl {
httpProto = "https" httpProto = "https"
} }
// Get a list of tests // Get a list of tests
var baseURL = fmt.Sprintf("%s://%s:%d", httpProto, httpAddr, revel_path.HTTPPort) baseURL := fmt.Sprintf("%s://%s:%d", httpProto, httpAddr, revelPath.HTTPPort)
utils.Logger.Infof("Testing %s (%s) in %s mode URL %s \n", revel_path.AppName, revel_path.ImportPath, mode, baseURL) 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
@@ -154,7 +154,7 @@ func testApp(c *model.CommandConfig) (err error) {
fmt.Println() fmt.Println()
// Run each suite. // Run each suite.
failedResults, overallSuccess := runTestSuites(revel_path, baseURL, resultPath, testSuites) failedResults, overallSuccess := runTestSuites(revelPath, baseURL, resultPath, testSuites)
fmt.Println() fmt.Println()
if overallSuccess { if overallSuccess {
@@ -177,14 +177,14 @@ func testApp(c *model.CommandConfig) (err error) {
return return
} }
// Outputs the results to a file // 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 {
utils.Logger.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 // 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
@@ -193,7 +193,7 @@ 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 *[]tests.TestSuiteDesc, suiteArgument string) *[]tests.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, ".")
@@ -234,7 +234,7 @@ func filterTestSuites(suites *[]tests.TestSuiteDesc, suiteArgument string) *[]te
// in case it hasn't finished starting up yet. // in case it hasn't finished starting up yet.
func getTestsList(baseURL string) (*[]tests.TestSuiteDesc, error) { func getTestsList(baseURL string) (*[]tests.TestSuiteDesc, error) {
var ( var (
err error err error
resp *http.Response resp *http.Response
testSuites []tests.TestSuiteDesc testSuites []tests.TestSuiteDesc
) )
@@ -263,9 +263,8 @@ func getTestsList(baseURL string) (*[]tests.TestSuiteDesc, error) {
return &testSuites, err return &testSuites, err
} }
// Run the testsuites using the container // Run the testsuites using the container.
func runTestSuites(paths *model.RevelContainer, baseURL, resultPath string, testSuites *[]tests.TestSuiteDesc) (*[]tests.TestSuiteResult, bool) { func runTestSuites(paths *model.RevelContainer, baseURL, resultPath string, testSuites *[]tests.TestSuiteDesc) (*[]tests.TestSuiteResult, bool) {
// We can determine the testsuite location by finding the test module and extracting the data from it // We can determine the testsuite location by finding the test module and extracting the data from it
resultFilePath := filepath.Join(paths.ModulePathMap["testrunner"].Path, "app", "views", "TestRunner/SuiteResult.html") resultFilePath := filepath.Join(paths.ModulePathMap["testrunner"].Path, "app", "views", "TestRunner/SuiteResult.html")

View File

@@ -1,18 +1,18 @@
package main_test package main_test
import ( import (
"github.com/revel/cmd/model"
"github.com/revel/cmd/revel"
"github.com/stretchr/testify/assert"
"os" "os"
"testing" "testing"
"github.com/revel/cmd/model"
main "github.com/revel/cmd/revel"
"github.com/stretchr/testify/assert"
) )
// test the commands.
// test the commands
func TestRevelTest(t *testing.T) { func TestRevelTest(t *testing.T) {
a := assert.New(t) a := assert.New(t)
gopath := setup("revel-test-test", a) gopath := setup("revel-test-test", a)
t.Run("Test", func(t *testing.T) { t.Run("Test", func(t *testing.T) {
a := assert.New(t) a := assert.New(t)

View File

@@ -9,11 +9,8 @@
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"github.com/revel/cmd"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"go/ast" "go/ast"
"go/parser" "go/parser"
"go/token" "go/token"
@@ -23,11 +20,14 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"bytes"
"github.com/revel/cmd"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
) )
type ( type (
// The version container // The version container.
VersionCommand struct { VersionCommand struct {
Command *model.CommandConfig // The command Command *model.CommandConfig // The command
revelVersion *model.Version // The Revel framework version revelVersion *model.Version // The Revel framework version
@@ -54,7 +54,7 @@ func init() {
cmdVersion.RunWith = v.RunWith cmdVersion.RunWith = v.RunWith
} }
// Update the version // Update the version.
func (v *VersionCommand) UpdateConfig(c *model.CommandConfig, args []string) bool { func (v *VersionCommand) UpdateConfig(c *model.CommandConfig, args []string) bool {
if len(args) > 0 { if len(args) > 0 {
c.Version.ImportPath = args[0] c.Version.ImportPath = args[0]
@@ -62,7 +62,7 @@ func (v *VersionCommand) UpdateConfig(c *model.CommandConfig, args []string) boo
return true return true
} }
// Displays the version of go and Revel // Displays the version of go and Revel.
func (v *VersionCommand) RunWith(c *model.CommandConfig) (err error) { func (v *VersionCommand) RunWith(c *model.CommandConfig) (err error) {
utils.Logger.Info("Requesting version information", "config", c) utils.Logger.Info("Requesting version information", "config", c)
v.Command = c v.Command = c
@@ -73,7 +73,6 @@ func (v *VersionCommand) RunWith(c *model.CommandConfig) (err error) {
needsUpdates := true needsUpdates := true
versionInfo := "" versionInfo := ""
for x := 0; x < 2 && needsUpdates; x++ { for x := 0; x < 2 && needsUpdates; x++ {
needsUpdates = false
versionInfo, needsUpdates = v.doRepoCheck(x == 0) versionInfo, needsUpdates = v.doRepoCheck(x == 0)
} }
@@ -83,16 +82,18 @@ func (v *VersionCommand) RunWith(c *model.CommandConfig) (err error) {
if e := cmd.Start(); e != nil { if e := cmd.Start(); e != nil {
fmt.Println("Go command error ", e) fmt.Println("Go command error ", e)
} else { } else {
cmd.Wait() if err = cmd.Wait(); err != nil {
return
}
} }
return return
} }
// Checks the Revel repos for the latest version // Checks the Revel repos for the latest version.
func (v *VersionCommand) doRepoCheck(updateLibs bool) (versionInfo string, needsUpdate bool) { func (v *VersionCommand) doRepoCheck(updateLibs bool) (versionInfo string, needsUpdate bool) {
var ( var (
title string title string
localVersion *model.Version localVersion *model.Version
) )
for _, repo := range []string{"revel", "cmd", "modules"} { for _, repo := range []string{"revel", "cmd", "modules"} {
@@ -110,35 +111,12 @@ func (v *VersionCommand) doRepoCheck(updateLibs bool) (versionInfo string, needs
} }
// Only do an update on the first loop, and if specified to update // Only do an update on the first loop, and if specified to update
shouldUpdate := updateLibs && v.Command.Version.Update versionInfo += v.outputVersion(title, repo, localVersion, versonFromRepo)
if v.Command.Version.Update {
if localVersion == nil || (versonFromRepo != nil && versonFromRepo.Newer(localVersion)) {
needsUpdate = true
if shouldUpdate {
v.doUpdate(title, repo, localVersion, versonFromRepo)
v.updateLocalVersions()
}
}
}
versionInfo = versionInfo + v.outputVersion(title, repo, localVersion, versonFromRepo)
} }
return return
} }
// Checks for updates if needed // Prints out the local and remote versions, calls update if needed.
func (v *VersionCommand) doUpdate(title, repo string, local, remote *model.Version) {
utils.Logger.Info("Updating package", "package", title, "repo", repo)
fmt.Println("Attempting to update package", title)
if err := v.Command.PackageResolver(repo); err != nil {
utils.Logger.Error("Unable to update repo", "repo", repo, "error", err)
} else if repo == "github.com/revel/cmd/revel" {
// One extra step required here to run the install for the command
utils.Logger.Fatal("Revel command tool was updated, you must manually run the following command before continuing\ngo install github.com/revel/cmd/revel")
}
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) { func (v *VersionCommand) outputVersion(title, repo string, local, remote *model.Version) (output string) {
buffer := &bytes.Buffer{} buffer := &bytes.Buffer{}
remoteVersion := "Unknown" remoteVersion := "Unknown"
@@ -154,7 +132,7 @@ func (v *VersionCommand) outputVersion(title, repo string, local, remote *model.
return buffer.String() return buffer.String()
} }
// Returns the version from the repository // Returns the version from the repository.
func (v *VersionCommand) versionFromRepo(repoName, branchName, fileName string) (version *model.Version, err error) { func (v *VersionCommand) versionFromRepo(repoName, branchName, fileName string) (version *model.Version, err error) {
if branchName == "" { if branchName == "" {
branchName = "master" branchName = "master"
@@ -176,10 +154,6 @@ func (v *VersionCommand) versionFromRepo(repoName, branchName, fileName string)
return v.versionFromBytes(body) return v.versionFromBytes(body)
} }
// Returns version information from a file called version on the gopath
func (v *VersionCommand) compareAndUpdateVersion(remoteVersion *model.Version, localVersion *model.Version) (err error) {
return
}
func (v *VersionCommand) versionFromFilepath(sourcePath string) (version *model.Version, err error) { func (v *VersionCommand) versionFromFilepath(sourcePath string) (version *model.Version, err error) {
utils.Logger.Info("Fullpath to revel", "dir", sourcePath) utils.Logger.Info("Fullpath to revel", "dir", sourcePath)
@@ -190,7 +164,7 @@ func (v *VersionCommand) versionFromFilepath(sourcePath string) (version *model.
return v.versionFromBytes(sourceStream) return v.versionFromBytes(sourceStream)
} }
// Returns version information from a file called version on the gopath // Returns version information from a file called version on the gopath.
func (v *VersionCommand) versionFromBytes(sourceStream []byte) (version *model.Version, err error) { func (v *VersionCommand) versionFromBytes(sourceStream []byte) (version *model.Version, err error) {
fset := token.NewFileSet() // positions are relative to fset fset := token.NewFileSet() // positions are relative to fset
@@ -216,7 +190,9 @@ func (v *VersionCommand) versionFromBytes(sourceStream []byte) (version *model.V
r := spec.Values[0].(*ast.BasicLit) r := spec.Values[0].(*ast.BasicLit)
switch spec.Names[0].Name { switch spec.Names[0].Name {
case "Version": case "Version":
version.ParseVersion(strings.Replace(r.Value, `"`, "", -1)) if err = version.ParseVersion(strings.ReplaceAll(r.Value, `"`, "")); err != nil {
return
}
case "BuildDate": case "BuildDate":
version.BuildDate = r.Value version.BuildDate = r.Value
case "MinimumGoVersion": case "MinimumGoVersion":
@@ -227,13 +203,22 @@ func (v *VersionCommand) versionFromBytes(sourceStream []byte) (version *model.V
return return
} }
// Fetch the local version of revel from the file system // Fetch the local version of revel from the file system.
func (v *VersionCommand) updateLocalVersions() { func (v *VersionCommand) updateLocalVersions() {
v.cmdVersion = &model.Version{} v.cmdVersion = &model.Version{}
v.cmdVersion.ParseVersion(cmd.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.BuildDate = cmd.BuildDate
v.cmdVersion.MinGoVersion = cmd.MinimumGoVersion 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) pathMap, err := utils.FindSrcPaths(v.Command.AppPath, []string{model.RevelImportPath, model.RevelModulesImportPath}, v.Command.PackageResolver)
if err != nil { if err != nil {
utils.Logger.Warn("Unable to extract version information from Revel library", "path", pathMap[model.RevelImportPath], "error", err) utils.Logger.Warn("Unable to extract version information from Revel library", "path", pathMap[model.RevelImportPath], "error", err)
@@ -249,6 +234,4 @@ func (v *VersionCommand) updateLocalVersions() {
if err != nil { if err != nil {
utils.Logger.Warn("Unable to extract version information from Revel Modules", "path", pathMap[model.RevelModulesImportPath], "error", err) utils.Logger.Warn("Unable to extract version information from Revel Modules", "path", pathMap[model.RevelModulesImportPath], "error", err)
} }
return
} }

View File

@@ -1,18 +1,20 @@
package main_test package main_test
import ( import (
"github.com/revel/cmd/model" "errors"
"github.com/revel/cmd/revel"
"github.com/stretchr/testify/assert"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/revel/cmd/model"
main "github.com/revel/cmd/revel"
"github.com/stretchr/testify/assert"
) )
// test the commands // test the commands.
func TestVersion(t *testing.T) { func TestVersion(t *testing.T) {
a := assert.New(t) a := assert.New(t)
gopath := setup("revel-test-version", a) gopath := setup("revel-test-version", a)
t.Run("Version", func(t *testing.T) { t.Run("Version", func(t *testing.T) {
a := assert.New(t) a := assert.New(t)
@@ -33,9 +35,10 @@ func TestVersion(t *testing.T) {
c.Version.ImportPath = c.ImportPath c.Version.ImportPath = c.ImportPath
a.Nil(main.Commands[model.VERSION].RunWith(c), "Failed to run version-test") a.Nil(main.Commands[model.VERSION].RunWith(c), "Failed to run version-test")
}) })
if !t.Failed() { if !t.Failed() {
if err := os.RemoveAll(gopath); err != nil { if err := os.RemoveAll(gopath); err != nil && !errors.Is(err, os.ErrNotExist) {
a.Fail("Failed to remove test path") a.Fail("Failed to remove test path", err.Error())
} }
} }
} }

View File

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

View File

@@ -1,10 +1,12 @@
package utils package utils
import ( import (
"errors"
"fmt" "fmt"
"github.com/revel/cmd/logger"
"strconv"
"regexp" "regexp"
"strconv"
"github.com/revel/cmd/logger"
) )
type ( type (
@@ -15,7 +17,7 @@ type (
} }
) )
// Returns a new builed error // Returns a new builed error.
func NewBuildError(message string, args ...interface{}) (b *BuildError) { func NewBuildError(message string, args ...interface{}) (b *BuildError) {
Logger.Info(message, args...) Logger.Info(message, args...)
b = &BuildError{} b = &BuildError{}
@@ -26,38 +28,40 @@ func NewBuildError(message string, args ...interface{}) (b *BuildError) {
return b return b
} }
// Returns a new BuildError if err is not nil // Returns a new BuildError if err is not nil.
func NewBuildIfError(err error, message string, args ...interface{}) (b error) { func NewBuildIfError(err error, message string, args ...interface{}) (b error) {
if err != nil { if err != nil {
if berr, ok := err.(*BuildError); ok { var berr *BuildError
if errors.As(err, &berr) {
// This is already a build error so just append the args // This is already a build error so just append the args
berr.Args = append(berr.Args, args...) berr.Args = append(berr.Args, args...)
return berr return berr
} else {
args = append(args, "error", err.Error())
b = NewBuildError(message, args...)
} }
args = append(args, "error", err.Error())
b = NewBuildError(message, args...)
} }
return return
} }
// BuildError implements Error() string // BuildError implements Error() string.
func (b *BuildError) Error() string { func (b *BuildError) Error() string {
return fmt.Sprint(b.Message, b.Args) return fmt.Sprint(b.Message, b.Args)
} }
// 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(importPath, errorLink string, error error) *SourceError { func NewCompileError(importPath, errorLink string, err error) *SourceError {
// Get the stack from the error // Get the stack from the error
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`). errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
FindSubmatch([]byte(error.Error())) FindSubmatch([]byte(err.Error()))
if errorMatch == nil { if errorMatch == nil {
errorMatch = regexp.MustCompile(`(?m)^(.*?):(\d+):\s(.*?)$`).FindSubmatch([]byte(error.Error())) errorMatch = regexp.MustCompile(`(?m)^(.*?):(\d+):\s(.*?)$`).FindSubmatch([]byte(err.Error()))
if errorMatch == nil { if errorMatch == nil {
Logger.Error("Failed to parse build errors", "error", error) Logger.Error("Failed to parse build errors", "error", err)
return &SourceError{ return &SourceError{
SourceType: "Go code", SourceType: "Go code",
Title: "Go Compilation Error", Title: "Go Compilation Error",
@@ -67,16 +71,15 @@ func NewCompileError(importPath, errorLink string, error error) *SourceError {
errorMatch = append(errorMatch, errorMatch[3]) errorMatch = append(errorMatch, errorMatch[3])
Logger.Error("Build errors", "errors", error) Logger.Error("Build errors", "errors", err)
} }
// 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 = relFilename absFilename = relFilename
line, _ = strconv.Atoi(string(errorMatch[2])) line, _ = strconv.Atoi(string(errorMatch[2]))
description = string(errorMatch[4]) description = string(errorMatch[4])
compileError = &SourceError{ compileError = &SourceError{
SourceType: "Go code", SourceType: "Go code",
Title: "Go Compilation Error", Title: "Go Compilation Error",
@@ -95,10 +98,10 @@ func NewCompileError(importPath, errorLink string, error error) *SourceError {
fileStr, err := ReadLines(absFilename) fileStr, err := ReadLines(absFilename)
if err != nil { if err != nil {
compileError.MetaError = absFilename + ": " + err.Error() compileError.MetaError = absFilename + ": " + err.Error()
Logger.Info("Unable to readlines " + compileError.MetaError, "error", err) Logger.Info("Unable to readlines "+compileError.MetaError, "error", err)
return compileError return compileError
} }
compileError.SourceLines = fileStr compileError.SourceLines = fileStr
return compileError return compileError
} }

View File

@@ -1,19 +1,28 @@
package utils package utils
import ( import (
"bytes"
"go/build" "go/build"
"os" "os"
"os/exec" "os/exec"
"strings"
"bytes"
"path/filepath" "path/filepath"
"strings"
) )
// Initialize the command based on the GO environment // Initialize the command based on the GO environment.
func CmdInit(c *exec.Cmd, addGoPath bool, basePath string) { func CmdInit(c *exec.Cmd, addGoPath bool, basePath string) {
c.Dir = basePath c.Dir = basePath
// Dep does not like paths that are not real, convert all paths in go to real paths // 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{} realPath := &bytes.Buffer{}
env := []string{}
if addGoPath { if addGoPath {
for _, p := range filepath.SplitList(build.Default.GOPATH) { for _, p := range filepath.SplitList(build.Default.GOPATH) {
rp, _ := filepath.EvalSymlinks(p) rp, _ := filepath.EvalSymlinks(p)
@@ -23,14 +32,18 @@ func CmdInit(c *exec.Cmd, addGoPath bool, basePath string) {
realPath.WriteString(rp) realPath.WriteString(rp)
} }
// Go 1.8 fails if we do not include the GOROOT // Go 1.8 fails if we do not include the GOROOT
c.Env = []string{"GOPATH=" + realPath.String(), "GOROOT=" + os.Getenv("GOROOT")} env = []string{"GOPATH=" + realPath.String(), "GOROOT=" + os.Getenv("GOROOT")}
} }
// Fetch the rest of the env variables
for _, e := range os.Environ() { for _, e := range os.Environ() {
pair := strings.Split(e, "=") pair := strings.Split(e, "=")
if pair[0] == "GOPATH" || pair[0] == "GOROOT" { // Always exclude gomodcache
if pair[0] == "GOMODCACHE" {
continue
} else if !addGoPath && (pair[0] == "GOPATH" || pair[0] == "GOROOT") {
continue continue
} }
c.Env = append(c.Env, e) env = append(env, e)
} }
} return env
}

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
) )
// The error is a wrapper for the // The error is a wrapper for the.
type ( type (
SourceError struct { SourceError struct {
SourceType string // The type of source that failed to build. SourceType string // The type of source that failed to build.
@@ -23,27 +23,28 @@ type (
IsError bool IsError bool
} }
) )
// Return a new error object
// Return a new error object.
func NewError(source, title, path, description string) *SourceError { func NewError(source, title, path, description string) *SourceError {
return &SourceError{ return &SourceError{
SourceType:source, SourceType: source,
Title:title, Title: title,
Path:path, Path: path,
Description:description, Description: description,
} }
} }
// Creates a link based on the configuration setting "errors.link" // Creates a link based on the configuration setting "errors.link".
func (e *SourceError) SetLink(errorLink string) { func (e *SourceError) SetLink(errorLink string) {
errorLink = strings.Replace(errorLink, "{{Path}}", e.Path, -1) errorLink = strings.ReplaceAll(errorLink, "{{Path}}", e.Path)
errorLink = strings.Replace(errorLink, "{{Line}}", strconv.Itoa(e.Line), -1) errorLink = strings.ReplaceAll(errorLink, "{{Line}}", strconv.Itoa(e.Line))
e.Link = "<a href=" + errorLink + ">" + e.Path + ":" + strconv.Itoa(e.Line) + "</a>" e.Link = "<a href=" + errorLink + ">" + e.Path + ":" + strconv.Itoa(e.Line) + "</a>"
} }
// Error method constructs a plaintext version of the error, taking // Error method constructs a plaintext version of the error, taking
// account that fields are optionally set. Returns e.g. Compilation Error // account that fields are optionally set. Returns e.g. Compilation Error
// (in views/header.html:51): expected right delim in end; got "}" // (in views/header.html:51): expected right delim in end; got "}".
func (e *SourceError) Error() string { func (e *SourceError) Error() string {
if e == nil { if e == nil {
panic("opps") panic("opps")
@@ -82,7 +83,7 @@ func (e *SourceError) ContextSource() []SourceLine {
end = len(e.SourceLines) end = len(e.SourceLines)
} }
lines := make([]SourceLine, end - start) lines := make([]SourceLine, end-start)
for i, src := range e.SourceLines[start:end] { for i, src := range e.SourceLines[start:end] {
fileLine := start + i + 1 fileLine := start + i + 1
lines[i] = SourceLine{src, fileLine, fileLine == e.Line} lines[i] = SourceLine{src, fileLine, fileLine == e.Line}

View File

@@ -4,14 +4,15 @@ import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"fmt"
"errors" "errors"
"fmt"
"html/template" "html/template"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages"
) )
@@ -39,9 +40,8 @@ func ReadLines(filename string) ([]string, error) {
return strings.Split(string(dataBytes), "\n"), nil return strings.Split(string(dataBytes), "\n"), nil
} }
// Copy file returns error // Copy file returns error.
func CopyFile(destFilename, srcFilename string) (err error) { func CopyFile(destFilename, srcFilename string) (err error) {
destFile, err := os.Create(destFilename) destFile, err := os.Create(destFilename)
if err != nil { if err != nil {
return NewBuildIfError(err, "Failed to create file", "file", destFilename) return NewBuildIfError(err, "Failed to create file", "file", destFilename)
@@ -105,11 +105,11 @@ func GenerateTemplate(filename, templateSource string, args map[string]interface
return return
} }
// Given the target path and source path and data. A template // Given the target path and source path and data. A template.
func RenderTemplate(destPath, srcPath string, data interface{}) (err error) { func RenderTemplate(destPath, srcPath string, data interface{}) (err error) {
tmpl, err := template.ParseFiles(srcPath) tmpl, err := template.ParseFiles(srcPath)
if err != nil { if err != nil {
return NewBuildIfError(err, "Failed to parse template " + srcPath) return NewBuildIfError(err, "Failed to parse template "+srcPath)
} }
f, err := os.Create(destPath) f, err := os.Create(destPath)
@@ -119,26 +119,26 @@ func RenderTemplate(destPath, srcPath string, data interface{}) (err error) {
err = tmpl.Execute(f, data) err = tmpl.Execute(f, data)
if err != nil { if err != nil {
return NewBuildIfError(err, "Failed to Render template " + srcPath) return NewBuildIfError(err, "Failed to Render template "+srcPath)
} }
err = f.Close() err = f.Close()
if err != nil { if err != nil {
return NewBuildIfError(err, "Failed to close file stream " + destPath) return NewBuildIfError(err, "Failed to close file stream "+destPath)
} }
return return
} }
// Given the target path and source path and data. A template // Given the target path and source path and data. A template.
func RenderTemplateToStream(output io.Writer, srcPath []string, data interface{}) (err error) { func RenderTemplateToStream(output io.Writer, srcPath []string, data interface{}) (err error) {
tmpl, err := template.ParseFiles(srcPath...) tmpl, err := template.ParseFiles(srcPath...)
if err != nil { if err != nil {
return NewBuildIfError(err, "Failed to parse template " + srcPath[0]) return NewBuildIfError(err, "Failed to parse template "+srcPath[0])
} }
err = tmpl.Execute(output, data) err = tmpl.Execute(output, data)
if err != nil { if err != nil {
return NewBuildIfError(err, "Failed to render template " + srcPath[0]) return NewBuildIfError(err, "Failed to render template "+srcPath[0])
} }
return return
} }
@@ -148,10 +148,11 @@ func MustChmod(filename string, mode os.FileMode) {
PanicOnError(err, fmt.Sprintf("Failed to chmod %d %q", mode, filename)) PanicOnError(err, fmt.Sprintf("Failed to chmod %d %q", mode, filename))
} }
// Called if panic // Called if panic.
func PanicOnError(err error, msg string) { func PanicOnError(err error, msg string) {
if revErr, ok := err.(*SourceError); (ok && revErr != nil) || (!ok && err != nil) { var serr *SourceError
Logger.Panicf("Abort: %s: %s %s", msg, revErr, err) if (errors.As(err, &serr) && serr != nil) || err != nil {
Logger.Panicf("Abort: %s: %s %s", msg, serr, err)
} }
} }
@@ -181,15 +182,14 @@ func CopyDir(destDir, srcDir string, data map[string]interface{}) error {
if info.IsDir() { if info.IsDir() {
err := os.MkdirAll(filepath.Join(destDir, relSrcPath), 0777) err := os.MkdirAll(filepath.Join(destDir, relSrcPath), 0777)
if !os.IsExist(err) { if !os.IsExist(err) {
return NewBuildIfError(err, "Failed to create directory", "path", destDir + "/" + relSrcPath) return NewBuildIfError(err, "Failed to create directory", "path", destDir+"/"+relSrcPath)
} }
return nil return nil
} }
// If this file ends in ".template", render it as a template. // If this file ends in ".template", render it as a template.
if strings.HasSuffix(relSrcPath, ".template") { if strings.HasSuffix(relSrcPath, ".template") {
return RenderTemplate(destPath[:len(destPath)-len(".template")], srcPath, data)
return RenderTemplate(destPath[:len(destPath) - len(".template")], srcPath, data)
} }
// Else, just copy it over. // Else, just copy it over.
@@ -198,12 +198,13 @@ func CopyDir(destDir, srcDir string, data map[string]interface{}) error {
}) })
} }
// Shortcut to fsWalk // Shortcut to fsWalk.
func Walk(root string, walkFn filepath.WalkFunc) error { func Walk(root string, walkFn filepath.WalkFunc) error {
return fsWalk(root, root, walkFn) return fsWalk(root, root, walkFn)
} }
// Walk the tree using the function // Walk the path tree using the function
// Every file found will call the function.
func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error { func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
fsWalkFunc := func(path string, info os.FileInfo, err error) error { fsWalkFunc := func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
@@ -218,7 +219,7 @@ func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
path = filepath.Join(linkName, name) path = filepath.Join(linkName, name)
if err == nil && info.Mode() & os.ModeSymlink == os.ModeSymlink { if err == nil && info.Mode()&os.ModeSymlink == os.ModeSymlink {
var symlinkPath string var symlinkPath string
symlinkPath, err = filepath.EvalSymlinks(path) symlinkPath, err = filepath.EvalSymlinks(path)
if err != nil { if err != nil {
@@ -243,7 +244,7 @@ func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
return err return err
} }
// Tar gz the folder // Tar gz the folder.
func TarGzDir(destFilename, srcDir string) (name string, err error) { func TarGzDir(destFilename, srcDir string) (name string, err error) {
zipFile, err := os.Create(destFilename) zipFile, err := os.Create(destFilename)
if err != nil { if err != nil {
@@ -265,6 +266,10 @@ func TarGzDir(destFilename, srcDir string) (name string, err error) {
}() }()
err = fsWalk(srcDir, srcDir, func(srcPath string, info os.FileInfo, err error) error { err = fsWalk(srcDir, srcDir, func(srcPath string, info os.FileInfo, err error) error {
if err != nil {
Logger.Debugf("error in walkFn: %s", err)
}
if info.IsDir() { if info.IsDir() {
return nil return nil
} }
@@ -299,7 +304,7 @@ func TarGzDir(destFilename, srcDir string) (name string, err error) {
return zipFile.Name(), err return zipFile.Name(), err
} }
// Return true if the file exists // Return true if the file exists.
func Exists(filename string) bool { func Exists(filename string) bool {
_, err := os.Stat(filename) _, err := os.Stat(filename)
return err == nil return err == nil
@@ -308,9 +313,13 @@ func Exists(filename string) bool {
// empty returns true if the given directory is empty. // empty returns true if the given directory is empty.
// the directory must exist. // the directory must exist.
func Empty(dirname string) bool { func Empty(dirname string) bool {
if !DirExists(dirname) {
return true
}
dir, err := os.Open(dirname) dir, err := os.Open(dirname)
if err != nil { if err != nil {
Logger.Infof("error opening directory: %s", err) Logger.Infof("error opening directory: %s", err)
return false
} }
defer func() { defer func() {
_ = dir.Close() _ = dir.Close()
@@ -319,10 +328,11 @@ func Empty(dirname string) bool {
return len(results) == 0 return len(results) == 0
} }
// Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory // 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) { func FindSrcPaths(appPath string, packageList []string, packageResolver func(pkgName string) error) (sourcePathsmap map[string]string, err error) {
sourcePathsmap, missingList, err := findSrcPaths(appPath, packageList) sourcePathsmap, missingList, err := findSrcPaths(appPath, packageList)
if err != nil && packageResolver != nil { 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 { for _, item := range missingList {
if err = packageResolver(item); err != nil { if err = packageResolver(item); err != nil {
return return
@@ -339,20 +349,33 @@ func FindSrcPaths(appPath string, packageList []string, packageResolver func(pkg
return return
} }
var NO_APP_FOUND = errors.New("No app found") // Error is used for constant errors.
var NO_REVEL_FOUND = errors.New("No revel found") type Error string
// Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory // Error implements the error interface.
func findSrcPaths(appPath string, packagesList []string) (sourcePathsmap map[string]string, missingList[] string, err error) { 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 // Use packages to fetch
// by not specifying env, we will use the default env // by not specifying env, we will use the default env
config := &packages.Config{ config := &packages.Config{
Mode: packages.NeedName | packages.NeedFiles, Mode: packages.NeedName | packages.NeedFiles | packages.NeedDeps,
Dir:appPath, Dir: appPath,
} }
config.Env = ReducedEnv(false)
sourcePathsmap = map[string]string{} 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...) 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) Logger.Info("Loaded packages ", "len results", len(pkgs), "error", err, "basedir", appPath)
for _, packageName := range packagesList { for _, packageName := range packagesList {
found := false found := false
@@ -361,22 +384,26 @@ func findSrcPaths(appPath string, packagesList []string) (sourcePathsmap map[str
log.Info("Found package", "package", pck.ID) log.Info("Found package", "package", pck.ID)
if pck.ID == packageName { if pck.ID == packageName {
if pck.Errors != nil && len(pck.Errors) > 0 { if pck.Errors != nil && len(pck.Errors) > 0 {
log.Info("Error ", "count", len(pck.Errors), "App Import Path", pck.ID, "errors", pck.Errors) log.Error("Error ", "count", len(pck.Errors), "App Import Path", pck.ID, "filesystem path", pck.PkgPath, "errors", pck.Errors)
// continue
} }
//a,_ := pck.MarshalJSON() // a,_ := pck.MarshalJSON()
log.Info("Found ", "count", len(pck.GoFiles), "App Import Path", pck.ID, "apppath", appPath) log.Info("Found ", "count", len(pck.GoFiles), "App Import Path", pck.ID, "apppath", appPath)
sourcePathsmap[packageName] = filepath.Dir(pck.GoFiles[0]) if len(pck.GoFiles) > 0 {
found = true sourcePathsmap[packageName] = filepath.Dir(pck.GoFiles[0])
found = true
}
} }
} }
if !found { if !found {
if packageName == "github.com/revel/revel" { if packageName == "github.com/revel/revel" {
err = NO_REVEL_FOUND err = ErrNoRevel
} else { } else {
err = NO_APP_FOUND err = ErrNoApp
} }
missingList = append(missingList, packageName) missingList = append(missingList, packageName)
} }
} }
return return
} }

View File

@@ -2,10 +2,11 @@ package utils
import ( import (
"fmt" "fmt"
"github.com/revel/cmd/logger"
"github.com/revel/config"
"os" "os"
"strings" "strings"
"github.com/revel/cmd/logger"
"github.com/revel/config"
) )
var Logger = logger.New() var Logger = logger.New()
@@ -31,8 +32,8 @@ func InitLogger(basePath string, logLevel logger.LogLevel) {
} }
// This function is to throw a panic that may be caught by the packger so it can perform the needed // This function is to throw a panic that may be caught by the packger so it can perform the needed
// imports // imports.
func Retry(format string, args ...interface{}) { func Retryf(format string, args ...interface{}) {
// Ensure the user's command prompt starts on the next line. // Ensure the user's command prompt starts on the next line.
if !strings.HasSuffix(format, "\n") { if !strings.HasSuffix(format, "\n") {
format += "\n" format += "\n"

View File

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

View File

@@ -6,11 +6,11 @@ package cmd
const ( const (
// Version current Revel version // Version current Revel version
Version = "1.0.0-dev" Version = "1.2.0-dev"
// BuildDate latest commit/release date // BuildDate latest commit/release date
BuildDate = "2018-10-30" BuildDate = "2022-04-11"
// MinimumGoVersion minimum required Go version for Revel // MinimumGoVersion minimum required Go version for Revel
MinimumGoVersion = ">= go1.8" MinimumGoVersion = ">= go1.17"
) )

View File

@@ -9,11 +9,11 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
"time"
"github.com/fsnotify/fsnotify"
"github.com/revel/cmd/model" "github.com/revel/cmd/model"
"github.com/revel/cmd/utils" "github.com/revel/cmd/utils"
"github.com/fsnotify/fsnotify"
"time"
) )
// Listener is an interface for receivers of filesystem events. // Listener is an interface for receivers of filesystem events.
@@ -46,16 +46,16 @@ type Watcher struct {
timerMutex *sync.Mutex // A mutex to prevent concurrent updates timerMutex *sync.Mutex // A mutex to prevent concurrent updates
refreshChannel chan *utils.SourceError refreshChannel chan *utils.SourceError
refreshChannelCount int refreshChannelCount int
refreshTimerMS time.Duration // The number of milliseconds between refreshing builds refreshInterval time.Duration // The interval between refreshing builds
} }
// Creates a new watched based on the container // Creates a new watched based on the container.
func NewWatcher(paths *model.RevelContainer, eagerRefresh bool) *Watcher { func NewWatcher(paths *model.RevelContainer, eagerRefresh bool) *Watcher {
return &Watcher{ return &Watcher{
forceRefresh: true, forceRefresh: true,
lastError: -1, lastError: -1,
paths: paths, paths: paths,
refreshTimerMS: time.Duration(paths.Config.IntDefault("watch.rebuild.delay", 10)), refreshInterval: time.Duration(paths.Config.IntDefault("watch.rebuild.delay", 1000)) * time.Millisecond,
eagerRefresh: eagerRefresh || eagerRefresh: eagerRefresh ||
paths.DevMode && paths.DevMode &&
paths.Config.BoolDefault("watch", true) && paths.Config.BoolDefault("watch", true) &&
@@ -109,9 +109,7 @@ func (w *Watcher) Listen(listener Listener, roots ...string) {
continue continue
} }
var watcherWalker func(path string, info os.FileInfo, err error) error watcherWalker := func(path string, info os.FileInfo, err error) error {
watcherWalker = func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
utils.Logger.Fatal("Watcher: Error walking path:", "error", err) utils.Logger.Fatal("Watcher: Error walking path:", "error", err)
return nil return nil
@@ -150,7 +148,6 @@ func (w *Watcher) Listen(listener Listener, roots ...string) {
// NotifyWhenUpdated notifies the watcher when a file event is received. // NotifyWhenUpdated notifies the watcher when a file event is received.
func (w *Watcher) NotifyWhenUpdated(listener Listener, watcher *fsnotify.Watcher) { func (w *Watcher) NotifyWhenUpdated(listener Listener, watcher *fsnotify.Watcher) {
for { for {
select { select {
case ev := <-watcher.Events: case ev := <-watcher.Events:
@@ -166,7 +163,10 @@ func (w *Watcher) NotifyWhenUpdated(listener Listener, watcher *fsnotify.Watcher
} else { } else {
// Run refresh in parallel // Run refresh in parallel
go func() { go func() {
w.notifyInProcess(listener) if err := w.notifyInProcess(listener); err != nil {
utils.Logger.Error("failed to notify",
"error", err)
}
}() }()
} }
} }
@@ -205,7 +205,8 @@ func (w *Watcher) Notify() *utils.SourceError {
break break
} }
utils.Logger.Info("Watcher:Notify refresh state", "Current Index", i, " last error index", w.lastError) utils.Logger.Info("Watcher:Notify refresh state", "Current Index", i, " last error index", w.lastError,
"force", w.forceRefresh, "refresh", refresh, "lastError", w.lastError == i)
if w.forceRefresh || refresh || w.lastError == i { if w.forceRefresh || refresh || w.lastError == i {
var err *utils.SourceError var err *utils.SourceError
if w.serial { if w.serial {
@@ -217,10 +218,10 @@ func (w *Watcher) Notify() *utils.SourceError {
w.lastError = i w.lastError = i
w.forceRefresh = true w.forceRefresh = true
return err return err
} else {
w.lastError = -1
w.forceRefresh = false
} }
w.lastError = -1
w.forceRefresh = false
} }
} }
@@ -228,7 +229,7 @@ func (w *Watcher) Notify() *utils.SourceError {
} }
// Build a queue for refresh notifications // Build a queue for refresh notifications
// this will not return until one of the queue completes // this will not return until one of the queue completes.
func (w *Watcher) notifyInProcess(listener Listener) (err *utils.SourceError) { func (w *Watcher) notifyInProcess(listener Listener) (err *utils.SourceError) {
shouldReturn := false shouldReturn := false
// This code block ensures that either a timer is created // This code block ensures that either a timer is created
@@ -240,11 +241,11 @@ func (w *Watcher) notifyInProcess(listener Listener) (err *utils.SourceError) {
w.forceRefresh = true w.forceRefresh = true
if w.refreshTimer != nil { if w.refreshTimer != nil {
utils.Logger.Info("Found existing timer running, resetting") utils.Logger.Info("Found existing timer running, resetting")
w.refreshTimer.Reset(time.Millisecond * w.refreshTimerMS) w.refreshTimer.Reset(w.refreshInterval)
shouldReturn = true shouldReturn = true
w.refreshChannelCount++ w.refreshChannelCount++
} else { } else {
w.refreshTimer = time.NewTimer(time.Millisecond * w.refreshTimerMS) w.refreshTimer = time.NewTimer(w.refreshInterval)
} }
}() }()
@@ -291,16 +292,3 @@ func (w *Watcher) rebuildRequired(ev fsnotify.Event, listener Listener) bool {
} }
return true return true
} }
/*
var WatchFilter = func(c *Controller, fc []Filter) {
if MainWatcher != nil {
err := MainWatcher.Notify()
if err != nil {
c.Result = c.RenderError(err)
return
}
}
fc[0](c, fc[1:])
}
*/