Compare commits

..

122 Commits

Author SHA1 Message Date
notzippy@gmail.com
ff8817efd9 Added github.com/revel/revel as an import path to be always copied. 2022-04-25 15:41:54 -07:00
notzippy@gmail.com
e9e804adff Added code to copy non go files into vendor folder. 2022-04-21 16:13:40 -07:00
notzippy@gmail.com
b7b15f820c Added vendor test 2022-04-21 15:16:19 -07:00
Brenden Soares
4c7ddf5567 Merge pull request #216 from revel/hotfix/v1.1.1_2
Updated gomod
2022-04-12 16:16:57 -07:00
notzippy@gmail.com
f090e4b4a7 Updated gomod 2022-04-12 16:15:05 -07:00
Brenden Soares
4950c86e82 Merge pull request #215 from revel/hotfix/v1.1.1_2
Removed duplicate package requests
2022-04-12 16:05:20 -07:00
notzippy@gmail.com
cedce5f9fb Removed duplicate package requests 2022-04-12 16:03:52 -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
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
notzippy@gmail.com
1d9df256a0 Moved test cases to run last 2020-04-29 21:39:19 -07:00
notzippy@gmail.com
ad694c0fb0 Debug travis 2020-04-29 21:32:12 -07:00
notzippy@gmail.com
fb4b56513a Debug travis
Added verbose flag so we can see what is occurring,
Removed checkout for revel, not needed anymore
2020-04-29 21:13:00 -07:00
notzippy@gmail.com
20d5766eb6 Added gomod-flags
Added a gomod-flags parameter which allows you to run go mod commands on the go.mod file before the build is performed. This allows for development environments.
2020-04-29 17:05:39 -07:00
notzippy@gmail.com
0920905a0c Updated to build go 1.12 and up
Modified to use fsnotify directlyUpdated travis to not use go deps
2020-04-28 12:56:23 -07:00
notzippy@gmail.com
31cb64e496 Check-in of command_test,
remaps the go mod command to use the develop branch.
2020-04-27 09:07:04 -07:00
notzippy@gmail.com
33abc47c7a Fixed remaining test 2020-04-26 23:00:51 -07:00
notzippy@gmail.com
86736d6e43 Updated formating
Ran through testing individually for vendored Revel applications
2020-04-26 22:29:16 -07:00
notzippy@gmail.com
07d67846c1 Restructured command config
Removed go/build reference in clean
2020-04-26 22:28:46 -07:00
notzippy@gmail.com
c1aee24445 Corrected version detection, so that equal versions match 2020-04-26 22:28:46 -07:00
notzippy@gmail.com
f2b54f5a69 Updated sourceinfo
Added packagepathmap to the SourceInfo, this in turn allows the RevelCLI app command to pass the source paths directly to Revel directly
Added default to build to be "target" of the current folder
Renamed source processor
2020-04-26 22:28:46 -07:00
notzippy@gmail.com
3f54665d4e Added processor to read the functions in the imported files, and populate the SourceInfo object the same as before 2020-04-26 22:28:46 -07:00
notzippy@gmail.com
548cbc1764 Upatede Error type to SourceError
Added processor object to code
Verified compile errors appearing
Signed-off-by: notzippy@gmail.com
2020-04-26 22:28:46 -07:00
notzippy@gmail.com
9a9511d28f Updated so revel new works and revel run starts parsing the source. 2020-04-26 22:28:46 -07:00
notzippy@gmail.com
acb8fb631b Initial commit to go mod 2020-04-26 22:28:46 -07:00
Steve
d2014633af Merge pull request #176 from xXLokerXx/fix_windows_path
acept slash and inverted slash in src path validation
2020-04-13 07:32:55 -07:00
Steve
773f6889b4 Merge branch 'develop' into fix_windows_path 2020-04-13 07:32:03 -07:00
Steve
ca4cfa567e Merge pull request #165 from kumakichi/fixed_import_C
fixed import "C"
2020-04-13 07:30:30 -07:00
Steve
436869049c Merge pull request #179 from Laur1nMartins/Laur1nMartins/fix-linkerFlags
Fix linker flags inclusion in build command
2020-04-13 07:29:34 -07:00
Steve
cf2e617618 Merge branch 'develop' into Laur1nMartins/fix-linkerFlags 2020-04-13 07:29:15 -07:00
Laurin
424474a035 Fix linker flags inclusion in build comamnd 2020-02-10 10:35:43 +01:00
Steve
531aa1e209 Merge pull request #178 from helgix/package-bugfix
eliminates package bug on silent build problems
2019-09-30 16:54:18 -07:00
Олег Вакарев
83dfdb8ad2 this fixes error when revel package creates tar without indicating that build had errors due to unhandled error 2019-09-26 13:14:33 +03:00
xXlokerXx
6d8fcd90c1 Fix sintax error 2019-07-08 19:48:09 -05:00
xXlokerXx
aa459c1b66 Fix sintaxis error 2019-07-08 19:19:33 -05:00
xXlokerXx
0b23b3e494 Fix complexity 2019-07-08 18:38:10 -05:00
xXlokerXx
3f65e1ef41 acept slash and inverted slash in src path validation 2019-07-08 18:23:58 -05:00
san
7dce3d8967 fixed import "C" 2018-11-03 09:12:25 +08:00
Steve
149f9f992b Merge pull request #161 from notzippy/master
Patch for windows
2018-11-01 20:31:14 -07:00
NotZippy
dfb08d9bd2 Amended importPathFromPath to detect vendor folder from basePath, not just the word vendor in the path 2018-11-01 09:06:31 -07:00
NotZippy
98e771cd01 Patch for windows
Made interrupt call os.Kill for windows
Added check for process still running before killing it
2018-10-31 12:52:43 -07:00
NotZippy
5c8d5bca7f develop v1.0.0-dev 2018-10-30 06:23:53 -07:00
NotZippy
5f558aca4e release v0.21.0 2018-10-30 06:23:52 -07:00
Steve
facfe0ecaf Merge pull request #159 from notzippy/develop
Patchset for 21
2018-10-29 16:29:03 -07:00
NotZippy
ee53d2f399 Patchset for 21
Added Version -u to update the checked out libaraies
Enhanced new skeleton toto support http https and git schemas when cloning the skeleton repo.
Enhanced version command to fetch server version from master branch
Enhanced version command to update local repository if a new version exists on the server
2018-10-29 15:26:59 -07:00
Steve
3c48e1f83e Merge pull request #158 from hwsoderlund/fix_compile_error
Fix compilation error visibility
2018-10-29 10:19:17 -07:00
Henrik Söderlund
19fb7d6776 Fix compilation error visibility 2018-10-29 16:55:59 +01:00
Steve
1e7b5322f5 Merge pull request #157 from HaraldNordgren/go_versions
Bump Go versions and use '.x' to always get latest patch versions
2018-10-28 11:08:56 -07:00
Harald Nordgren
205c652f07 Bump Go versions and use '.x' to always get latest patch versions 2018-10-28 18:42:35 +01:00
Steve
3de8b8c03c Merge pull request #156 from notzippy/develop
Modified run command to translate symlinks to absolute paths
2018-10-25 09:19:43 -07:00
NotZippy
4a877b2a8a Modified run command to translate symlinks to absolute paths 2018-10-24 21:29:07 -07:00
Steve
a0bafdc2a7 Merge pull request #155 from notzippy/develop
Added ability to readback output from command line
2018-10-24 10:26:16 -07:00
NotZippy
cdef0b75a8 Added ability to readback output from command line
Updated readme
2018-10-24 10:23:09 -07:00
Steve
e0d3f83ca8 Merge pull request #153 from notzippy/develop
Updated tool to give more meaningful messages
2018-10-19 09:12:48 -07:00
NotZippy
87c9e56322 Tool updates
Updated tool to give more meaningful errors
Added file system as an option to fetch the skeleton from
2018-10-19 06:45:01 -07:00
Steve
554e62574d Merge pull request #152 from notzippy/develop
Allow windows to fail on travis since to address a bug on current master
2018-10-18 08:05:51 -07:00
NotZippy
32a3f08dde Allow windows to fail on travis since to address a bug on current master 2018-10-18 08:05:15 -07:00
Steve
e6e1cad795 Merge pull request #148 from notzippy/develop
Shutdown / logger update
2018-10-12 21:20:05 -07:00
NotZippy
5e36cb1025 Updated travis to use checkout matching branch of Revel Framework for build. 2018-10-12 20:50:42 -07:00
NotZippy
8c21a56302 Revel tool enhancements
* run Command will choose CWD if no additional arguments are supplied
* Added Revel version check, compatible lists are in model/version
2018-10-12 20:40:48 -07:00
NotZippy
031fde6009 Update to logger 2018-10-12 20:40:48 -07:00
Steve
09ca80add8 Merge pull request #151 from lujiacn/master
Update build.go
2018-10-12 15:48:22 -07:00
Jia Lu
2d6c2eefa4 Update build.go
add missing c.Build.ImportPath, which is required to generate run.sh and run.bat
2018-10-12 22:53:01 +08:00
Steve
644d6e12bd Added check for copy dir
CopyDir should not fail if the source folder does not exist.
2018-10-10 11:40:29 -07:00
Steve
be7bebd962 Merge pull request #146 from notzippy/develop
Added missing environment variables to command, skipping the gopath
2018-10-02 09:27:54 -07:00
NotZippy
43c188c1eb Added missing environment variables to command, skipping the gopath 2018-10-02 08:48:12 -07:00
Steve
f745fb3edf Merge pull request #145 from revel/develop
V 0.20.1 For revel/cmd
To address a lot of the automatic import issues.
Added in a number of test cases 
Refactored CI testing
2018-10-01 11:08:23 -07:00
Steve
1302671687 Merge pull request #144 from notzippy/develop
Moved skeleton to its own repository
2018-10-01 09:33:37 -07:00
NotZippy
9ad0065f6c Moved skeleton to its own repository 2018-10-01 08:58:25 -07:00
Steve
e5303e84cd Merge pull request #143 from notzippy/develop
Enhancements to Revel command
2018-09-30 10:08:35 -07:00
NotZippy
f4fb2ec091 Enhancements to Revel command
Reformat of code
Allow user to use a mix of command line arguments and flags
Enhance the import tool to detect missing packages in the modules side
Added test cases for all commands
2018-09-30 10:08:11 -07:00
99 changed files with 5706 additions and 3733 deletions

View File

@@ -1,11 +1,13 @@
{
"GOLANG": {
"ABC":[15, 25, 50, 70],
"BLOCK_NESTING":[5, 6, 7, 8],
"CYCLO":[20, 30, 45, 60],
"TOO_MANY_IVARS": [15, 18, 20, 25],
"ABC":[33, 38, 50, 70],
"ARITY":[5,6,7,8],
"BLOCK_NESTING":[9, 10, 12, 13],
"CYCLO":[30, 35, 45, 60],
"TOO_MANY_IVARS": [28, 30, 40, 45],
"TOO_MANY_FUNCTIONS": [20, 30, 40, 50],
"TOTAL_COMPLEXITY": [150, 250, 400, 500],
"LOC": [50, 75, 90, 120]
"LOC": [100, 175, 250, 320],
"TOTAL_LOC": [300, 400, 500, 600]
}
}
}

2
.gitignore vendored
View File

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

View File

@@ -1,15 +1,13 @@
language: go
go:
- "1.8"
- "1.9"
- "1.10"
- "1.11"
- "1.18.x"
- "1.17.x"
- "tip"
os:
- linux
- osx
- linux
- windows
sudo: false
@@ -19,67 +17,64 @@ branches:
- master
- develop
services:
- memcache
- redis-server
before_install:
# TRAVIS_OS_NAME - linux and osx
- echo $TRAVIS_OS_NAME
- |
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
brew update && brew install memcached redis && brew services start redis && brew services start memcached
fi
- redis-server --daemonize yes
- redis-cli info
env:
# Setting environments variables
- GO111MODULE=on
install:
# Setting environments variables
- export PATH=$PATH:$HOME/gopath/bin
- export REVEL_BRANCH="develop"
- 'if [[ "$TRAVIS_BRANCH" == "master" ]]; then export REVEL_BRANCH="master"; fi'
- 'echo "Travis branch: $TRAVIS_BRANCH, Revel dependency branch: $REVEL_BRANCH"'
- git clone -b $REVEL_BRANCH git://github.com/revel/modules ../modules/
- git clone -b $REVEL_BRANCH git://github.com/revel/revel ../revel/
- git clone -b $REVEL_BRANCH git://github.com/revel/config ../config/
- git clone -b $REVEL_BRANCH git://github.com/revel/cron ../cron/
- git clone -b $REVEL_BRANCH git://github.com/revel/examples ../examples/
- go get -v github.com/revel/revel/...
- go get -v github.com/revel/cmd/revel
- go get -u github.com/golang/dep/cmd/dep
# Since travis already checks out go build the commandline tool (revel)
- mkdir $HOME/GOPATH_PROTECTED
- export GOPATH=$HOME/GOPATH_PROTECTED
- go build -o $HOME/gopath/bin/revel github.com/revel/cmd/revel
- pwd
- env
script:
- go test -v github.com/revel/cmd/...
- go test -v github.com/revel/cmd/revel/...
# Ensure the new-app flow works (plus the other commands).
- revel version
- revel new my/testapp
- revel test my/testapp
- revel clean my/testapp
- revel build my/testapp build/testapp
- revel build my/testapp build/testapp prod
- revel package my/testapp
- revel package my/testapp prod
#- revel version
#- revel new my/testapp
#- revel test my/testapp
#- revel clean my/testapp
#- revel build my/testapp build/testapp
#- revel build my/testapp build/testapp prod
#- revel package my/testapp
#- revel package my/testapp prod
# Ensure the new-app flow works (plus the other commands).
- revel new -a my/testapp2
- revel test -a my/testapp2
- revel clean -a my/testapp2
- revel build -a my/testapp2 -t build/testapp
- revel build -a my/testapp2 -t build/testapp -m prod
- revel package -a my/testapp2
- revel package -a my/testapp2 -m prod
- revel new --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 --package revelframework.com -v
- revel test --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 -v
- revel clean --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 -v
- revel build --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 -v -t build/testapp2
- revel build --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 -v -t build/testapp2 -m prod
- revel package --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 -v
- revel package --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp2 -v -m prod
- revel new -a my/testapp3 -V
- revel test -a my/testapp3
- revel clean -a my/testapp3
- revel build -a my/testapp3 -t build/testapp
- revel build -a my/testapp3 -t build/testapp -m prod
- revel package -a my/testapp3
- revel package -a my/testapp3 -m prod
- export INITIALWD=$PWD
# Check build works with no-vendor flag
- cd $GOPATH
- export GO111MODULE=auto
- revel new -a my/testapp2 --no-vendor -v
- revel test -a my/testapp2 -v
# Check non verbose build, outside of GO path
- cd $INITIALWD
- revel new --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp3 --package revelframework.com
- revel test --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp3
# Check vendored version of revel
- cd $INITIALWD
- revel new --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp4 --package revelframework.com
- cd my/testapp4
- go mod vendor
- cd ../..
- revel test --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@$REVEL_BRANCH" -a my/testapp4
matrix:
allow_failures:
- go: tip
- go: 1.6
os: osx
- os: windows

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/reveltest && mkdir -p ${workspaceRoot}/.temp/revel/GOPATH"
},
{
"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
------------
```bash
go get -u github.com/revel/cmd/revel
go install github.com/revel/cmd/revel@latest
```
New Application
@@ -19,5 +19,33 @@ New Application
Create a new application
```commandline
revel new my/app
revel new -a my/app
```
## Community
* [Gitter](https://gitter.im/revel/community)
* [StackOverflow](http://stackoverflow.com/questions/tagged/revel)
## Learn More
* [Manual, Samples, Godocs, etc](http://revel.github.io)
* [Apps using Revel](https://github.com/revel/revel/wiki/Apps-in-the-Wild)
* [Articles Featuring Revel](https://github.com/revel/revel/wiki/Articles)
## Contributing
* [Contributing Code Guidelines](https://github.com/revel/revel/blob/master/CONTRIBUTING.md)
* [Revel Contributors](https://github.com/revel/revel/graphs/contributors)
## Contributors
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/0)](https://sourcerer.io/fame/notzippy/revel/cmd/links/0)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/1)](https://sourcerer.io/fame/notzippy/revel/cmd/links/1)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/2)](https://sourcerer.io/fame/notzippy/revel/cmd/links/2)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/3)](https://sourcerer.io/fame/notzippy/revel/cmd/links/3)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/4)](https://sourcerer.io/fame/notzippy/revel/cmd/links/4)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/5)](https://sourcerer.io/fame/notzippy/revel/cmd/links/5)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/6)](https://sourcerer.io/fame/notzippy/revel/cmd/links/6)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/7)](https://sourcerer.io/fame/notzippy/revel/cmd/links/7)

40
go.mod Normal file
View File

@@ -0,0 +1,40 @@
module github.com/revel/cmd
go 1.17
retract (
v1.1.0 // v1.1.0-1.1.1 are failed releases
v1.1.1
)
require (
github.com/BurntSushi/toml v1.0.0 // indirect
github.com/agtorre/gocolorize v1.0.0
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.4.9
github.com/go-stack/stack v1.8.0 // indirect
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac // indirect
github.com/jessevdk/go-flags v1.4.0
github.com/mattn/go-colorable v0.1.8
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/myesui/uuid v1.0.0 // indirect
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/revel/config v1.1.0
github.com/revel/log15 v2.11.20+incompatible
github.com/revel/pathtree v0.0.0-20140121041023-41257a1839e9 // indirect
github.com/revel/revel v1.1.0
github.com/stretchr/testify v1.7.0
github.com/twinj/uuid v1.0.0 // indirect
github.com/xeonx/timeago v1.0.0-rc4 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
golang.org/x/tools v0.1.10
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/stack.v0 v0.0.0-20141108040640-9b43fcefddd0
gopkg.in/stretchr/testify.v1 v1.2.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)

86
go.sum Normal file
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,30 +6,41 @@ package harness
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"sync"
"sync/atomic"
"time"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"log"
)
// 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)
// Its only purpose is constructing the command to execute.
type App struct {
BinaryPath string // Path to the app executable
Port int // Port to pass as a command line argument.
cmd AppCmd // The last cmd returned.
Paths *model.RevelContainer
BinaryPath string // Path to the app executable
Port int // Port to pass as a command line argument.
cmd AppCmd // The last cmd returned.
PackagePathMap map[string]string // Package to directory path map
Paths *model.RevelContainer
}
// NewApp returns app instance with binary path in it
func NewApp(binPath string, paths *model.RevelContainer) *App {
return &App{BinaryPath: binPath, Paths: paths, Port: paths.HTTPPort}
// NewApp returns app instance with binary path in it.
func NewApp(binPath string, paths *model.RevelContainer, packagePathMap map[string]string) *App {
return &App{BinaryPath: binPath, Paths: paths, Port: paths.HTTPPort, PackagePathMap: packagePathMap}
}
// Cmd returns a command to run the app server using the current configuration.
@@ -49,7 +60,7 @@ type AppCmd struct {
*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 {
cmd := exec.Command(binPath,
fmt.Sprintf("-port=%d", port),
@@ -61,33 +72,41 @@ func NewAppCmd(binPath string, port int, runMode string, paths *model.RevelConta
// Start the app server, and wait until it is ready to serve requests.
func (cmd AppCmd) Start(c *model.CommandConfig) error {
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool),c}
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool), c, &bytes.Buffer{}}
cmd.Stdout = listeningWriter
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args)
cmd.Stderr = listeningWriter
utils.CmdInit(cmd.Cmd, !c.Vendored, c.AppPath)
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args, "dir", cmd.Dir, "env", cmd.Env)
if err := cmd.Cmd.Start(); err != nil {
utils.Logger.Fatal("Error running:", "error", err)
}
select {
case exitState := <-cmd.waitChan():
return errors.New("revel/harness: app died reason: " + exitState)
fmt.Println("Startup failure view previous messages, \n Proxy is listening :", c.Run.Port)
err := utils.NewError("", "Revel Run Error", "starting your application there was an exception. See terminal output, "+exitState, "")
atomic.SwapInt32(&startupError, 1)
// TODO pretiffy command line output
err.Stack = listeningWriter.buffer.String()
return err
case <-time.After(60 * time.Second):
println("Revel proxy is listening, point your browser to :", c.Run.Port)
utils.Logger.Error("Killing revel server process did not respond after wait timeout.", "processid", cmd.Process.Pid)
cmd.Kill()
return errors.New("revel/harness: app timed out")
return fmt.Errorf("revel/harness: %w", ErrTimedOut)
case <-listeningWriter.notifyReady:
println("Revel proxy is listening, point your browser to :", c.Run.Port)
return nil
}
// TODO remove this unreachable code and document it
panic("Impossible")
}
// Run the app server inline. Never returns.
func (cmd AppCmd) Run() {
log.Println("Exec app:", "path", cmd.Path, "args", cmd.Args)
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)
if err := cmd.Cmd.Run(); err != nil {
utils.Logger.Fatal("Error running:", "error", err)
}
@@ -96,11 +115,61 @@ func (cmd AppCmd) Run() {
// Kill terminates the app server if it's running.
func (cmd AppCmd) Kill() {
if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) {
utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid)
err := cmd.Process.Kill()
if err != nil {
utils.Logger.Fatal("Failed to kill revel server:", "error", err)
// 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
if _, err := os.FindProcess(cmd.Process.Pid); err != nil {
// Server has already exited
utils.Logger.Info("Server not running revel server pid", "pid", cmd.Process.Pid)
return
}
// Wait for the shutdown channel
waitMutex := &sync.WaitGroup{}
waitMutex.Add(1)
ch := make(chan bool, 1)
go func() {
waitMutex.Done()
s, err := cmd.Process.Wait()
defer func() {
ch <- true
}()
if err != nil {
utils.Logger.Info("Wait failed for process ", "error", err)
}
if s != nil {
utils.Logger.Info("Revel App exited", "state", s.String())
}
}()
// Wait for the channel to begin waiting
waitMutex.Wait()
// Send an interrupt signal to allow for a graceful shutdown
utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid)
err := cmd.Process.Signal(os.Interrupt)
if err != nil {
utils.Logger.Info(
"Revel app already exited.",
"processid", cmd.Process.Pid, "error", err,
"killerror", cmd.Process.Kill())
return
}
// Use a timer to ensure that the process exits
utils.Logger.Info("Waiting to exit")
select {
case <-ch:
return
case <-time.After(60 * time.Second):
// Kill the process
utils.Logger.Error(
"Revel app failed to exit in 60 seconds - killing.",
"processid", cmd.Process.Pid,
"killerror", cmd.Process.Kill())
}
utils.Logger.Info("Done Waiting to exit")
}
}
@@ -111,7 +180,7 @@ func (cmd AppCmd) waitChan() <-chan string {
_ = cmd.Wait()
state := cmd.ProcessState
exitStatus := " unknown "
if state!=nil {
if state != nil {
exitStatus = state.String()
}
@@ -126,9 +195,11 @@ func (cmd AppCmd) waitChan() <-chan string {
type startupListeningWriter struct {
dest io.Writer
notifyReady chan bool
c *model.CommandConfig
c *model.CommandConfig
buffer *bytes.Buffer
}
// Writes to this output stream.
func (w *startupListeningWriter) Write(p []byte) (int, error) {
if w.notifyReady != nil && bytes.Contains(p, []byte("Revel engine is listening on")) {
w.notifyReady <- true
@@ -140,5 +211,8 @@ func (w *startupListeningWriter) Write(p []byte) (int, error) {
w.notifyReady = nil
}
}
if w.notifyReady != nil {
w.buffer.Write(p)
}
return w.dest.Write(p)
}

View File

@@ -20,28 +20,45 @@ import (
"github.com/revel/cmd/model"
"github.com/revel/cmd/parser"
"github.com/revel/cmd/parser2"
"github.com/revel/cmd/utils"
)
var importErrorPattern = regexp.MustCompile("cannot find package \"([^\"]+)\"")
var importErrorPattern2 = regexp.MustCompile("no required module provides package ([^;]+)+")
var addPackagePattern = regexp.MustCompile(`to add:\n\tgo get (.*)\n`)
type ByString []*model.TypeInfo
func (c ByString) Len() int { return len(c) }
func (c ByString) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c ByString) Less(i, j int) bool { return c[i].String() < c[j].String() }
func (c ByString) Len() int {
return len(c)
}
func (c ByString) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
func (c ByString) Less(i, j int) bool {
return c[i].String() < c[j].String()
}
// Build the app:
// 1. Generate the the main.go file.
// 2. Run the appropriate "go build" command.
// Requires that revel.Init has been called previously.
// Returns the path to the built binary, and an error if there was a problem building it.
func Build(c *model.CommandConfig, paths *model.RevelContainer) (app *App, compileError *utils.Error) {
func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err error) {
// First, clear the generated files (to avoid them messing with ProcessSource).
cleanSource(paths, "tmp", "routes")
sourceInfo, compileError := parser.ProcessSource(paths)
if compileError != nil {
var sourceInfo *model.SourceInfo
if c.HistoricBuildMode {
sourceInfo, err = parser.ProcessSource(paths)
} else {
sourceInfo, err = parser2.ProcessSource(paths)
}
if err != nil {
return
}
@@ -68,9 +85,15 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (app *App, compi
// without being the main thread
cleanSource(paths, "tmp", "routes")
genSource(paths, "tmp", "main.go", RevelMainTemplate, templateArgs)
genSource(paths, filepath.Join("tmp", "run"), "run.go", RevelRunTemplate, templateArgs)
genSource(paths, "routes", "routes.go", RevelRoutesTemplate, templateArgs)
if err = genSource(paths, "tmp", "main.go", RevelMainTemplate, templateArgs); err != nil {
return
}
if err = genSource(paths, filepath.Join("tmp", "run"), "run.go", RevelRunTemplate, templateArgs); err != nil {
return
}
if err = genSource(paths, "routes", "routes.go", RevelRoutesTemplate, templateArgs); err != nil {
return
}
// Read build config.
buildTags := paths.Config.StringDefault("build.tags", "")
@@ -82,47 +105,8 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (app *App, compi
utils.Logger.Fatal("Go executable not found in PATH.")
}
// Detect if deps tool should be used (is there a vendor folder ?)
useVendor := utils.DirExists(filepath.Join(paths.BasePath, "vendor"))
basePath := paths.BasePath
for !useVendor {
basePath = filepath.Dir(basePath)
found := false
// Check to see if we are still in the GOPATH
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
if strings.HasPrefix(basePath, gopath) {
found = true
break
}
}
if !found {
break
} else {
useVendor = utils.DirExists(filepath.Join(basePath, "vendor"))
}
}
var depPath string
if useVendor {
utils.Logger.Info("Vendor folder detected, scanning for deps in path")
depPath, err = exec.LookPath("dep")
if err != nil {
// Do not halt build unless a new package needs to be imported
utils.Logger.Warn("Build: `dep` executable not found in PATH, but vendor folder detected." +
"Packages can only be added automatically to the vendor folder using the `dep` tool. " +
"You can install the `dep` tool by doing a `go get -u github.com/golang/dep/cmd/dep`")
}
} else {
utils.Logger.Info("No vendor folder detected, not using dependency manager to import files")
}
pkg, err := build.Default.Import(paths.ImportPath, "", build.FindOnly)
if err != nil {
utils.Logger.Fatal("Failure importing", "path", paths.ImportPath)
}
// Binary path is a combination of $GOBIN/revel.d directory, app's import path and its name.
binName := filepath.Join(pkg.BinDir, "revel.d", paths.ImportPath, filepath.Base(paths.BasePath))
// Binary path is a combination of target/app directory, app's import path and its name.
binName := filepath.Join("target", "app", paths.ImportPath, filepath.Base(paths.BasePath))
// Change binary path for Windows build
goos := runtime.GOOS
@@ -143,11 +127,28 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (app *App, compi
return false
}
if len(c.GoModFlags) > 0 {
for _, gomod := range c.GoModFlags {
goModCmd := exec.Command(goPath, append([]string{"mod"}, strings.Split(gomod, " ")...)...)
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
output, err := goModCmd.CombinedOutput()
utils.Logger.Info("Gomod applied ", "output", string(output))
// If the build succeeded, we're done.
if err != nil {
utils.Logger.Error("Gomod Failed continuing ", "error", err, "output", string(output))
}
}
}
for {
appVersion := getAppVersion(paths)
if appVersion == "" {
appVersion = "noVersionProvided"
}
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)
// Append any build flags specified, they will override existing flags
@@ -155,19 +156,23 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (app *App, compi
if len(c.BuildFlags) == 0 {
flags = []string{
"build",
"-i",
"-ldflags", versionLinkerFlags,
"-tags", buildTags,
"-o", binName}
"-o", binName,
}
} else {
if !contains(c.BuildFlags, "build") {
flags = []string{"build"}
}
flags = append(flags, c.BuildFlags...)
if !contains(flags, "-ldflags") {
flags = append(flags, "-ldflags", versionLinkerFlags)
ldflags := "-ldflags= " + versionLinkerFlags
// Add user defined build flags
for i := range c.BuildFlags {
ldflags += " -X '" + c.BuildFlags[i] + "'"
}
flags = append(flags, ldflags)
}
if !contains(flags, "-tags") {
if !contains(flags, "-tags") && buildTags != "" {
flags = append(flags, "-tags", buildTags)
}
if !contains(flags, "-o") {
@@ -175,86 +180,86 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (app *App, compi
}
}
// Add in build flags
flags = append(flags, c.BuildFlags...)
// This is Go main path
gopath := c.GoPath
for _, o := range paths.ModulePathMap {
gopath += string(filepath.ListSeparator) + o
}
// Note: It's not applicable for filepath.* usage
flags = append(flags, path.Join(paths.ImportPath, "app", "tmp"))
buildCmd := exec.Command(goPath, flags...)
buildCmd.Env = append(os.Environ(),
"GOPATH="+gopath,
)
utils.Logger.Info("Exec:", "args", buildCmd.Args)
if !c.Vendored {
// This is Go main path
gopath := c.GoPath
for _, o := range paths.ModulePathMap {
gopath += string(filepath.ListSeparator) + o.Path
}
buildCmd.Env = append(os.Environ(),
"GOPATH="+gopath,
)
}
utils.CmdInit(buildCmd, !c.Vendored, c.AppPath)
utils.Logger.Info("Exec:", "args", buildCmd.Args, "working dir", buildCmd.Dir)
output, err := buildCmd.CombinedOutput()
// If the build succeeded, we're done.
if err == nil {
utils.Logger.Info("Build successful continuing")
return NewApp(binName, paths), nil
return NewApp(binName, paths, sourceInfo.PackageMap), nil
}
// Since there was an error, capture the output in case we need to report it
stOutput := string(output)
utils.Logger.Infof("Got error on build of app %s", stOutput)
// See if it was an import error that we can go get.
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))
if matches == nil {
utils.Logger.Info("Build failed no missing imports", "message", stOutput)
return nil, newCompileError(paths, output)
}
utils.Logger.Warn("Detected missing packages, importing them", "packages", len(matches))
// Reduce the matches down to unique ones
missedPkgs := []string{}
for _, match := range matches {
found := false
for _, pkgName := range missedPkgs {
if match[1] == pkgName {
found = true
break
}
}
if !found {
missedPkgs = append(missedPkgs, match[1])
}
}
utils.Logger.Warn("Detected missing packages, importing them", "packages", len(matches))
for _, pkgName := range missedPkgs {
// Ensure we haven't already tried to go get it.
pkgName := match[1]
utils.Logger.Info("Trying to import ", "package", pkgName)
if _, alreadyTried := gotten[pkgName]; alreadyTried {
utils.Logger.Error("Failed to import ", "package", pkgName)
return nil, newCompileError(paths, output)
}
gotten[pkgName] = struct{}{}
// Execute "go get <pkg>"
// Or dep `dep ensure -add <pkg>` if it is there
var getCmd *exec.Cmd
if useVendor {
if depPath == "" {
utils.Logger.Warn("Build: Vendor folder found, but the `dep` tool was not found, " +
"if you use a different vendoring (package management) tool please add the following packages by hand, " +
"or install the `dep` tool into your gopath by doing a `go get -u github.com/golang/dep/cmd/dep`. " +
"For more information and usage of the tool please see http://github.com/golang/dep")
for _, pkg := range matches {
utils.Logger.Warn("Missing package", "package", pkg[1])
}
}
getCmd = exec.Command(depPath, "ensure", "-add", pkgName)
getCmd.Dir = paths.AppPath
} else {
getCmd = exec.Command(goPath, "get", pkgName)
}
utils.Logger.Info("Exec:", "args", getCmd.Args)
getOutput, err := getCmd.CombinedOutput()
if err != nil {
utils.Logger.Error("Build failed", "message", stOutput, "error", err)
utils.Logger.Error("Failed to fetch the output", "getOutput", string(getOutput))
return nil, newCompileError(paths, output)
if err := c.PackageResolver(pkgName); err != nil {
// panic("failed to resolve")
utils.Logger.Error("Unable to resolve package", "package", pkgName, "error", err)
return nil, newCompileError(paths, []byte(err.Error()))
}
}
// Success getting the import, attempt to build again.
}
// TODO remove this unreachable code and document it
utils.Logger.Fatal("Not reachable")
return nil, nil
// unreachable
}
// Try to define a version string for the compiled app
@@ -279,7 +284,6 @@ func getAppVersion(paths *model.RevelContainer) string {
gitCmd := exec.Command(gitPath, "--git-dir="+gitDir, "--work-tree="+paths.BasePath, "describe", "--always", "--dirty")
utils.Logger.Info("Exec:", "args", gitCmd.Args)
output, err := gitCmd.Output()
if err != nil {
utils.Logger.Error("Cannot determine git repository version:", "error", err)
return ""
@@ -336,12 +340,8 @@ func cleanDir(paths *model.RevelContainer, dir string) {
// genSource renders the given template to produce source code, which it writes
// to the given directory and file.
func genSource(paths *model.RevelContainer, dir, filename, templateSource string, args map[string]interface{}) {
err := utils.MustGenerateTemplate(filepath.Join(paths.AppPath, dir, filename), templateSource, args)
if err != nil {
utils.Logger.Fatal("Failed to generate template for source file", "error", err)
}
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)
}
// Looks through all the method args and returns a set of unique import paths
@@ -376,17 +376,16 @@ func calcImportAliases(src *model.SourceInfo) map[string]string {
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) {
alias, ok := aliases[importPath]
_, ok := aliases[importPath]
if ok {
return
}
alias = makePackageAlias(aliases, pkgName)
aliases[importPath] = alias
aliases[importPath] = makePackageAlias(aliases, pkgName)
}
// Generates a package alias
// Generates a package alias.
func makePackageAlias(aliases map[string]string, pkgName string) string {
i := 0
alias := pkgName
@@ -397,7 +396,7 @@ func makePackageAlias(aliases map[string]string, pkgName string) string {
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 {
for _, v := range m {
if v == val {
@@ -409,7 +408,7 @@ func containsValue(m map[string]string, val string) bool {
// Parse the output of the "go build" command.
// Return a detailed Error.
func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error {
func newCompileError(paths *model.RevelContainer, output []byte) *utils.SourceError {
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
FindSubmatch(output)
if errorMatch == nil {
@@ -417,7 +416,7 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error {
if errorMatch == nil {
utils.Logger.Error("Failed to parse build errors", "error", string(output))
return &utils.Error{
return &utils.SourceError{
SourceType: "Go code",
Title: "Go Compilation Error",
Description: "See console for build error.",
@@ -433,7 +432,8 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error {
// Extract the paths from the gopaths, and search for file there first
gopaths := filepath.SplitList(build.Default.GOPATH)
for _, gp := range gopaths {
newPath := filepath.Join(gp, relFilename)
newPath := filepath.Join(gp, "src", paths.ImportPath, relFilename)
println(newPath)
if utils.Exists(newPath) {
return newPath
}
@@ -449,7 +449,7 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error {
absFilename = findInPaths(relFilename)
line, _ = strconv.Atoi(string(errorMatch[2]))
description = string(errorMatch[4])
compileError = &utils.Error{
compileError = &utils.SourceError{
SourceType: "Go code",
Title: "Go Compilation Error",
Path: relFilename,
@@ -467,7 +467,7 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error {
fileStr, err := utils.ReadLines(absFilename)
if err != nil {
compileError.MetaError = absFilename + ": " + err.Error()
utils.Logger.Error("Unable to readlines "+compileError.MetaError, "error", err)
utils.Logger.Info("Unable to readlines "+compileError.MetaError, "error", err)
return compileError
}
@@ -475,7 +475,7 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error {
return compileError
}
// RevelMainTemplate template for app/tmp/main.go
// RevelMainTemplate template for app/tmp/run/run.go.
const RevelRunTemplate = `// GENERATED CODE - DO NOT EDIT
// This file is the run file for Revel.
// It registers all the controllers and provides details for the Revel server engine to
@@ -530,6 +530,7 @@ func Register() {
}
}
`
const RevelMainTemplate = `// GENERATED CODE - DO NOT EDIT
// This file is the main file for Revel.
// It registers all the controllers and provides details for the Revel server engine to
@@ -557,7 +558,7 @@ func main() {
}
`
// RevelRoutesTemplate template for app/conf/routes
// RevelRoutesTemplate template for app/conf/routes.
const RevelRoutesTemplate = `// GENERATED CODE - DO NOT EDIT
// This file provides a way of creating URL's based on all the actions
// found in all the controllers.

View File

@@ -15,9 +15,13 @@ package harness
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"go/build"
"html/template"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
@@ -26,20 +30,21 @@ import (
"os/signal"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"github.com/revel/cmd/watcher"
"sync"
"html/template"
"io/ioutil"
)
var (
doNotWatch = []string{"tmp", "views", "routes"}
lastRequestHadError int32
startupError int32
startupErrorText error
)
// Harness reverse proxies requests to the application server.
@@ -55,6 +60,7 @@ type Harness struct {
paths *model.RevelContainer // The Revel container
config *model.CommandConfig // The configuration
runMode string // The runmode the harness is running in
ranOnce bool // True app compiled once
}
func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err error) {
@@ -63,61 +69,58 @@ func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err erro
// 1) Application/views/errors
// 2) revel_home/views/errors
// 3) views/errors
if err==nil {
if err == nil {
utils.Logger.Panic("Caller passed in a nil error")
}
templateSet := template.New("__root__")
seekViewOnPath:=func(view string) (path string) {
seekViewOnPath := func(view string) (path string) {
path = filepath.Join(h.paths.ViewsPath, "errors", view)
if !utils.Exists(path) {
path = filepath.Join(h.paths.RevelPath, "templates", "errors", view)
}
data,err := ioutil.ReadFile(path)
if err!=nil {
data, err := ioutil.ReadFile(path)
if err != nil {
utils.Logger.Error("Unable to read template file", path)
}
_,err = templateSet.New("errors/"+view).Parse(string(data))
if err!=nil {
_, err = templateSet.New("errors/" + view).Parse(string(data))
if err != nil {
utils.Logger.Error("Unable to parse template file", path)
}
return
}
target := []string{seekViewOnPath("500.html"),seekViewOnPath("500-dev.html")}
target := []string{seekViewOnPath("500.html"), seekViewOnPath("500-dev.html")}
if !utils.Exists(target[0]) {
fmt.Fprintf(iw, "Target template not found not found %s<br />\n", target[0])
fmt.Fprintf(iw, "An error ocurred %s", err.Error())
return
fmt.Fprintf(iw, "An error occurred %s", err.Error())
return
}
var revelError *utils.Error
switch e := err.(type) {
case *utils.Error:
revelError = e
case error:
revelError = &utils.Error{
var revelError *utils.SourceError
if !errors.As(err, &revelError) {
revelError = &utils.SourceError{
Title: "Server Error",
Description: e.Error(),
Description: err.Error(),
}
}
if revelError == nil {
panic("no error provided")
}
viewArgs := map[string]interface{}{}
viewArgs["RunMode"] = h.paths.RunMode
viewArgs["DevMode"] = h.paths.DevMode
viewArgs["Error"] = revelError
// Render the template from the file
err = templateSet.ExecuteTemplate(iw,"errors/500.html",viewArgs)
if err!=nil {
utils.Logger.Error("Failed to execute","error",err)
err = templateSet.ExecuteTemplate(iw, "errors/500.html", viewArgs)
if err != nil {
utils.Logger.Error("Failed to execute", "error", err)
}
fmt.Println("template ",templateSet.Templates()[0].Name(), templateSet.Templates()[1].Name())
//utils.MustRenderTemplateToStream(iw,target, viewArgs)
}
// ServeHTTP handles all requests.
@@ -157,15 +160,16 @@ func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func NewHarness(c *model.CommandConfig, paths *model.RevelContainer, runMode string, noProxy bool) *Harness {
// Get a template loader to render errors.
// Prefer the app's views/errors directory, and fall back to the stock error pages.
//revel.MainTemplateLoader = revel.NewTemplateLoader(
// revel.MainTemplateLoader = revel.NewTemplateLoader(
// []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)
//}
// }
addr := paths.HTTPAddr
port := paths.Config.IntDefault("harness.port", 0)
scheme := "http"
if paths.HTTPSsl {
scheme = "https"
}
@@ -192,7 +196,6 @@ func NewHarness(c *model.CommandConfig, paths *model.RevelContainer, runMode str
useProxy: !noProxy,
config: c,
runMode: runMode,
}
if paths.HTTPSsl {
@@ -204,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.
// called by the watcher
func (h *Harness) Refresh() (err *utils.Error) {
// called by the watcher.
func (h *Harness) Refresh() (err *utils.SourceError) {
t := time.Now()
fmt.Println("Change detected, recompiling")
err = h.refresh()
if err != nil && !h.ranOnce && h.useProxy {
addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort)
fmt.Printf("\nError compiling code, to view error details see proxy running on http://%s\n\n", addr)
}
h.ranOnce = true
fmt.Printf("\nTime to recompile %s\n", time.Since(t).String())
return
}
func (h *Harness) refresh() (err *utils.SourceError) {
// Allow only one thread to rebuild the process
// If multiple requests to rebuild are queued only the last one is executed on
// So before a build is started we wait for a second to determine if
@@ -219,17 +237,47 @@ func (h *Harness) Refresh() (err *utils.Error) {
}
utils.Logger.Info("Rebuild Called")
h.app, err = Build(h.config, h.paths)
if err != nil {
utils.Logger.Error("Build detected an error", "error", err)
var newErr error
h.app, newErr = Build(h.config, h.paths)
if newErr != nil {
utils.Logger.Error("Build detected an error", "error", newErr)
var castErr *utils.SourceError
if errors.As(newErr, &castErr) {
return castErr
}
err = &utils.SourceError{
Title: "App failed to start up",
Description: newErr.Error(),
}
return
}
if h.useProxy {
h.app.Port = h.port
if err2 := h.app.Cmd(h.runMode).Start(h.config); err2 != nil {
runMode := h.runMode
if !h.config.HistoricMode {
// Recalulate run mode based on the config
var paths []byte
if len(h.app.PackagePathMap) > 0 {
paths, _ = json.Marshal(h.app.PackagePathMap)
}
runMode = fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, h.app.Paths.RunMode, h.config.GetVerbose(), string(paths))
}
if err2 := h.app.Cmd(runMode).Start(h.config); err2 != nil {
utils.Logger.Error("Could not start application", "error", err2)
return &utils.Error{
var serr *utils.SourceError
if errors.As(err2, &serr) {
return err
}
return &utils.SourceError{
Title: "App failed to start up",
Description: err2.Error(),
}
@@ -242,13 +290,13 @@ func (h *Harness) Refresh() (err *utils.Error) {
}
// WatchDir method returns false to file matches with doNotWatch
// otheriwse true
// otheriwse true.
func (h *Harness) WatchDir(info os.FileInfo) bool {
return !utils.ContainsString(doNotWatch, info.Name())
}
// 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 {
return strings.HasSuffix(filename, ".go")
}
@@ -264,17 +312,21 @@ func (h *Harness) Run() {
paths = append(paths, h.paths.CodePaths...)
h.watcher = watcher.NewWatcher(h.paths, false)
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 {
go func() {
// Check the port to start on a random port
if h.paths.HTTPPort==0 {
if h.paths.HTTPPort == 0 {
h.paths.HTTPPort = getFreePort()
}
addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort)
utils.Logger.Infof("Proxy server is listening on %s", addr)
var err error
if h.paths.HTTPSsl {
err = http.ListenAndServeTLS(
@@ -289,19 +341,21 @@ func (h *Harness) Run() {
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)
//nolint:staticcheck // os.Kill ineffective on Unix, useful on Windows?
signal.Notify(ch, os.Interrupt, os.Kill)
<-ch
// Kill the app and exit
if h.app != nil {
h.app.Kill()
}
os.Exit(1)
}
// Find an unused port
// Find an unused port.
func getFreePort() (port int) {
conn, err := net.Listen("tcp", ":0")
if err != nil {

View File

@@ -0,0 +1,178 @@
package logger
import (
"io"
"os"
"github.com/mattn/go-colorable"
"gopkg.in/natefinch/lumberjack.v2"
)
type CompositeMultiHandler struct {
DebugHandler LogHandler
InfoHandler LogHandler
WarnHandler LogHandler
ErrorHandler LogHandler
CriticalHandler LogHandler
}
func NewCompositeMultiHandler() (*CompositeMultiHandler, LogHandler) {
cw := &CompositeMultiHandler{}
return cw, cw
}
func (h *CompositeMultiHandler) Log(r *Record) (err error) {
var handler LogHandler
switch r.Level {
case LvlInfo:
handler = h.InfoHandler
case LvlDebug:
handler = h.DebugHandler
case LvlWarn:
handler = h.WarnHandler
case LvlError:
handler = h.ErrorHandler
case LvlCrit:
handler = h.CriticalHandler
}
// Embed the caller function in the context
if handler != nil {
if err := handler.Log(r); err != nil {
panic(err)
}
}
return
}
func (h *CompositeMultiHandler) SetHandler(handler LogHandler, replace bool, level LogLevel) {
if handler == nil {
// Ignore empty handler
return
}
source := &h.DebugHandler
switch level {
case LvlDebug:
source = &h.DebugHandler
case LvlInfo:
source = &h.InfoHandler
case LvlWarn:
source = &h.WarnHandler
case LvlError:
source = &h.ErrorHandler
case LvlCrit:
source = &h.CriticalHandler
}
if !replace && *source != nil {
// If we are not replacing the source make sure that the level handler is applied first
if _, isLevel := (*source).(*LevelFilterHandler); !isLevel {
*source = LevelHandler(level, *source)
}
// If this already was a list add a new logger to it
if ll, found := (*source).(*ListLogHandler); found {
ll.Add(handler)
} else {
*source = NewListLogHandler(*source, handler)
}
} else {
*source = handler
}
}
// For the multi handler set the handler, using the LogOptions defined.
func (h *CompositeMultiHandler) SetHandlers(handler LogHandler, options *LogOptions) {
if len(options.Levels) == 0 {
options.Levels = LvlAllList
}
// Set all levels
for _, lvl := range options.Levels {
h.SetHandler(handler, options.ReplaceExistingHandler, lvl)
}
}
func (h *CompositeMultiHandler) SetJSON(writer io.Writer, options *LogOptions) {
handler := CallerFileHandler(StreamHandler(writer, JSONFormatEx(
options.GetBoolDefault("pretty", false),
options.GetBoolDefault("lineSeparated", true),
)))
if options.HandlerWrap != nil {
handler = options.HandlerWrap.SetChild(handler)
}
h.SetHandlers(handler, options)
}
// Use built in rolling function.
func (h *CompositeMultiHandler) SetJSONFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{
Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), // days
MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true),
}
h.SetJSON(writer, options)
}
func (h *CompositeMultiHandler) SetTerminal(writer io.Writer, options *LogOptions) {
streamHandler := StreamHandler(
writer,
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
if os.Stdout == writer {
streamHandler = StreamHandler(
colorable.NewColorableStdout(),
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
} else if os.Stderr == writer {
streamHandler = StreamHandler(
colorable.NewColorableStderr(),
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
}
handler := CallerFileHandler(streamHandler)
if options.HandlerWrap != nil {
handler = options.HandlerWrap.SetChild(handler)
}
h.SetHandlers(handler, options)
}
// Use built in rolling function.
func (h *CompositeMultiHandler) SetTerminalFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{
Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), // days
MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true),
}
h.SetTerminal(writer, options)
}
func (h *CompositeMultiHandler) Disable(levels ...LogLevel) {
if len(levels) == 0 {
levels = LvlAllList
}
for _, level := range levels {
switch level {
case LvlDebug:
h.DebugHandler = nil
case LvlInfo:
h.InfoHandler = nil
case LvlWarn:
h.WarnHandler = nil
case LvlError:
h.ErrorHandler = nil
case LvlCrit:
h.CriticalHandler = nil
}
}
}

View File

@@ -1,11 +1,12 @@
/*
Package logger contains filters and handles for the logging utilities in Revel.
These facilities all currently use the logging library called log15 at
https://github.com/inconshreveable/log15
Package logger contains filters and handles for the logging utilities in Revel.
These facilities all currently use the logging library called log15 at
https://github.com/inconshreveable/log15
Wrappers for the handlers are written here to provide a kind of isolation layer for Revel
in case sometime in the future we would like to switch to another source to implement logging
*/
Defining handlers happens as follows
1) ALL handlers (log.all.output) replace any existing handlers
2) Output handlers (log.error.output) replace any existing handlers
3) Filter handlers (log.xxx.filter, log.xxx.nfilter) append to existing handlers,
note log.all.filter is treated as a filter handler, so it will NOT replace existing ones
*/
package logger

View File

@@ -1,172 +1,189 @@
package logger
import (
"fmt"
"io"
"os"
colorable "github.com/mattn/go-colorable"
"github.com/revel/log15"
"gopkg.in/natefinch/lumberjack.v2"
)
// Filters out records which do not match the level
// Uses the `log15.FilterHandler` to perform this task
func LevelHandler(lvl LogLevel, h LogHandler) LogHandler {
l15Lvl := log15.Lvl(lvl)
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
return r.Lvl == l15Lvl
}, h)
type LevelFilterHandler struct {
Level LogLevel
h LogHandler
}
// 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 {
return &LevelFilterHandler{lvl, h}
}
// The implementation of the Log.
func (h LevelFilterHandler) Log(r *Record) error {
if r.Level == h.Level {
return h.h.Log(r)
}
return nil
}
// Filters out records which do not match the level
// Uses the `log15.FilterHandler` to perform this task.
func MinLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
l15Lvl := log15.Lvl(lvl)
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
return r.Lvl <= l15Lvl
return FilterHandler(func(r *Record) (pass bool) {
return r.Level <= lvl
}, h)
}
// Filters out records which match the level
// Uses the `log15.FilterHandler` to perform this task
// Uses the `log15.FilterHandler` to perform this task.
func NotLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
l15Lvl := log15.Lvl(lvl)
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
return r.Lvl != l15Lvl
return FilterHandler(func(r *Record) (pass bool) {
return r.Level != lvl
}, h)
}
// Adds in a context called `caller` to the record (contains file name and line number like `foo.go:12`)
// Uses the `log15.CallerFileHandler` to perform this task
func CallerFileHandler(h LogHandler) LogHandler {
return log15.CallerFileHandler(h)
return FuncHandler(func(r *Record) error {
r.Context.Add("caller", fmt.Sprint(r.Call))
return h.Log(r)
})
}
// Adds in a context called `caller` to the record (contains file name and line number like `foo.go:12`)
// Uses the `log15.CallerFuncHandler` to perform this task
// Uses the `log15.CallerFuncHandler` to perform this task.
func CallerFuncHandler(h LogHandler) LogHandler {
return log15.CallerFuncHandler(h)
// TODO: infinite recursion
return CallerFuncHandler(h)
}
// 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 {
return log15.MatchFilterHandler(key, value, h)
return MatchFilterHandler(key, value, h)
}
// If match then A handler is called otherwise B handler is called
// MatchFilterHandler returns a Handler that only writes records
// to the wrapped Handler if the given key in the logged
// context matches the value. For example, to only log records
// from your ui package:
//
// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler)
//
func MatchFilterHandler(key string, value interface{}, h LogHandler) LogHandler {
return FilterHandler(func(r *Record) (pass bool) {
return r.Context[key] == value
}, h)
}
// If match then A handler is called otherwise B handler is called.
func MatchAbHandler(key string, value interface{}, a, b LogHandler) LogHandler {
return log15.FuncHandler(func(r *log15.Record) error {
for i := 0; i < len(r.Ctx); i += 2 {
if r.Ctx[i] == key {
if r.Ctx[i+1] == value {
if a != nil {
return a.Log(r)
}
return nil
}
}
}
if b != nil {
return FuncHandler(func(r *Record) error {
if r.Context[key] == value {
return a.Log(r)
} else if b != nil {
return b.Log(r)
}
return nil
})
}
// The nil handler is used if logging for a specific request needs to be turned off
// The nil handler is used if logging for a specific request needs to be turned off.
func NilHandler() LogHandler {
return log15.FuncHandler(func(r *log15.Record) error {
return FuncHandler(func(r *Record) error {
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 {
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 {
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 {
return log15.FuncHandler(func(r *log15.Record) error {
checkMap := map[string]bool{}
// Copy the map to a bool
for i := 0; i < len(r.Ctx); i += 2 {
if value, found := matchMap[r.Ctx[i].(string)]; found && value == r.Ctx[i+1] {
checkMap[r.Ctx[i].(string)] = true
return FuncHandler(func(r *Record) error {
matchCount := 0
for k, v := range matchMap {
value, found := r.Context[k]
if !found {
return nil
}
// Test for two failure cases
if value == v && inverse || value != v && !inverse {
return nil
}
matchCount++
}
if len(checkMap) == len(matchMap) {
if !inverse {
return a.Log(r)
if matchCount != len(matchMap) {
return nil
}
return a.Log(r)
})
}
// Filters out records which do not match the key value pair
// Uses the `log15.FilterHandler` to perform this task.
func NotMatchHandler(key string, value interface{}, h LogHandler) LogHandler {
return FilterHandler(func(r *Record) (pass bool) {
return r.Context[key] != value
}, h)
}
func MultiHandler(hs ...LogHandler) LogHandler {
return FuncHandler(func(r *Record) error {
for _, h := range hs {
if err := h.Log(r); err != nil {
panic(err)
}
} else if inverse {
return a.Log(r)
}
return nil
})
}
// Filters out records which do not match the key value pair
// Uses the `log15.FilterHandler` to perform this task
func NotMatchHandler(key string, value interface{}, h LogHandler) LogHandler {
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
switch key {
case r.KeyNames.Lvl:
return r.Lvl != value
case r.KeyNames.Time:
return r.Time != value
case r.KeyNames.Msg:
return r.Msg != value
}
for i := 0; i < len(r.Ctx); i += 2 {
if r.Ctx[i] == key {
return r.Ctx[i+1] == value
}
}
return true
}, h)
}
func MultiHandler(hs ...LogHandler) LogHandler {
// Convert the log handlers to log15.Handlers
handlers := []log15.Handler{}
for _, h := range hs {
if h != nil {
handlers = append(handlers, h)
}
}
return log15.MultiHandler(handlers...)
}
// Outputs the records to the passed in stream
// Uses the `log15.StreamHandler` to perform this task
// StreamHandler writes log records to an io.Writer
// with the given format. StreamHandler can be used
// to easily begin writing log records to other
// outputs.
//
// StreamHandler wraps itself with LazyHandler and SyncHandler
// to evaluate Lazy objects and perform safe concurrent writes.
func StreamHandler(wr io.Writer, fmtr LogFormat) LogHandler {
return log15.StreamHandler(wr, fmtr)
h := FuncHandler(func(r *Record) error {
_, err := wr.Write(fmtr.Format(r))
return err
})
return LazyHandler(SyncHandler(h))
}
// Filter handler, this is the only
// Uses the `log15.FilterHandler` to perform this task
func FilterHandler(fn func(r *log15.Record) bool, h LogHandler) LogHandler {
return log15.FilterHandler(fn, h)
// Filter handler.
func FilterHandler(fn func(r *Record) bool, h LogHandler) LogHandler {
return FuncHandler(func(r *Record) error {
if fn(r) {
return h.Log(r)
}
return nil
})
}
// List log handler handles a list of LogHandlers.
type ListLogHandler struct {
handlers []LogHandler
}
// Create a new list of log handlers.
func NewListLogHandler(h1, h2 LogHandler) *ListLogHandler {
ll := &ListLogHandler{handlers: []LogHandler{h1, h2}}
return ll
}
func (ll *ListLogHandler) Log(r *log15.Record) (err error) {
// Log the record.
func (ll *ListLogHandler) Log(r *Record) (err error) {
for _, handler := range ll.handlers {
if err == nil {
err = handler.Log(r)
@@ -174,13 +191,18 @@ func (ll *ListLogHandler) Log(r *log15.Record) (err error) {
handler.Log(r)
}
}
return
}
// Add another log handler.
func (ll *ListLogHandler) Add(h LogHandler) {
if h != nil {
ll.handlers = append(ll.handlers, h)
}
}
// Remove a log handler.
func (ll *ListLogHandler) Del(h LogHandler) {
if h != nil {
for i, handler := range ll.handlers {
@@ -190,161 +212,3 @@ func (ll *ListLogHandler) Del(h LogHandler) {
}
}
}
type CompositeMultiHandler struct {
DebugHandler LogHandler
InfoHandler LogHandler
WarnHandler LogHandler
ErrorHandler LogHandler
CriticalHandler LogHandler
}
func NewCompositeMultiHandler() (*CompositeMultiHandler, LogHandler) {
cw := &CompositeMultiHandler{}
return cw, cw
}
func (h *CompositeMultiHandler) Log(r *log15.Record) (err error) {
var handler LogHandler
switch r.Lvl {
case log15.LvlInfo:
handler = h.InfoHandler
case log15.LvlDebug:
handler = h.DebugHandler
case log15.LvlWarn:
handler = h.WarnHandler
case log15.LvlError:
handler = h.ErrorHandler
case log15.LvlCrit:
handler = h.CriticalHandler
}
// Embed the caller function in the context
if handler != nil {
handler.Log(r)
}
return
}
func (h *CompositeMultiHandler) SetHandler(handler LogHandler, replace bool, level LogLevel) {
if handler == nil {
// Ignore empty handler
return
}
source := &h.DebugHandler
switch level {
case LvlDebug:
source = &h.DebugHandler
case LvlInfo:
source = &h.InfoHandler
case LvlWarn:
source = &h.WarnHandler
case LvlError:
source = &h.ErrorHandler
case LvlCrit:
source = &h.CriticalHandler
}
if !replace && *source != nil {
// If this already was a list add a new logger to it
if ll, found := (*source).(*ListLogHandler); found {
ll.Add(handler)
} else {
*source = NewListLogHandler(*source, handler)
}
} else {
*source = handler
}
}
func (h *CompositeMultiHandler) SetHandlers(handler LogHandler, options *LogOptions) {
if len(options.Levels) == 0 {
options.Levels = LvlAllList
}
// Set all levels
for _, lvl := range options.Levels {
h.SetHandler(handler, options.ReplaceExistingHandler, lvl)
}
}
func (h *CompositeMultiHandler) SetJson(writer io.Writer, options *LogOptions) {
handler := CallerFileHandler(StreamHandler(writer, log15.JsonFormatEx(
options.GetBoolDefault("pretty", false),
options.GetBoolDefault("lineSeparated", true),
)))
if options.HandlerWrap != nil {
handler = options.HandlerWrap.SetChild(handler)
}
h.SetHandlers(handler, options)
}
// Use built in rolling function
func (h *CompositeMultiHandler) SetJsonFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{
Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days
MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true),
}
h.SetJson(writer, options)
}
func (h *CompositeMultiHandler) SetTerminal(writer io.Writer, options *LogOptions) {
streamHandler := StreamHandler(
writer,
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
if os.Stdout == writer {
streamHandler = StreamHandler(
colorable.NewColorableStdout(),
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
} else if os.Stderr == writer {
streamHandler = StreamHandler(
colorable.NewColorableStderr(),
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
}
handler := CallerFileHandler(streamHandler)
if options.HandlerWrap != nil {
handler = options.HandlerWrap.SetChild(handler)
}
h.SetHandlers(handler, options)
}
// Use built in rolling function
func (h *CompositeMultiHandler) SetTerminalFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{
Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days
MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true),
}
h.SetTerminal(writer, options)
}
func (h *CompositeMultiHandler) Disable(levels ...LogLevel) {
if len(levels) == 0 {
levels = LvlAllList
}
for _, level := range levels {
switch level {
case LvlDebug:
h.DebugHandler = nil
case LvlInfo:
h.InfoHandler = nil
case LvlWarn:
h.WarnHandler = nil
case LvlError:
h.ErrorHandler = nil
case LvlCrit:
h.CriticalHandler = nil
}
}
}

190
logger/init.go Normal file
View File

@@ -0,0 +1,190 @@
package logger
// Get all handlers based on the Config (if available).
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/revel/config"
)
func InitializeFromConfig(basePath string, config *config.Context) (c *CompositeMultiHandler) {
// If running in test mode suppress anything that is not an error
if config != nil && config.BoolDefault(TestModeFlag, false) {
// Preconfigure all the options
config.SetOption("log.info.output", "none")
config.SetOption("log.debug.output", "none")
config.SetOption("log.warn.output", "none")
config.SetOption("log.error.output", "stderr")
config.SetOption("log.crit.output", "stderr")
}
// If the configuration has an all option we can skip some
c, _ = NewCompositeMultiHandler()
// Filters are assigned first, non filtered items override filters
if config != nil && !config.BoolDefault(TestModeFlag, false) {
initAllLog(c, basePath, config)
}
initLogLevels(c, basePath, config)
if c.CriticalHandler == nil && c.ErrorHandler != nil {
c.CriticalHandler = c.ErrorHandler
}
if config != nil && !config.BoolDefault(TestModeFlag, false) {
initFilterLog(c, basePath, config)
if c.CriticalHandler == nil && c.ErrorHandler != nil {
c.CriticalHandler = c.ErrorHandler
}
initRequestLog(c, basePath, config)
}
return c
}
// Init the log.all configuration options.
func initAllLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
if config != nil {
extraLogFlag := config.BoolDefault(SpecialUseFlag, false)
if output, found := config.String("log.all.output"); found {
// Set all output for the specified handler
if extraLogFlag {
log.Printf("Adding standard handler for levels to >%s< ", output)
}
initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, LvlAllList...))
}
}
}
// Init the filter options
// log.all.filter ....
// log.error.filter ....
func initFilterLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
if config != nil {
extraLogFlag := config.BoolDefault(SpecialUseFlag, false)
for _, logFilter := range logFilterList {
// Init for all filters
for _, name := range []string{
"all", "debug", "info", "warn", "error", "crit",
"trace", // TODO trace is deprecated
} {
optionList := config.Options(logFilter.LogPrefix + name + logFilter.LogSuffix)
for _, option := range optionList {
splitOptions := strings.Split(option, ".")
keyMap := map[string]interface{}{}
for x := 3; x < len(splitOptions); x += 2 {
keyMap[splitOptions[x]] = splitOptions[x+1]
}
phandler := logFilter.parentHandler(keyMap)
if extraLogFlag {
log.Printf("Adding key map handler %s %s output %s", option, name, config.StringDefault(option, ""))
fmt.Printf("Adding key map handler %s %s output %s matching %#v\n", option, name, config.StringDefault(option, ""), keyMap)
}
if name == "all" {
initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler))
} else {
initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler, toLevel[name]))
}
}
}
}
}
}
// Init the log.error, log.warn etc configuration options.
func initLogLevels(c *CompositeMultiHandler, basePath string, config *config.Context) {
for _, name := range []string{
"debug", "info", "warn", "error", "crit",
"trace", // TODO trace is deprecated
} {
if config != nil {
extraLogFlag := config.BoolDefault(SpecialUseFlag, false)
output, found := config.String("log." + name + ".output")
if found {
if extraLogFlag {
log.Printf("Adding standard handler %s output %s", name, output)
}
initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, toLevel[name]))
}
// Gets the list of options with said prefix
} else {
initHandlerFor(c, "stderr", basePath, NewLogOptions(config, true, nil, toLevel[name]))
}
}
}
// Init the request log options.
func initRequestLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
// Request logging to a separate output handler
// This takes the InfoHandlers and adds a MatchAbHandler handler to it to direct
// context with the word "section=requestlog" to that handler.
// Note if request logging is not enabled the MatchAbHandler will not be added and the
// request log messages will be sent out the INFO handler
outputRequest := "stdout"
if config != nil {
outputRequest = config.StringDefault("log.request.output", "")
}
oldInfo := c.InfoHandler
c.InfoHandler = nil
if outputRequest != "" {
initHandlerFor(c, outputRequest, basePath, NewLogOptions(config, false, nil, LvlInfo))
}
if c.InfoHandler != nil || oldInfo != nil {
if c.InfoHandler == nil {
c.InfoHandler = oldInfo
} else {
c.InfoHandler = MatchAbHandler("section", "requestlog", c.InfoHandler, oldInfo)
}
}
}
// Returns a handler for the level using the output string
// Accept formats for output string are
// LogFunctionMap[value] callback function
// `stdout` `stderr` `full/file/path/to/location/app.log` `full/file/path/to/location/app.json`.
func initHandlerFor(c *CompositeMultiHandler, output, basePath string, options *LogOptions) {
if options.Ctx != nil {
options.SetExtendedOptions(
"noColor", !options.Ctx.BoolDefault("log.colorize", true),
"smallDate", options.Ctx.BoolDefault("log.smallDate", true),
"maxSize", options.Ctx.IntDefault("log.maxsize", 1024*10),
"maxAge", options.Ctx.IntDefault("log.maxage", 14),
"maxBackups", options.Ctx.IntDefault("log.maxbackups", 14),
"compressBackups", !options.Ctx.BoolDefault("log.compressBackups", true),
)
}
output = strings.TrimSpace(output)
if funcHandler, found := LogFunctionMap[output]; found {
funcHandler(c, options)
} else {
switch output {
case "":
fallthrough
case "off":
// No handler, discard data
default:
// Write to file specified
if !filepath.IsAbs(output) {
output = filepath.Join(basePath, output)
}
if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil {
log.Panic(err)
}
if strings.HasSuffix(output, "json") {
c.SetJSONFile(output, options)
} else {
// Override defaults for a terminal file
options.SetExtendedOptions("noColor", true)
options.SetExtendedOptions("smallDate", false)
c.SetTerminalFile(output, options)
}
}
}
}

346
logger/init_test.go Normal file
View File

@@ -0,0 +1,346 @@
// Copyright (c) 2012-2018 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package logger_test
import (
"os"
"strings"
"testing"
"github.com/revel/cmd/logger"
"github.com/revel/config"
"github.com/stretchr/testify/assert"
)
type (
// A counter for the tester.
testCounter struct {
debug, info, warn, error, critical int
}
// The data to tes.
testData struct {
config []string
result testResult
tc *testCounter
}
// The test result.
testResult struct {
debug, info, warn, error, critical int
}
)
// Single test cases.
var singleCases = []testData{
{
config: []string{"log.crit.output"},
result: testResult{0, 0, 0, 0, 1},
},
{
config: []string{"log.error.output"},
result: testResult{0, 0, 0, 1, 1},
},
{
config: []string{"log.warn.output"},
result: testResult{0, 0, 1, 0, 0},
},
{
config: []string{"log.info.output"},
result: testResult{0, 1, 0, 0, 0},
},
{
config: []string{"log.debug.output"},
result: testResult{1, 0, 0, 0, 0},
},
}
// Test singles.
func TestSingleCases(t *testing.T) {
rootLog := logger.New()
for _, testCase := range singleCases {
testCase.logTest(t, rootLog)
testCase.validate(t)
}
}
// Filter test cases.
var filterCases = []testData{
{
config: []string{"log.crit.filter.module.app"},
result: testResult{0, 0, 0, 0, 1},
},
{
config: []string{"log.crit.filter.module.appa"},
result: testResult{0, 0, 0, 0, 0},
},
{
config: []string{"log.error.filter.module.app"},
result: testResult{0, 0, 0, 1, 1},
},
{
config: []string{"log.error.filter.module.appa"},
result: testResult{0, 0, 0, 0, 0},
},
{
config: []string{"log.warn.filter.module.app"},
result: testResult{0, 0, 1, 0, 0},
},
{
config: []string{"log.warn.filter.module.appa"},
result: testResult{0, 0, 0, 0, 0},
},
{
config: []string{"log.info.filter.module.app"},
result: testResult{0, 1, 0, 0, 0},
},
{
config: []string{"log.info.filter.module.appa"},
result: testResult{0, 0, 0, 0, 0},
},
{
config: []string{"log.debug.filter.module.app"},
result: testResult{1, 0, 0, 0, 0},
},
{
config: []string{"log.debug.filter.module.appa"},
result: testResult{0, 0, 0, 0, 0},
},
}
// Filter test.
func TestFilterCases(t *testing.T) {
rootLog := logger.New("module", "app")
for _, testCase := range filterCases {
testCase.logTest(t, rootLog)
testCase.validate(t)
}
}
// Inverse test cases.
var nfilterCases = []testData{
{
config: []string{"log.crit.nfilter.module.appa"},
result: testResult{0, 0, 0, 0, 1},
},
{
config: []string{"log.crit.nfilter.modules.appa"},
result: testResult{0, 0, 0, 0, 0},
},
{
config: []string{"log.crit.nfilter.module.app"},
result: testResult{0, 0, 0, 0, 0},
},
{
config: []string{"log.error.nfilter.module.appa"}, // Special case, when error is not nill critical inherits from error
result: testResult{0, 0, 0, 1, 1},
},
{
config: []string{"log.error.nfilter.module.app"},
result: testResult{0, 0, 0, 0, 0},
},
{
config: []string{"log.warn.nfilter.module.appa"},
result: testResult{0, 0, 1, 0, 0},
},
{
config: []string{"log.warn.nfilter.module.app"},
result: testResult{0, 0, 0, 0, 0},
},
{
config: []string{"log.info.nfilter.module.appa"},
result: testResult{0, 1, 0, 0, 0},
},
{
config: []string{"log.info.nfilter.module.app"},
result: testResult{0, 0, 0, 0, 0},
},
{
config: []string{"log.debug.nfilter.module.appa"},
result: testResult{1, 0, 0, 0, 0},
},
{
config: []string{"log.debug.nfilter.module.app"},
result: testResult{0, 0, 0, 0, 0},
},
}
// Inverse test.
func TestNotFilterCases(t *testing.T) {
rootLog := logger.New("module", "app")
for _, testCase := range nfilterCases {
testCase.logTest(t, rootLog)
testCase.validate(t)
}
}
// off test cases.
var offCases = []testData{
{
config: []string{"log.all.output", "log.error.output=off"},
result: testResult{1, 1, 1, 0, 1},
},
}
// Off test.
func TestOffCases(t *testing.T) {
rootLog := logger.New("module", "app")
for _, testCase := range offCases {
testCase.logTest(t, rootLog)
testCase.validate(t)
}
}
// Duplicate test cases.
var duplicateCases = []testData{
{
config: []string{"log.all.output", "log.error.output", "log.error.filter.module.app"},
result: testResult{1, 1, 1, 2, 1},
},
}
// test duplicate cases.
func TestDuplicateCases(t *testing.T) {
rootLog := logger.New("module", "app")
for _, testCase := range duplicateCases {
testCase.logTest(t, rootLog)
testCase.validate(t)
}
}
// Contradicting cases.
var contradictCases = []testData{
{
config: []string{"log.all.output", "log.error.output=off", "log.all.output"},
result: testResult{1, 1, 1, 0, 1},
},
{
config: []string{"log.all.output", "log.error.output=off", "log.debug.filter.module.app"},
result: testResult{2, 1, 1, 0, 1},
},
{
config: []string{"log.all.filter.module.app", "log.info.output=off", "log.info.filter.module.app"},
result: testResult{1, 2, 1, 1, 1},
},
{
config: []string{"log.all.output", "log.info.output=off", "log.info.filter.module.app"},
result: testResult{1, 1, 1, 1, 1},
},
}
// Contradiction test.
func TestContradictCases(t *testing.T) {
rootLog := logger.New("module", "app")
for _, testCase := range contradictCases {
testCase.logTest(t, rootLog)
testCase.validate(t)
}
}
// All test cases.
var allCases = []testData{
{
config: []string{"log.all.filter.module.app"},
result: testResult{1, 1, 1, 1, 1},
},
{
config: []string{"log.all.output"},
result: testResult{2, 2, 2, 2, 2},
},
}
// All tests.
func TestAllCases(t *testing.T) {
rootLog := logger.New("module", "app")
for i, testCase := range allCases {
testCase.logTest(t, rootLog)
allCases[i] = testCase
}
rootLog = logger.New()
for i, testCase := range allCases {
testCase.logTest(t, rootLog)
allCases[i] = testCase
}
for _, testCase := range allCases {
testCase.validate(t)
}
}
func (c *testCounter) Log(r *logger.Record) error {
switch r.Level {
case logger.LvlDebug:
c.debug++
case logger.LvlInfo:
c.info++
case logger.LvlWarn:
c.warn++
case logger.LvlError:
c.error++
case logger.LvlCrit:
c.critical++
default:
panic("Unknown log level")
}
return nil
}
func (td *testData) logTest(t *testing.T, rootLog logger.MultiLogger) {
t.Helper()
if td.tc == nil {
td.tc = &testCounter{}
counterInit(td.tc)
}
newContext := config.NewContext()
for _, i := range td.config {
iout := strings.Split(i, "=")
if len(iout) > 1 {
newContext.SetOption(iout[0], iout[1])
} else {
newContext.SetOption(i, "test")
}
}
newContext.SetOption("specialUseFlag", "true")
handler := logger.InitializeFromConfig("test", newContext)
rootLog.SetHandler(handler)
td.runLogTest(rootLog)
}
func (td *testData) runLogTest(log logger.MultiLogger) {
log.Debug("test")
log.Info("test")
log.Warn("test")
log.Error("test")
log.Crit("test")
}
func (td *testData) validate(t *testing.T) {
t.Helper()
t.Logf("Test %#v expected %#v", td.tc, td.result)
assert.Equal(t, td.result.debug, td.tc.debug, "Debug failed "+strings.Join(td.config, " "))
assert.Equal(t, td.result.info, td.tc.info, "Info failed "+strings.Join(td.config, " "))
assert.Equal(t, td.result.warn, td.tc.warn, "Warn failed "+strings.Join(td.config, " "))
assert.Equal(t, td.result.error, td.tc.error, "Error failed "+strings.Join(td.config, " "))
assert.Equal(t, td.result.critical, td.tc.critical, "Critical failed "+strings.Join(td.config, " "))
}
// Add test to the function map.
func counterInit(tc *testCounter) {
logger.LogFunctionMap["test"] = func(c *logger.CompositeMultiHandler, logOptions *logger.LogOptions) {
// Output to the test log and the stdout
outHandler := logger.LogHandler(
logger.NewListLogHandler(tc,
logger.StreamHandler(os.Stdout, logger.TerminalFormatHandler(false, true))),
)
if logOptions.HandlerWrap != nil {
outHandler = logOptions.HandlerWrap.SetChild(outHandler)
}
c.SetHandlers(outHandler, logOptions)
}
}

View File

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

View File

@@ -0,0 +1,37 @@
package logger
import (
"os"
)
// LogFunctionMap can be added to, so that you can specify your own logging mechanism
// it has defaults for off, stdout, stderr.
var LogFunctionMap = map[string]func(*CompositeMultiHandler, *LogOptions){
// Do nothing - set the logger off
"off": func(c *CompositeMultiHandler, logOptions *LogOptions) {
// Only drop the results if there is a parent handler defined
if logOptions.HandlerWrap != nil {
for _, l := range logOptions.Levels {
c.SetHandler(logOptions.HandlerWrap.SetChild(NilHandler()), logOptions.ReplaceExistingHandler, l)
}
} else {
// Clear existing handler
c.SetHandlers(NilHandler(), logOptions)
}
},
// Do nothing - set the logger off
"": func(*CompositeMultiHandler, *LogOptions) {},
// Set the levels to stdout, replace existing
"stdout": func(c *CompositeMultiHandler, logOptions *LogOptions) {
if logOptions.Ctx != nil {
logOptions.SetExtendedOptions(
"noColor", !logOptions.Ctx.BoolDefault("log.colorize", true),
"smallDate", logOptions.Ctx.BoolDefault("log.smallDate", true))
}
c.SetTerminal(os.Stdout, logOptions)
},
// Set the levels to stderr output to terminal
"stderr": func(c *CompositeMultiHandler, logOptions *LogOptions) {
c.SetTerminal(os.Stderr, logOptions)
},
}

View File

@@ -2,64 +2,94 @@ package logger
import (
"fmt"
"time"
"github.com/revel/config"
"github.com/revel/log15"
"log"
"os"
)
// The LogHandler defines the interface to handle the log records
// The LogHandler defines the interface to handle the log records.
type (
// The Multilogger reduces the number of exposed defined logging variables,
// and allows the output to be easily refined
// and allows the output to be easily refined.
MultiLogger interface {
//log15.Logger
//// New returns a new Logger that has this logger's context plus the given context
// New returns a new Logger that has this logger's context plus the given context
New(ctx ...interface{}) MultiLogger
//
//// SetHandler updates the logger to write records to the specified handler.
// SetHandler updates the logger to write records to the specified handler.
SetHandler(h LogHandler)
// Set the stack depth for the logger
SetStackDepth(int) MultiLogger
//
//// Log a message at the given level with context key/value pairs
// Log a message at the given level with context key/value pairs
Debug(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters
Debugf(msg string, params ...interface{})
// Log a message at the given level with context key/value pairs
Info(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters
Infof(msg string, params ...interface{})
// Log a message at the given level with context key/value pairs
Warn(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters
Warnf(msg string, params ...interface{})
// Log a message at the given level with context key/value pairs
Error(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters
Errorf(msg string, params ...interface{})
// Log a message at the given level with context key/value pairs
Crit(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters
Critf(msg string, params ...interface{})
//// Logs a message as an Crit and exits
// Log a message at the given level with context key/value pairs and exits
Fatal(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters and exits
Fatalf(msg string, params ...interface{})
//// Logs a message as an Crit and panics
// Log a message at the given level with context key/value pairs and panics
Panic(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters and panics
Panicf(msg string, params ...interface{})
}
// The log handler interface.
LogHandler interface {
log15.Handler
Log(*Record) error
// log15.Handler
}
// The log stack handler interface.
LogStackHandler interface {
LogHandler
GetStack() int
}
// The log handler interface which has child logs.
ParentLogHandler interface {
SetChild(handler LogHandler) LogHandler
}
// The log format interface.
LogFormat interface {
log15.Format
Format(r *Record) []byte
}
LogLevel log15.Lvl
RevelLogger struct {
log15.Logger
}
// The log level type.
LogLevel int
// Used for the callback to LogFunctionMap
// Used for the callback to LogFunctionMap.
LogOptions struct {
Ctx *config.Context
ReplaceExistingHandler bool
@@ -67,132 +97,74 @@ type (
Levels []LogLevel
ExtendedOptions map[string]interface{}
}
// The log record.
Record struct {
Message string // The message
Time time.Time // The time
Level LogLevel // The level
Call CallStack // The call stack if built
Context ContextMap // The context
}
// The lazy structure to implement a function to be invoked only if needed.
Lazy struct {
Fn interface{} // the function
}
// Currently the only requirement for the callstack is to support the Formatter method
// which stack.Call does so we use that.
CallStack interface {
fmt.Formatter // Requirement
}
)
// FormatFunc returns a new Format object which uses
// the given function to perform record formatting.
func FormatFunc(f func(*Record) []byte) LogFormat {
return formatFunc(f)
}
type formatFunc func(*Record) []byte
func (f formatFunc) Format(r *Record) []byte {
return f(r)
}
func NewRecord(message string, level LogLevel) *Record {
return &Record{Message: message, Context: ContextMap{}, Level: level}
}
const (
LvlDebug = LogLevel(log15.LvlDebug)
LvlInfo = LogLevel(log15.LvlInfo)
LvlWarn = LogLevel(log15.LvlWarn)
LvlError = LogLevel(log15.LvlError)
LvlCrit = LogLevel(log15.LvlCrit)
LvlCrit LogLevel = iota // Critical
LvlError // Error
LvlWarn // Warning
LvlInfo // Information
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}
// The log function map can be added to, so that you can specify your own logging mechanism
var LogFunctionMap = map[string]func(*CompositeMultiHandler, *LogOptions){
// Do nothing - set the logger off
"off": func(c *CompositeMultiHandler, logOptions *LogOptions) {
// Only drop the results if there is a parent handler defined
if logOptions.HandlerWrap != nil {
for _, l := range logOptions.Levels {
c.SetHandler(logOptions.HandlerWrap.SetChild(NilHandler()), logOptions.ReplaceExistingHandler, l)
}
}
},
// Do nothing - set the logger off
"": func(*CompositeMultiHandler, *LogOptions) {},
// Set the levels to stdout, replace existing
"stdout": func(c *CompositeMultiHandler, logOptions *LogOptions) {
if logOptions.Ctx != nil {
logOptions.SetExtendedOptions(
"noColor", !logOptions.Ctx.BoolDefault("log.colorize", true),
"smallDate", logOptions.Ctx.BoolDefault("log.smallDate", true))
}
c.SetTerminal(os.Stdout, logOptions)
},
// Set the levels to stderr output to terminal
"stderr": func(c *CompositeMultiHandler, logOptions *LogOptions) {
c.SetTerminal(os.Stderr, logOptions)
},
}
// Set the systems default logger
// Default logs will be captured and handled by revel at level info
func SetDefaultLog(fromLog MultiLogger) {
log.SetOutput(loggerRewrite{Logger: fromLog, Level: log15.LvlInfo, hideDeprecated: true})
// No need to show date and time, that will be logged with revel
log.SetFlags(0)
}
// Formatted debug call
func (rl *RevelLogger) Debugf(msg string, param ...interface{}) {
rl.Debug(fmt.Sprintf(msg, param...))
}
// Formatted info call
func (rl *RevelLogger) Infof(msg string, param ...interface{}) {
rl.Info(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Warnf(msg string, param ...interface{}) {
rl.Warn(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Errorf(msg string, param ...interface{}) {
rl.Error(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Critf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Fatalf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...))
os.Exit(1)
}
func (rl *RevelLogger) Panicf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...))
panic(msg)
}
func (rl *RevelLogger) Fatal(msg string, ctx ...interface{}) {
rl.Crit(msg, ctx...)
os.Exit(1)
}
func (rl *RevelLogger) Panic(msg string, ctx ...interface{}) {
rl.Crit(msg, ctx...)
panic(msg)
}
// Override log15 method
func (rl *RevelLogger) New(ctx ...interface{}) MultiLogger {
old := &RevelLogger{Logger: rl.Logger.New(ctx...)}
return old
}
// Set the stack level to check for the caller
func (rl *RevelLogger) SetStackDepth(amount int) MultiLogger {
rl.Logger.SetStackDepth(amount) // Ignore the logger returned
return rl
}
// Create a new logger
func New(ctx ...interface{}) MultiLogger {
r := &RevelLogger{Logger: log15.New(ctx...)}
r.SetStackDepth(1)
return r
}
// Set the handler in the Logger
func (rl *RevelLogger) SetHandler(h LogHandler) {
rl.Logger.SetHandler(h)
}
// Implements the ParentLogHandler.
type parentLogHandler struct {
setChild func(handler LogHandler) LogHandler
}
// Create a new parent log handler.
func NewParentLogHandler(callBack func(child LogHandler) LogHandler) ParentLogHandler {
return &parentLogHandler{callBack}
}
// Sets the child of the log handler.
func (p *parentLogHandler) SetChild(child LogHandler) LogHandler {
return p.setChild(child)
}
// Create a new log options
// Create a new log options.
func NewLogOptions(cfg *config.Context, replaceHandler bool, phandler ParentLogHandler, lvl ...LogLevel) (logOptions *LogOptions) {
logOptions = &LogOptions{
Ctx: cfg,
Ctx: cfg,
ReplaceExistingHandler: replaceHandler,
HandlerWrap: phandler,
Levels: lvl,
@@ -201,24 +173,30 @@ func NewLogOptions(cfg *config.Context, replaceHandler bool, phandler ParentLogH
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{}) {
for x := 0; x < len(options); x += 2 {
l.ExtendedOptions[options[x].(string)] = options[x+1]
}
}
// Gets a string option with default.
func (l *LogOptions) GetStringDefault(option, value string) string {
if v, found := l.ExtendedOptions[option]; found {
return v.(string)
}
return value
}
// Gets an int option with default.
func (l *LogOptions) GetIntDefault(option string, value int) int {
if v, found := l.ExtendedOptions[option]; found {
return v.(int)
}
return value
}
// Gets a boolean option with default.
func (l *LogOptions) GetBoolDefault(option string, value bool) bool {
if v, found := l.ExtendedOptions[option]; found {
return v.(bool)

144
logger/revel_logger.go Normal file
View File

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

View File

@@ -2,8 +2,8 @@ package logger
import (
"bytes"
"encoding/json"
"fmt"
"github.com/revel/log15"
"reflect"
"strconv"
"sync"
@@ -18,69 +18,66 @@ const (
errorKey = "REVEL_ERROR"
)
var (
// Name the log level
toRevel = map[log15.Lvl]string{log15.LvlDebug: "DEBUG",
log15.LvlInfo: "INFO", log15.LvlWarn: "WARN", log15.LvlError: "ERROR", log15.LvlCrit: "CRIT"}
)
var levelString = map[LogLevel]string{
LvlDebug: "DEBUG",
LvlInfo: "INFO", LvlWarn: "WARN", LvlError: "ERROR", LvlCrit: "CRIT",
}
// Outputs to the terminal in a format like below
// INFO 09:11:32 server-engine.go:169: Request Stats
// INFO 09:11:32 server-engine.go:169: Request Stats.
func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
dateFormat := termTimeFormat
if smallDate {
dateFormat = termSmallTimeFormat
}
return log15.FormatFunc(func(r *log15.Record) []byte {
return FormatFunc(func(r *Record) []byte {
// Bash coloring http://misc.flogisoft.com/bash/tip_colors_and_formatting
var color = 0
switch r.Lvl {
case log15.LvlCrit:
color := 0
switch r.Level {
case LvlCrit:
// Magenta
color = 35
case log15.LvlError:
case LvlError:
// Red
color = 31
case log15.LvlWarn:
case LvlWarn:
// Yellow
color = 33
case log15.LvlInfo:
case LvlInfo:
// Green
color = 32
case log15.LvlDebug:
case LvlDebug:
// Cyan
color = 36
}
b := &bytes.Buffer{}
caller := findInContext("caller", r.Ctx)
module := findInContext("module", r.Ctx)
if noColor == false && color > 0 {
caller, _ := r.Context["caller"].(string)
module, _ := r.Context["module"].(string)
if !noColor && color > 0 {
if len(module) > 0 {
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %6s %13s: %-40s ", color, toRevel[r.Lvl], r.Time.Format(dateFormat), module, caller, r.Msg)
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %6s %13s: %-40s ", color, levelString[r.Level], r.Time.Format(dateFormat), module, caller, r.Message)
} else {
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %13s: %-40s ", color, toRevel[r.Lvl], r.Time.Format(dateFormat), caller, r.Msg)
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %13s: %-40s ", color, levelString[r.Level], r.Time.Format(dateFormat), caller, r.Message)
}
} else {
fmt.Fprintf(b, "%-5s %s %6s %13s: %-40s", toRevel[r.Lvl], r.Time.Format(dateFormat), module, caller, r.Msg)
fmt.Fprintf(b, "%-5s %s %6s %13s: %-40s", levelString[r.Level], r.Time.Format(dateFormat), module, caller, r.Message)
}
for i := 0; i < len(r.Ctx); i += 2 {
i := 0
for k, v := range r.Context {
if i != 0 {
b.WriteByte(' ')
}
k, ok := r.Ctx[i].(string)
if k == "caller" || k == "fn" || k == "module" {
i++
if k == "module" || k == "caller" {
continue
}
v := formatLogfmtValue(r.Ctx[i+1])
if !ok {
k, v = errorKey, formatLogfmtValue(k)
}
v := formatLogfmtValue(v)
// 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)
} else {
b.WriteString(k)
@@ -94,17 +91,8 @@ func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
return b.Bytes()
})
}
func findInContext(key string, ctx []interface{}) string {
for i := 0; i < len(ctx); i += 2 {
k := ctx[i].(string)
if key == k {
return formatLogfmtValue(ctx[i+1])
}
}
return ""
}
// formatValue formats a value for serialization
// formatValue formats a value for serialization.
func formatLogfmtValue(value interface{}) string {
if value == nil {
return "nil"
@@ -132,6 +120,8 @@ func formatLogfmtValue(value interface{}) string {
return escapeString(fmt.Sprintf("%+v", value))
}
}
// Format the value in json format.
func formatShared(value interface{}) (result interface{}) {
defer func() {
if err := recover(); err != nil {
@@ -158,10 +148,12 @@ func formatShared(value interface{}) (result interface{}) {
}
}
// A reusuable buffer for outputting data.
var stringBufPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
// Escape the string when needed.
func escapeString(s string) string {
needsQuotes := false
needsEscape := false
@@ -173,7 +165,7 @@ func escapeString(s string) string {
needsEscape = true
}
}
if needsEscape == false && needsQuotes == false {
if !needsEscape && !needsQuotes {
return s
}
e := stringBufPool.Get().(*bytes.Buffer)
@@ -204,3 +196,50 @@ func escapeString(s string) string {
stringBufPool.Put(e)
return ret
}
// JSONFormatEx formats log records as JSON objects. If pretty is true,
// records will be pretty-printed. If lineSeparated is true, records
// will be logged with a new line between each record.
func JSONFormatEx(pretty, lineSeparated bool) LogFormat {
jsonMarshal := json.Marshal
if pretty {
jsonMarshal = func(v interface{}) ([]byte, error) {
return json.MarshalIndent(v, "", " ")
}
}
return FormatFunc(func(r *Record) []byte {
props := make(map[string]interface{})
props["t"] = r.Time
props["lvl"] = levelString[r.Level]
props["msg"] = r.Message
for k, v := range r.Context {
props[k] = formatJSONValue(v)
}
b, err := jsonMarshal(props)
if err != nil {
b, _ = jsonMarshal(map[string]string{
errorKey: err.Error(),
})
return b
}
if lineSeparated {
b = append(b, '\n')
}
return b
})
}
func formatJSONValue(value interface{}) interface{} {
value = formatShared(value)
switch value.(type) {
case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string:
return value
default:
return fmt.Sprintf("%+v", value)
}
}

View File

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

111
logger/wrap_handlers.go Normal file
View File

@@ -0,0 +1,111 @@
package logger
// FuncHandler returns a Handler that logs records with the given
// function.
import (
"fmt"
"reflect"
"sync"
"time"
)
// Error is used for constant errors.
type Error string
// Error implements the error interface.
func (e Error) Error() string {
return string(e)
}
const (
ErrNotFunc Error = "not a function"
ErrTakesArgs Error = "takes arguments"
ErrNoReturn Error = "no return value"
)
// Function handler wraps the declared function and returns the handler for it.
func FuncHandler(fn func(r *Record) error) LogHandler {
return funcHandler(fn)
}
// The type declaration for the function.
type funcHandler func(r *Record) error
// The implementation of the Log.
func (h funcHandler) Log(r *Record) error {
return h(r)
}
// This function allows you to do a full declaration for the log,
// it is recommended you use FuncHandler instead.
func HandlerFunc(log func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error) LogHandler {
return remoteHandler(log)
}
// The type used for the HandlerFunc.
type remoteHandler func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error
// The Log implementation.
func (c remoteHandler) Log(record *Record) error {
return c(record.Message, record.Time, record.Level, record.Call, record.Context)
}
// SyncHandler can be wrapped around a handler to guarantee that
// only a single Log operation can proceed at a time. It's necessary
// for thread-safe concurrent writes.
func SyncHandler(h LogHandler) LogHandler {
var mu sync.Mutex
return FuncHandler(func(r *Record) error {
defer mu.Unlock()
mu.Lock()
return h.Log(r)
})
}
// LazyHandler writes all values to the wrapped handler after evaluating
// any lazy functions in the record's context. It is already wrapped
// around StreamHandler and SyslogHandler in this library, you'll only need
// it if you write your own Handler.
func LazyHandler(h LogHandler) LogHandler {
return FuncHandler(func(r *Record) error {
for k, v := range r.Context {
if lz, ok := v.(Lazy); ok {
_, err := evaluateLazy(lz)
if err != nil {
r.Context[errorKey] = "bad lazy " + k
}
}
}
return h.Log(r)
})
}
func evaluateLazy(lz Lazy) (interface{}, error) {
t := reflect.TypeOf(lz.Fn)
if t.Kind() != reflect.Func {
return nil, fmt.Errorf("%w %+v", ErrNotFunc, lz.Fn)
}
if t.NumIn() > 0 {
return nil, fmt.Errorf("%w %+v", ErrTakesArgs, lz.Fn)
}
if t.NumOut() == 0 {
return nil, fmt.Errorf("%w %+v", ErrNoReturn, lz.Fn)
}
value := reflect.ValueOf(lz.Fn)
results := value.Call([]reflect.Value{})
if len(results) == 1 {
return results[0].Interface(), nil
}
values := make([]interface{}, len(results))
for i, v := range results {
values[i] = v.Interface()
}
return values, nil
}

10
model/command/build.go Normal file
View File

@@ -0,0 +1,10 @@
package command
type (
Build struct {
ImportCommand
TargetPath string `short:"t" long:"target-path" description:"Path to target folder. Folder will be completely deleted if it exists" required:"false"`
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"`
}
)

7
model/command/clean.go Normal file
View File

@@ -0,0 +1,7 @@
package command
type (
Clean struct {
ImportCommand
}
)

View File

@@ -0,0 +1,7 @@
package command
type (
ImportCommand struct {
ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"`
}
)

12
model/command/new.go Normal file
View File

@@ -0,0 +1,12 @@
package command
type (
New struct {
ImportCommand
SkeletonPath string `short:"s" long:"skeleton" description:"Path to skeleton folder (Must exist on GO PATH)" required:"false"`
Package string `short:"p" long:"package" description:"The package name, this becomes the repfix to the app name, if defined vendored is set to true" required:"false"`
NotVendored bool `long:"no-vendor" description:"True if project should not be configured with a go.mod, this requires you to have the project on the GOPATH, this is only compatible with go versions v1.12 or older"`
Run bool `short:"r" long:"run" description:"True if you want to run the application right away"`
Callback func() error
}
)

10
model/command/package.go Normal file
View File

@@ -0,0 +1,10 @@
package command
type (
Package struct {
ImportCommand
TargetPath string `short:"t" long:"target-path" description:"Full path and filename of target package to deploy" required:"false"`
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"`
}
)

10
model/command/run.go Normal file
View File

@@ -0,0 +1,10 @@
package command
type (
Run struct {
ImportCommand
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
Port int `short:"p" long:"port" default:"-1" description:"The port to listen" `
NoProxy bool `short:"n" long:"no-proxy" description:"True if proxy server should not be started. This will only update the main and routes files on change"`
}
)

View File

@@ -0,0 +1,9 @@
package command
type (
Test struct {
ImportCommand
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
Function string `short:"f" long:"suite-function" description:"The suite.function"`
}
)

7
model/command/version.go Normal file
View File

@@ -0,0 +1,7 @@
package command
type (
Version struct {
ImportCommand
}
)

View File

@@ -1,14 +1,23 @@
package model
// The constants
import (
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/revel/cmd"
"github.com/revel/cmd/model/command"
"github.com/revel/cmd/utils"
)
// The constants.
const (
NEW COMMAND = iota + 1
RUN
@@ -19,70 +28,45 @@ const (
VERSION
)
const (
ErrImportInvalid Error = "invalid import path, working dir is in GOPATH root"
ErrUnableToImport Error = "unable to determine import path from"
)
type (
// The Revel command type
// The Revel command type.
COMMAND int
// The Command config for the line input
// The Command config for the line input.
CommandConfig struct {
Index COMMAND // The index
Verbose bool `short:"v" long:"debug" description:"If set the logger is set to verbose"` // True if debug is active
HistoricMode bool `long:"historic-run-mode" description:"If set the runmode is passed a string not json"` // True if debug is active
ImportPath string // The import path (converted from various commands)
GoPath string // The GoPath
GoCmd string // The full path to the go executable
SrcRoot string // The source root
AppPath string // The application path
AppName string // The applicaiton name
BasePath string // The base path
SkeletonPath string // The skeleton path
BuildFlags []string `short:"X" long:"build-flags" description:"These flags will be used when building the application. May be specified multiple times, only applicable for Build, Run, Package, Test commands"`
// The new command
New struct {
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
Skeleton string `short:"s" long:"skeleton" description:"Path to skeleton folder (Must exist on GO PATH)" required:"false"`
Vendored bool `short:"V" long:"vendor" description:"True if project should contain a vendor folder to be initialized. Creates the vendor folder and the 'Gopkg.toml' file in the root"`
Run bool `short:"r" long:"run" description:"True if you want to run the application right away"`
} `command:"new"`
// The build command
Build struct {
TargetPath string `short:"t" long:"target-path" description:"Path to target folder. Folder will be completely deleted if it exists" required:"true"`
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"`
} `command:"build"`
// The run command
Run struct {
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
Port string `short:"p" long:"port" description:"The port to listen"`
NoProxy bool `short:"n" long:"no-proxy" description:"True if proxy server should not be started. This will only update the main and routes files on change"`
} `command:"run"`
// The package command
Package struct {
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"`
} `command:"package"`
// The clean command
Clean struct {
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
} `command:"clean"`
// The test command
Test struct {
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"true"`
Function string `short:"f" long:"suite-function" description:"The suite.function"`
} `command:"test"`
// The version command
Version struct {
ImportPath string `short:"a" long:"application-path" description:"Path to applicaiton folder" required:"false"`
} `command:"version"`
Index COMMAND // The index
Verbose []bool `short:"v" long:"debug" description:"If set the logger is set to verbose"` // True if debug is active
FrameworkVersion *Version // The framework version
CommandVersion *Version // The command version
HistoricMode bool `long:"historic-run-mode" description:"If set the runmode is passed a string not json"` // True if debug is active
ImportPath string // The import path (relative to a GOPATH)
GoPath string // The GoPath
GoCmd string // The full path to the go executable
// SrcRoot string // The source root
AppPath string // The application path (absolute)
AppName string // The application name
HistoricBuildMode bool `long:"historic-build-mode" description:"If set the code is scanned using the original parsers, not the go.1.11+"` // True if debug is active
Vendored bool // True if the application is vendored
PackageResolver func(pkgName string) error // a package resolver for the config
BuildFlags []string `short:"X" long:"build-flags" description:"These flags will be used when building the application. May be specified multiple times, only applicable for Build, Run, Package, Test commands"`
GoModFlags []string `long:"gomod-flags" description:"These flags will execute go mod commands for each flag, this happens during the build process"`
New command.New `command:"new"`
Build command.Build `command:"build"`
Run command.Run `command:"run"`
Package command.Package `command:"package"`
Clean command.Clean `command:"clean"`
Test command.Test `command:"test"`
Version command.Version `command:"version"`
}
)
// Updates the import path depending on the command
func (c *CommandConfig) UpdateImportPath() bool {
// Updates the import path depending on the command.
func (c *CommandConfig) UpdateImportPath() error {
var importPath string
required := true
switch c.Index {
@@ -90,20 +74,26 @@ func (c *CommandConfig) UpdateImportPath() bool {
importPath = c.New.ImportPath
case RUN:
importPath = c.Run.ImportPath
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
case BUILD:
importPath = c.Build.ImportPath
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
case PACKAGE:
importPath = c.Package.ImportPath
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
case CLEAN:
importPath = c.Clean.ImportPath
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
case TEST:
importPath = c.Test.ImportPath
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
case VERSION:
importPath = c.Version.ImportPath
required = false
}
if len(importPath) == 0 || filepath.IsAbs(importPath) || importPath[0] == '.' {
utils.Logger.Info("Import path is absolute or not specified", "path", importPath)
// Try to determine the import path from the GO paths and the command line
currentPath, err := os.Getwd()
if len(importPath) > 0 {
@@ -113,17 +103,20 @@ func (c *CommandConfig) UpdateImportPath() bool {
}
// For an absolute path
currentPath, _ = filepath.Abs(importPath)
}
if err == nil {
for _, path := range strings.Split(build.Default.GOPATH, string(filepath.ListSeparator)) {
utils.Logger.Infof("Checking import path %s with %s", currentPath, path)
if strings.HasPrefix(currentPath, path) {
importPath = currentPath[len(path) + 1:]
if strings.HasPrefix(currentPath, path) && len(currentPath) > len(path)+1 {
importPath = currentPath[len(path)+1:]
// Remove the source from the path if it is there
if len(importPath)>4 && strings.ToLower(importPath[0:4]) == "src/" {
if len(importPath) > 4 && (strings.ToLower(importPath[0:4]) == "src/" || strings.ToLower(importPath[0:4]) == "src\\") {
importPath = importPath[4:]
} else if importPath == "src" {
if c.Index != VERSION {
return ErrImportInvalid
}
importPath = ""
}
utils.Logger.Info("Updated import path", "path", importPath)
@@ -133,6 +126,238 @@ func (c *CommandConfig) UpdateImportPath() bool {
}
c.ImportPath = importPath
utils.Logger.Info("Returned import path", "path", importPath, "buildpath",build.Default.GOPATH)
return (len(importPath) > 0 || !required)
// We need the source root determined at this point to check the setversions
if err := c.initAppFolder(); err != nil {
utils.Logger.Error("Error initing app folder", "error", err)
}
utils.Logger.Info("Returned import path", "path", importPath)
if required && c.Index != NEW {
if err := c.SetVersions(); err != nil {
utils.Logger.Panic("Failed to fetch revel versions", "error", err)
}
if err := c.FrameworkVersion.CompatibleFramework(c); err != nil {
utils.Logger.Fatal("Compatibility Error", "message", err,
"Revel framework version", c.FrameworkVersion.String(), "Revel tool version", c.CommandVersion.String())
}
utils.Logger.Info("Revel versions", "revel-tool", c.CommandVersion.String(), "Revel Framework", c.FrameworkVersion.String())
}
if !required {
return nil
}
if len(importPath) == 0 {
return fmt.Errorf("%w: %s", ErrUnableToImport, importPath)
}
return nil
}
func (c *CommandConfig) initAppFolder() (err error) {
utils.Logger.Info("initAppFolder", "vendored", c.Vendored, "build-gopath", build.Default.GOPATH, "gopath-env", os.Getenv("GOPATH"))
// check for go executable
c.GoCmd, err = exec.LookPath("go")
if err != nil {
utils.Logger.Fatal("Go executable not found in PATH.")
}
// First try to determine where the application is located - this should be the import value
appFolder := c.ImportPath
wd, _ := os.Getwd()
if len(appFolder) == 0 {
// We will assume the working directory is the appFolder
appFolder = wd
} else if strings.LastIndex(wd, appFolder) == len(wd)-len(appFolder) {
// Check for existence of an /app folder
if utils.Exists(filepath.Join(wd, "app")) {
appFolder = wd
} else {
appFolder = filepath.Join(wd, appFolder)
}
} else if !filepath.IsAbs(appFolder) {
appFolder = filepath.Join(wd, appFolder)
}
utils.Logger.Info("Determined app folder to be", "appfolder", appFolder, "working", wd, "importPath", c.ImportPath)
// Use app folder to read the go.mod if it exists and extract the package information
goModFile := filepath.Join(appFolder, "go.mod")
utils.Logger.Info("Checking gomod, extracting from file", "path", goModFile, "exists", utils.Exists(goModFile))
if utils.Exists(goModFile) {
c.Vendored = true
utils.Logger.Info("Found go mod, extracting from file", "path", goModFile)
file, err := ioutil.ReadFile(goModFile)
if err != nil {
return err
}
for _, line := range strings.Split(string(file), "\n") {
if strings.Index(line, "module ") == 0 {
c.ImportPath = strings.TrimSpace(strings.Split(line, "module")[1])
c.AppPath = appFolder
// c.SrcRoot = appFolder
utils.Logger.Info("Set application path and package based on go mod", "path", c.AppPath)
return nil
}
}
// c.SrcRoot = appFolder
c.AppPath = appFolder
} else if c.Index != NEW || (c.Index == NEW && c.New.NotVendored) {
workingDir, _ := os.Getwd()
goPathList := filepath.SplitList(c.GoPath)
bestpath := ""
for _, path := range goPathList {
if c.Index == NEW {
// If the GOPATH is part of the working dir this is the most likely target
if strings.HasPrefix(workingDir, path) {
bestpath = path
}
} else {
if utils.Exists(filepath.Join(path, "src", c.ImportPath)) {
bestpath = path
break
}
}
}
utils.Logger.Info("Source root", "cwd", workingDir, "gopath", c.GoPath, "c.ImportPath", c.ImportPath, "bestpath", bestpath)
if len(bestpath) > 0 {
c.AppPath = filepath.Join(bestpath, "src", c.ImportPath)
}
// Recalculate the appFolder because we are using a GOPATH
} else {
// This is new and not vendored, so the app path is the appFolder
c.AppPath = appFolder
}
utils.Logger.Info("Set application path", "path", c.AppPath, "vendored", c.Vendored, "importpath", c.ImportPath)
return nil
}
// Used to initialize the package resolver.
func (c *CommandConfig) InitPackageResolver() {
c.initGoPaths()
utils.Logger.Info("InitPackageResolver", "useVendor", c.Vendored, "path", c.AppPath)
// This should get called when needed
c.PackageResolver = func(pkgName string) error {
utils.Logger.Info("Request for package ", "package", pkgName, "use vendor", c.Vendored)
var getCmd *exec.Cmd
print("Downloading related packages ...")
if c.Vendored {
getCmd = exec.Command(c.GoCmd, "mod", "tidy", "-v")
} else {
utils.Logger.Info("No vendor folder detected, not using dependency manager to import package", "package", pkgName)
getCmd = exec.Command(c.GoCmd, "get", "-u", pkgName)
}
utils.CmdInit(getCmd, !c.Vendored, c.AppPath)
utils.Logger.Info("Go get command ", "exec", getCmd.Path, "dir", getCmd.Dir, "args", getCmd.Args, "env", getCmd.Env, "package", pkgName)
output, err := getCmd.CombinedOutput()
if err != nil {
utils.Logger.Error("Failed to import package", "error", err, "gopath", build.Default.GOPATH, "GO-ROOT", build.Default.GOROOT, "output", string(output))
}
println(" completed.")
return nil
}
}
// lookup and set Go related variables.
func (c *CommandConfig) initGoPaths() {
utils.Logger.Info("InitGoPaths", "vendored", c.Vendored)
// check for go executable
var err error
c.GoCmd, err = exec.LookPath("go")
if err != nil {
utils.Logger.Fatal("Go executable not found in PATH.")
}
if c.Vendored {
return
}
// lookup go path
c.GoPath = build.Default.GOPATH
if c.GoPath == "" {
utils.Logger.Fatal("Abort: GOPATH environment variable is not set. " +
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
}
// todo determine if the rest needs to happen
// revel/revel#1004 choose go path relative to current working directory
// What we want to do is to add the import to the end of the
// gopath, and discover which import exists - If none exist this is an error except in the case
// where we are dealing with new which is a special case where we will attempt to target the working directory first
/*
// If source root is empty and this isn't a version then skip it
if len(c.SrcRoot) == 0 {
if c.Index == NEW {
c.SrcRoot = c.New.ImportPath
} else {
if c.Index != VERSION {
utils.Logger.Fatal("Abort: could not create a Revel application outside of GOPATH.")
}
return
}
}
// set go src path
c.SrcRoot = filepath.Join(c.SrcRoot, "src")
c.AppPath = filepath.Join(c.SrcRoot, filepath.FromSlash(c.ImportPath))
utils.Logger.Info("Set application path", "path", c.AppPath)
*/
}
// Sets the versions on the command config.
func (c *CommandConfig) GetVerbose() (verbose bool) {
if len(c.Verbose) > 0 {
verbose = c.Verbose[0]
}
return
}
// Sets the versions on the command config.
func (c *CommandConfig) SetVersions() (err error) {
c.CommandVersion, _ = ParseVersion(cmd.Version)
pathMap, err := utils.FindSrcPaths(c.AppPath, []string{RevelImportPath}, c.PackageResolver)
if err == nil {
utils.Logger.Info("Fullpath to revel", "dir", pathMap[RevelImportPath])
fset := token.NewFileSet() // positions are relative to fset
versionData, err := ioutil.ReadFile(filepath.Join(pathMap[RevelImportPath], "version.go"))
if err != nil {
utils.Logger.Error("Failed to find Revel version:", "error", err, "path", pathMap[RevelImportPath])
}
// Parse src but stop after processing the imports.
f, err := parser.ParseFile(fset, "", versionData, parser.ParseComments)
if err != nil {
return utils.NewBuildError("Failed to parse Revel version error:", "error", err)
}
// Print the imports from the file's AST.
for _, s := range f.Decls {
genDecl, ok := s.(*ast.GenDecl)
if !ok {
continue
}
if genDecl.Tok != token.CONST {
continue
}
for _, a := range genDecl.Specs {
spec := a.(*ast.ValueSpec)
r := spec.Values[0].(*ast.BasicLit)
if spec.Names[0].Name == "Version" {
c.FrameworkVersion, err = ParseVersion(strings.ReplaceAll(r.Value, `"`, ``))
if err != nil {
utils.Logger.Errorf("Failed to parse version")
} else {
utils.Logger.Info("Parsed revel version", "version", c.FrameworkVersion.String())
}
}
}
}
}
return
}

View File

@@ -1,11 +1,11 @@
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 {
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 {
return s.ImportPath + "." + s.StructName
}
}

61
model/event.go Normal file
View File

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

24
model/event_test.go Normal file
View File

@@ -0,0 +1,24 @@
package model_test
import (
"testing"
"github.com/revel/revel"
"github.com/stretchr/testify/assert"
)
// Test that the event handler can be attached and it dispatches the event received.
func TestEventHandler(t *testing.T) {
counter := 0
newListener := func(typeOf revel.Event, value interface{}) (responseOf revel.EventResponse) {
if typeOf == revel.ENGINE_SHUTDOWN_REQUEST {
counter++
}
return
}
// Attach the same handler twice so we expect to see the response twice as well
revel.AddInitEventHandler(newListener)
revel.AddInitEventHandler(newListener)
revel.StopServer(1)
assert.Equal(t, counter, 2, "Expected event handler to have been called")
}

View File

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

View File

@@ -2,119 +2,166 @@
package model
import (
"github.com/revel/cmd/utils"
"github.com/revel/config"
"bufio"
"fmt"
"go/build"
"io"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
"unicode"
"github.com/revel/cmd/utils"
"github.com/revel/config"
"golang.org/x/tools/go/packages"
)
// Error is used for constant errors.
type Error string
// Error implements the error interface.
func (e Error) Error() string {
return string(e)
}
const (
// Event type when templates are going to be refreshed (receivers are registered template engines added to the template.engine conf option)
TEMPLATE_REFRESH_REQUESTED = iota
// Event type when templates are refreshed (receivers are registered template engines added to the template.engine conf option)
TEMPLATE_REFRESH_COMPLETED
// Event type before all module loads, events thrown to handlers added to AddInitEventHandler
// Event type before all module loads, events thrown to handlers added to AddInitEventHandler
REVEL_BEFORE_MODULES_LOADED
// Event type called when a new module is found
REVEL_BEFORE_MODULE_LOADED
// Event type called when after a new module is found
REVEL_AFTER_MODULE_LOADED
// Event type after all module loads, events thrown to handlers added to AddInitEventHandler
REVEL_AFTER_MODULES_LOADED
// Event type before server engine is initialized, receivers are active server engine and handlers added to AddInitEventHandler
ENGINE_BEFORE_INITIALIZED
// Event type before server engine is started, receivers are active server engine and handlers added to AddInitEventHandler
ENGINE_STARTED
// Event type after server engine is stopped, receivers are active server engine and handlers added to AddInitEventHandler
ENGINE_SHUTDOWN
// Called before routes are refreshed
ROUTE_REFRESH_REQUESTED
// Called after routes have been refreshed
ROUTE_REFRESH_COMPLETED
ErrNoApp Error = "no app found at path"
ErrNoConfig Error = "no config found at path"
ErrNotFound Error = "not found"
ErrMissingCert Error = "no http.sslcert provided"
ErrMissingKey Error = "no http.sslkey provided"
ErrNoFiles Error = "no files found in import path"
ErrNoPackages Error = "no packages found for import"
)
type (
// The container object for describing all Revels variables
// The container object for describing all Revels variables.
RevelContainer struct {
ImportPath string // The import path
SourcePath string // The full source path
RunMode string // The current run mode
RevelPath string // The path to the Revel source code
BasePath string // The base path to the application
AppPath string // The application path (BasePath + "/app"
ViewsPath string // The application views path
CodePaths []string // All the code paths
TemplatePaths []string // All the template paths
ConfPaths []string // All the configuration paths
Config *config.Context // The global config object
Packaged bool // True if packaged
DevMode bool // True if running in dev mode
HTTPPort int // The http port
HTTPAddr string // The http address
HTTPSsl bool // True if running https
HTTPSslCert string // The SSL certificate
HTTPSslKey string // The SSL key
AppName string // The application name
AppRoot string // The application root from the config `app.root`
CookiePrefix string // The cookie prefix
CookieDomain string // The cookie domain
CookieSecure bool // True if cookie is secure
SecretStr string // The secret string
MimeConfig *config.Context // The mime configuration
ModulePathMap map[string]string // The module path map
BuildPaths struct {
Revel string
}
Paths struct {
Import string
Source string
Base string
App string
Views string
Code []string
Template []string
Config []string
}
PackageInfo struct {
Config config.Context
Packaged bool
DevMode bool
Vendor bool
}
Application struct {
Name string
Root string
}
ImportPath string // The import path
SourcePath string // The full source path
RunMode string // The current run mode
RevelPath string // The path to the Revel source code
BasePath string // The base path to the application
AppPath string // The application path (BasePath + "/app")
ViewsPath string // The application views path
CodePaths []string // All the code paths
TemplatePaths []string // All the template paths
ConfPaths []string // All the configuration paths
Config *config.Context // The global config object
Packaged bool // True if packaged
DevMode bool // True if running in dev mode
HTTPPort int // The http port
HTTPAddr string // The http address
HTTPSsl bool // True if running https
HTTPSslCert string // The SSL certificate
HTTPSslKey string // The SSL key
AppName string // The application name
AppRoot string // The application root from the config `app.root`
CookiePrefix string // The cookie prefix
CookieDomain string // The cookie domain
CookieSecure bool // True if cookie is secure
SecretStr string // The secret string
MimeConfig *config.Context // The mime configuration
ModulePathMap map[string]*ModuleInfo // The module path map
}
ModuleInfo struct {
ImportPath string
Path string
}
RevelCallback interface {
FireEvent(key int, value interface{}) (response int)
WrappedRevelCallback struct {
FireEventFunction func(key Event, value interface{}) (response EventResponse)
ImportFunction func(pkgName string) error
}
doNothingRevelCallback struct {
Mod struct {
ImportPath string
SourcePath string
Version string
SourceVersion string
Dir string // full path, $GOPATH/pkg/mod/
Pkgs []string // sub-pkg import paths
VendorList []string // files to vendor
}
)
// Simple callback to pass to the RevelCallback that does nothing
var DoNothingRevelCallback = RevelCallback(&doNothingRevelCallback{})
// Simple Wrapped RevelCallback.
func NewWrappedRevelCallback(fe func(key Event, value interface{}) (response EventResponse), ie func(pkgName string) error) RevelCallback {
return &WrappedRevelCallback{fe, ie}
}
func (_ *doNothingRevelCallback) FireEvent(key int, value interface{}) (response int) {
// Function to implement the FireEvent.
func (w *WrappedRevelCallback) FireEvent(key Event, value interface{}) (response EventResponse) {
if w.FireEventFunction != nil {
response = w.FireEventFunction(key, value)
}
return
}
// RevelImportPath Revel framework import path
var RevelImportPath = "github.com/revel/revel"
func (w *WrappedRevelCallback) PackageResolver(pkgName string) error {
return w.ImportFunction(pkgName)
}
// RevelImportPath Revel framework import path.
var (
RevelImportPath = "github.com/revel/revel"
RevelModulesImportPath = "github.com/revel/modules"
)
// This function returns a container object describing the revel application
// eventually this type of function will replace the global variables.
func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp *RevelContainer) {
rp = &RevelContainer{ModulePathMap: map[string]string{}}
func NewRevelPaths(mode, importPath, appSrcPath string, callback RevelCallback) (rp *RevelContainer, err error) {
rp = &RevelContainer{ModulePathMap: map[string]*ModuleInfo{}}
// Ignore trailing slashes.
rp.ImportPath = strings.TrimRight(importPath, "/")
rp.SourcePath = srcPath
rp.SourcePath = appSrcPath
rp.RunMode = mode
// If the SourcePath is not specified, find it using build.Import.
var revelSourcePath string // may be different from the app source path
if rp.SourcePath == "" {
revelSourcePath, rp.SourcePath = findSrcPaths(importPath)
} else {
// If the SourcePath was specified, assume both Revel and the app are within it.
rp.SourcePath = filepath.Clean(rp.SourcePath)
revelSourcePath = rp.SourcePath
// We always need to determine the paths for files
pathMap, err := utils.FindSrcPaths(appSrcPath, []string{importPath + "/app", RevelImportPath}, callback.PackageResolver)
if err != nil {
return
}
rp.AppPath, rp.RevelPath = pathMap[importPath], pathMap[RevelImportPath]
// Setup paths for application
rp.BasePath = rp.SourcePath
rp.PackageInfo.Vendor = utils.Exists(filepath.Join(rp.BasePath, "go.mod"))
rp.AppPath = filepath.Join(rp.BasePath, "app")
// Sanity check , ensure app and conf paths exist
if !utils.DirExists(rp.AppPath) {
return rp, fmt.Errorf("%w: %s", ErrNoApp, rp.AppPath)
}
if !utils.DirExists(filepath.Join(rp.BasePath, "conf")) {
return rp, fmt.Errorf("%w: %s", ErrNoConfig, filepath.Join(rp.BasePath, "conf"))
}
// Setup paths for application
rp.RevelPath = filepath.Join(revelSourcePath, filepath.FromSlash(RevelImportPath))
rp.BasePath = filepath.Join(rp.SourcePath, filepath.FromSlash(importPath))
rp.AppPath = filepath.Join(rp.BasePath, "app")
rp.ViewsPath = filepath.Join(rp.AppPath, "views")
rp.CodePaths = []string{rp.AppPath}
rp.TemplatePaths = []string{}
@@ -133,11 +180,9 @@ func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp
},
rp.ConfPaths...)
var err error
rp.Config, err = config.LoadContext("app.conf", rp.ConfPaths)
if err != nil {
utils.Logger.Fatal("Unable to load configuartion file ","error", err)
os.Exit(1)
return rp, fmt.Errorf("unable to load configuration file %w", err)
}
// Ensure that the selected runmode appears in app.conf.
@@ -146,7 +191,7 @@ func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp
mode = config.DefaultSection
}
if !rp.Config.HasSection(mode) {
utils.Logger.Fatal("app.conf: No mode found:","run-mode", mode)
return rp, fmt.Errorf("app.conf: %w %s %s", ErrNotFound, "run-mode", mode)
}
rp.Config.SetSection(mode)
@@ -159,13 +204,14 @@ func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp
rp.HTTPSslKey = rp.Config.StringDefault("http.sslkey", "")
if rp.HTTPSsl {
if rp.HTTPSslCert == "" {
utils.Logger.Fatal("No http.sslcert provided.")
return rp, ErrMissingCert
}
if rp.HTTPSslKey == "" {
utils.Logger.Fatal("No http.sslkey provided.")
return rp, ErrMissingKey
}
}
//
rp.AppName = rp.Config.StringDefault("app.name", "(not set)")
rp.AppRoot = rp.Config.StringDefault("app.root", "")
rp.CookiePrefix = rp.Config.StringDefault("cookie.prefix", "REVEL")
@@ -173,36 +219,62 @@ func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp
rp.CookieSecure = rp.Config.BoolDefault("cookie.secure", rp.HTTPSsl)
rp.SecretStr = rp.Config.StringDefault("app.secret", "")
callback.FireEvent(REVEL_BEFORE_MODULES_LOADED, nil)
rp.loadModules(callback)
utils.Logger.Info("Loading modules")
if err := rp.loadModules(callback); err != nil {
return rp, err
}
callback.FireEvent(REVEL_AFTER_MODULES_LOADED, nil)
return
}
// LoadMimeConfig load mime-types.conf on init.
func (rp *RevelContainer) LoadMimeConfig() {
var err error
func (rp *RevelContainer) LoadMimeConfig() (err error) {
rp.MimeConfig, err = config.LoadContext("mime-types.conf", rp.ConfPaths)
if err != nil {
utils.Logger.Fatal("Failed to load mime type config:", "error", err)
return fmt.Errorf("failed to load mime type config: %s %w", "error", err)
}
return
}
// Loads modules based on the configuration setup.
// This will fire the REVEL_BEFORE_MODULE_LOADED, REVEL_AFTER_MODULE_LOADED
// for each module loaded. The callback will receive the RevelContainer, name, moduleImportPath and modulePath
// It will automatically add in the code paths for the module to the
// container object
func (rp *RevelContainer) loadModules(callback RevelCallback) {
// container object.
func (rp *RevelContainer) loadModules(callback RevelCallback) (err error) {
keys := []string{}
for _, key := range rp.Config.Options("module.") {
keys = append(keys, key)
}
keys = append(keys, rp.Config.Options("module.")...)
// Reorder module order by key name, a poor mans sort but at least it is consistent
sort.Strings(keys)
modtxtPath := filepath.Join(rp.SourcePath, "vendor", "modules.txt")
if utils.Exists(modtxtPath) {
// Parse out require sections of module.txt
modules := rp.vendorInitilizeLocal(modtxtPath, keys)
for _, mod := range modules {
for _, vendorFile := range mod.VendorList {
x := strings.Index(vendorFile, mod.Dir)
if x < 0 {
utils.Logger.Crit("Error! vendor file doesn't belong to mod, strange.", "vendorFile", "mod.Dir", mod.Dir)
}
localPath := fmt.Sprintf("%s%s", mod.ImportPath, vendorFile[len(mod.Dir):])
localFile := filepath.Join(rp.SourcePath, "vendor", localPath)
utils.Logger.Infof("vendoring %s\n", localPath)
os.MkdirAll(filepath.Dir(localFile), os.ModePerm)
if _, err := copyFile(vendorFile, localFile); err != nil {
fmt.Printf("Error! %s - unable to copy file %s\n", err.Error(), vendorFile)
os.Exit(1)
}
}
}
}
for _, key := range keys {
moduleImportPath := rp.Config.StringDefault(key, "")
if moduleImportPath == "" {
@@ -210,8 +282,17 @@ func (rp *RevelContainer) loadModules(callback RevelCallback) {
}
modulePath, err := rp.ResolveImportPath(moduleImportPath)
utils.Logger.Info("Resolving import path ", "modulePath", modulePath, "module_import_path", moduleImportPath, "error", err)
if err != nil {
utils.Logger.Error("Failed to load module. Import of path failed", "modulePath", moduleImportPath, "error", err)
if err := callback.PackageResolver(moduleImportPath); err != nil {
return fmt.Errorf("failed to resolve package %w", err)
}
modulePath, err = rp.ResolveImportPath(moduleImportPath)
if err != nil {
return fmt.Errorf("failed to load module. Import of path failed %s:%s %s:%w ", "modulePath", moduleImportPath, "error", err)
}
}
// Drop anything between module.???.<name of module>
name := key[len("module."):]
@@ -222,13 +303,97 @@ func (rp *RevelContainer) loadModules(callback RevelCallback) {
rp.addModulePaths(name, moduleImportPath, modulePath)
callback.FireEvent(REVEL_AFTER_MODULE_LOADED, []interface{}{rp, name, moduleImportPath, modulePath})
}
return
}
// Adds a module paths to the container object
// Adds a module paths to the container object.
func (rp *RevelContainer) vendorInitilizeLocal(modtxtPath string, revel_modules_keys []string) []*Mod {
revel_modules := []string{"github.com/revel/revel"}
for _, key := range revel_modules_keys {
moduleImportPath := rp.Config.StringDefault(key, "")
if moduleImportPath == "" {
continue
}
revel_modules = append(revel_modules, moduleImportPath)
}
f, _ := os.Open(modtxtPath)
defer f.Close()
scanner := bufio.NewScanner(f)
scanner.Split(bufio.ScanLines)
var (
mod *Mod
err error
)
modules := []*Mod{}
for scanner.Scan() {
line := scanner.Text()
// Look for # character
if line[0] == 35 {
s := strings.Split(line, " ")
if (len(s) != 6 && len(s) != 3) || s[1] == "explicit" {
continue
}
mod = &Mod{
ImportPath: s[1],
Version: s[2],
}
if s[2] == "=>" {
// issue https://github.com/golang/go/issues/33848 added these,
// see comments. I think we can get away with ignoring them.
continue
}
// Handle "replace" in module file if any
if len(s) > 3 && s[3] == "=>" {
mod.SourcePath = s[4]
// Handle replaces with a relative target. For example:
// "replace github.com/status-im/status-go/protocol => ./protocol"
if strings.HasPrefix(s[4], ".") || strings.HasPrefix(s[4], "/") {
mod.Dir, err = filepath.Abs(s[4])
if err != nil {
fmt.Printf("invalid relative path: %v", err)
os.Exit(1)
}
} else {
mod.SourceVersion = s[5]
mod.Dir = pkgModPath(mod.SourcePath, mod.SourceVersion)
}
} else {
mod.Dir = pkgModPath(mod.ImportPath, mod.Version)
}
if _, err := os.Stat(mod.Dir); os.IsNotExist(err) {
utils.Logger.Critf("Error! %q module path does not exist, check $GOPATH/pkg/mod\n", mod.Dir)
}
// Determine if we need to examine this mod, based on the list of modules being imported
for _, importPath := range revel_modules {
if strings.HasPrefix(importPath, mod.ImportPath) {
updateModVendorList(mod, importPath)
}
}
// Build list of files to module path source to project vendor folder
modules = append(modules, mod)
continue
}
mod.Pkgs = append(mod.Pkgs, line)
}
return modules
}
// Adds a module paths to the container object.
func (rp *RevelContainer) addModulePaths(name, importPath, modulePath string) {
utils.Logger.Info("Adding module path", "name", name, "import path", importPath, "system path", modulePath)
if codePath := filepath.Join(modulePath, "app"); utils.DirExists(codePath) {
rp.CodePaths = append(rp.CodePaths, codePath)
rp.ModulePathMap[name] = modulePath
rp.ModulePathMap[name] = &ModuleInfo{importPath, modulePath}
if viewsPath := filepath.Join(modulePath, "app", "views"); utils.DirExists(viewsPath) {
rp.TemplatePaths = append(rp.TemplatePaths, viewsPath)
}
@@ -251,44 +416,95 @@ func (rp *RevelContainer) ResolveImportPath(importPath string) (string, error) {
if rp.Packaged {
return filepath.Join(rp.SourcePath, importPath), nil
}
modPkg, err := build.Import(importPath, rp.RevelPath, build.FindOnly)
config := &packages.Config{
Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports |
packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo,
Dir: rp.AppPath,
}
config.Env = utils.ReducedEnv(false)
pkgs, err := packages.Load(config, importPath)
if len(pkgs) == 0 {
return "", fmt.Errorf("%w %s using app path %s", ErrNoPackages, importPath, rp.AppPath)
}
// modPkg, err := build.Import(importPath, rp.AppPath, build.FindOnly)
if err != nil {
return "", err
}
return modPkg.Dir, nil
if len(pkgs[0].GoFiles) > 0 {
return filepath.Dir(pkgs[0].GoFiles[0]), nil
}
return pkgs[0].PkgPath, fmt.Errorf("%w: %s", ErrNoFiles, importPath)
}
// Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory
func findSrcPaths(importPath string) (revelSourcePath, appSourcePath string) {
var (
gopaths = filepath.SplitList(build.Default.GOPATH)
goroot = build.Default.GOROOT
)
if len(gopaths) == 0 {
utils.Logger.Fatalf("GOPATH environment variable is not set. " +
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
func normString(str string) (normStr string) {
for _, char := range str {
if unicode.IsUpper(char) {
normStr += "!" + string(unicode.ToLower(char))
} else {
normStr += string(char)
}
}
if utils.ContainsString(gopaths, goroot) {
utils.Logger.Fatalf("GOPATH (%s) must not include your GOROOT (%s). "+
"Please refer to http://golang.org/doc/code.html to configure your Go environment.",
gopaths, goroot)
}
appPkg, err := build.Import(importPath, "", build.FindOnly)
if err != nil {
utils.Logger.Fatal("Failed to import "+importPath+" with error:", "error", err)
}
revelPkg, err := build.Import(RevelImportPath, appPkg.Dir, build.FindOnly)
if err != nil {
utils.Logger.Fatal("Failed to find Revel with error:", "error", err)
}
revelSourcePath, appSourcePath = revelPkg.Dir[:len(revelPkg.Dir)-len(RevelImportPath)], appPkg.SrcRoot
return
}
func pkgModPath(importPath, version string) string {
goPath := build.Default.GOPATH
if goPath == "" {
if goPath = os.Getenv("GOPATH"); goPath == "" {
// the default GOPATH for go v1.11
goPath = filepath.Join(os.Getenv("HOME"), "go")
}
}
normPath := normString(importPath)
normVersion := normString(version)
return filepath.Join(goPath, "pkg", "mod", fmt.Sprintf("%s@%s", normPath, normVersion))
}
func copyFile(src, dst string) (int64, error) {
srcStat, err := os.Stat(src)
if err != nil {
return 0, err
}
if !srcStat.Mode().IsRegular() {
return 0, fmt.Errorf("%s is not a regular file", src)
}
srcFile, err := os.Open(src)
if err != nil {
return 0, err
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return 0, err
}
defer dstFile.Close()
return io.Copy(dstFile, srcFile)
}
func updateModVendorList(mod *Mod, importPath string) {
vendorList := []string{}
pathPrefix := filepath.Join(mod.Dir, importPath[len(mod.ImportPath):])
filepath.WalkDir(pathPrefix, func(path string, d fs.DirEntry, err error) (e error) {
if d.IsDir() {
return
}
if err != nil {
utils.Logger.Crit("Failed to walk vendor dir")
}
utils.Logger.Info("Adding to file in vendor list", "path", path)
vendorList = append(vendorList, path)
return
})
utils.Logger.Info("For module", "module", mod.ImportPath, "files", len(vendorList))
mod.VendorList = append(mod.VendorList, vendorList...)
}

View File

@@ -3,10 +3,11 @@ package model
// SourceInfo is the top-level struct containing all extracted information
// about the app source code, used to generate main.go.
import (
"github.com/revel/cmd/utils"
"path/filepath"
"strings"
"unicode"
"github.com/revel/cmd/utils"
)
type SourceInfo struct {
@@ -29,11 +30,13 @@ type SourceInfo struct {
controllerSpecs []*TypeInfo
// testSuites list the types that constitute the set of application tests.
testSuites []*TypeInfo
// packageMap a map of import to system directory (if available)
PackageMap map[string]string
}
// TypesThatEmbed returns all types that (directly or indirectly) embed the
// target type, which must be a fully qualified type name,
// e.g. "github.com/revel/revel.Controller"
// e.g. "github.com/revel/revel.Controller".
func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered []*TypeInfo) {
// Do a search in the "embedded type graph", starting with the target type.
var (
@@ -55,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.
for _, embeddedType := range spec.EmbeddedTypes {
// If so, add this type's simple name to the nodeQueue, and its spec to
// the filtered list.
if typeSimpleName == embeddedType.String() {
@@ -75,6 +77,7 @@ func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered
"type", filteredItem.StructName,
"package", filteredItem.ImportPath)
filtered = append(filtered[:i], filtered[i+1:]...)
//nolint:ineffassign // huh?
exit = false
break
}
@@ -107,8 +110,9 @@ func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered
}
// ControllerSpecs returns the all the controllers that embeds
// `revel.Controller`
// `revel.Controller`.
func (s *SourceInfo) ControllerSpecs() []*TypeInfo {
utils.Logger.Info("Scanning controller specifications for types ", "typePath", RevelImportPath+".Controller", "speclen", len(s.controllerSpecs))
if s.controllerSpecs == nil {
s.controllerSpecs = s.TypesThatEmbed(RevelImportPath+".Controller", "controllers")
}
@@ -116,10 +120,22 @@ func (s *SourceInfo) ControllerSpecs() []*TypeInfo {
}
// TestSuites returns the all the Application tests that embeds
// `testing.TestSuite`
// `testing.TestSuite`.
func (s *SourceInfo) TestSuites() []*TypeInfo {
if s.testSuites == nil {
s.testSuites = s.TypesThatEmbed(RevelImportPath+"/testing.TestSuite", "testsuite")
}
return s.testSuites
}
func (s *SourceInfo) Merge(srcInfo2 *SourceInfo) {
s.StructSpecs = append(s.StructSpecs, srcInfo2.StructSpecs...)
s.InitImportPaths = append(s.InitImportPaths, srcInfo2.InitImportPaths...)
for k, v := range srcInfo2.ValidationKeys {
if _, ok := s.ValidationKeys[k]; ok {
utils.Logger.Warn("Warn: Key conflict when scanning validation calls:", "key", k)
continue
}
s.ValidationKeys[k] = v
}
}

View File

@@ -13,14 +13,14 @@ type TypeExpr struct {
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 {
return TypeExpr{expr, pkgName, pkgIndex, valid}
}
// NewTypeExpr returns the syntactic expression for referencing this type in Go.
func NewTypeExprFromAst(pkgName string, expr ast.Expr) TypeExpr {
error := ""
err := ""
switch t := expr.(type) {
case *ast.Ident:
if IsBuiltinType(t.Name) {
@@ -41,15 +41,14 @@ func NewTypeExprFromAst(pkgName string, expr ast.Expr) TypeExpr {
e := NewTypeExprFromAst(pkgName, t.Value)
return NewTypeExprFromData("map["+identKey.Name+"]"+e.Expr, e.PkgName, e.pkgIndex+len("map["+identKey.Name+"]"), e.Valid)
}
error = fmt.Sprintf("Failed to generate name for Map field :%v. Make sure the field name is valid.", t.Key)
err = fmt.Sprintf("Failed to generate name for Map field :%v. Make sure the field name is valid.", t.Key)
case *ast.Ellipsis:
e := NewTypeExprFromAst(pkgName, t.Elt)
return NewTypeExprFromData("[]"+e.Expr, e.PkgName, e.pkgIndex+2, e.Valid)
default:
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.
@@ -62,7 +61,7 @@ func (e TypeExpr) TypeName(pkgOverride string) string {
return e.Expr[:e.pkgIndex] + pkgName + "." + e.Expr[e.pkgIndex:]
}
var builtInTypes = map[string]struct{}{
var builtInTypes = map[string]struct{}{ //nolint:gochecknoglobals
"bool": {},
"byte": {},
"complex128": {},
@@ -85,13 +84,13 @@ var builtInTypes = map[string]struct{}{
"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 {
_, ok := builtInTypes[name]
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 {
for _, str := range strs {
if len(str) > 0 {

View File

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

126
model/version.go Normal file
View File

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

35
model/version_test.go Normal file
View File

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

224
parser/appends.go Normal file
View File

@@ -0,0 +1,224 @@
package parser
import (
"go/ast"
"go/token"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
)
// If this Decl is a struct type definition, it is summarized and added to specs.
// Else, specs is returned unchanged.
func appendStruct(fileName string, specs []*model.TypeInfo, pkgImportPath string, pkg *ast.Package, decl ast.Decl, imports map[string]string, fset *token.FileSet) []*model.TypeInfo {
// Filter out non-Struct type declarations.
spec, found := getStructTypeDecl(decl, fset)
if !found {
return specs
}
structType := spec.Type.(*ast.StructType)
// At this point we know it's a type declaration for a struct.
// Fill in the rest of the info by diving into the fields.
// Add it provisionally to the Controller list -- it's later filtered using field info.
controllerSpec := &model.TypeInfo{
StructName: spec.Name.Name,
ImportPath: pkgImportPath,
PackageName: pkg.Name,
}
for _, field := range structType.Fields.List {
// If field.Names is set, it's not an embedded type.
if field.Names != nil {
continue
}
// A direct "sub-type" has an ast.Field as either:
// Ident { "AppController" }
// SelectorExpr { "rev", "Controller" }
// Additionally, that can be wrapped by StarExprs.
fieldType := field.Type
pkgName, typeName := func() (string, string) {
// Drill through any StarExprs.
for {
if starExpr, ok := fieldType.(*ast.StarExpr); ok {
fieldType = starExpr.X
continue
}
break
}
// If the embedded type is in the same package, it's an Ident.
if ident, ok := fieldType.(*ast.Ident); ok {
return "", ident.Name
}
if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
return pkgIdent.Name, selectorExpr.Sel.Name
}
}
return "", ""
}()
// If a typename wasn't found, skip it.
if typeName == "" {
continue
}
// Find the import path for this type.
// If it was referenced without a package name, use the current package import path.
// Else, look up the package's import path by name.
var importPath string
if pkgName == "" {
importPath = pkgImportPath
} else {
var ok bool
if importPath, ok = imports[pkgName]; !ok {
utils.Logger.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName)
continue
}
}
controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{
ImportPath: importPath,
StructName: typeName,
})
}
return append(specs, controllerSpec)
}
// If decl is a Method declaration, it is summarized and added to the array
// underneath its receiver type.
// e.g. "Login" => {MethodSpec, MethodSpec, ..}.
func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPath, pkgName string, imports map[string]string) {
// Func declaration?
funcDecl, ok := decl.(*ast.FuncDecl)
if !ok {
return
}
// Have a receiver?
if funcDecl.Recv == nil {
return
}
// Is it public?
if !funcDecl.Name.IsExported() {
return
}
// Does it return a Result?
if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 {
return
}
selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
if !ok {
return
}
if selExpr.Sel.Name != "Result" {
return
}
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != model.RevelImportPath {
return
}
method := &model.MethodSpec{
Name: funcDecl.Name.Name,
}
// Add a description of the arguments to the method.
for _, field := range funcDecl.Type.Params.List {
for _, name := range field.Names {
var importPath string
typeExpr := model.NewTypeExprFromAst(pkgName, field.Type)
if !typeExpr.Valid {
utils.Logger.Warnf("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, getFuncName(funcDecl))
return // We didn't understand one of the args. Ignore this action.
}
// Local object
if typeExpr.PkgName == pkgName {
importPath = pkgImportPath
} else if typeExpr.PkgName != "" {
var ok bool
if importPath, ok = imports[typeExpr.PkgName]; !ok {
utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
}
}
method.Args = append(method.Args, &model.MethodArg{
Name: name.Name,
TypeExpr: typeExpr,
ImportPath: importPath,
})
}
}
// Add a description of the calls to Render from the method.
// Inspect every node (e.g. always return true).
method.RenderCalls = []*model.MethodCall{}
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
// Is it a function call?
callExpr, ok := node.(*ast.CallExpr)
if !ok {
return true
}
// Is it calling (*Controller).Render?
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
// The type of the receiver is not easily available, so just store every
// call to any method called Render.
if selExpr.Sel.Name != "Render" {
return true
}
// Add this call's args to the renderArgs.
pos := fset.Position(callExpr.Lparen)
methodCall := &model.MethodCall{
Line: pos.Line,
Names: []string{},
}
for _, arg := range callExpr.Args {
argIdent, ok := arg.(*ast.Ident)
if !ok {
continue
}
methodCall.Names = append(methodCall.Names, argIdent.Name)
}
method.RenderCalls = append(method.RenderCalls, methodCall)
return true
})
var recvTypeName string
recvType := funcDecl.Recv.List[0].Type
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
recvTypeName = recvStarType.X.(*ast.Ident).Name
} else {
recvTypeName = recvType.(*ast.Ident).Name
}
mm[recvTypeName] = append(mm[recvTypeName], method)
}
// Combine the 2 source info models into one.
func appendSourceInfo(srcInfo1, srcInfo2 *model.SourceInfo) *model.SourceInfo {
if srcInfo1 == nil {
return srcInfo2
}
srcInfo1.StructSpecs = append(srcInfo1.StructSpecs, srcInfo2.StructSpecs...)
srcInfo1.InitImportPaths = append(srcInfo1.InitImportPaths, srcInfo2.InitImportPaths...)
for k, v := range srcInfo2.ValidationKeys {
if _, ok := srcInfo1.ValidationKeys[k]; ok {
utils.Logger.Warn("Warn: Key conflict when scanning validation calls:", "key", k)
continue
}
srcInfo1.ValidationKeys[k] = v
}
return srcInfo1
}

87
parser/imports.go Normal file
View File

@@ -0,0 +1,87 @@
package parser
import (
"go/ast"
"go/build"
"go/token"
"path/filepath"
"strings"
"github.com/revel/cmd/utils"
)
// Add imports to the map from the source dir.
func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
genDecl, ok := decl.(*ast.GenDecl)
if !ok {
return
}
if genDecl.Tok != token.IMPORT {
return
}
for _, spec := range genDecl.Specs {
importSpec := spec.(*ast.ImportSpec)
var pkgAlias string
if importSpec.Name != nil {
pkgAlias = importSpec.Name.Name
if pkgAlias == "_" {
continue
}
}
quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\""
if quotedPath == `"C"` {
continue
}
fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes
// If the package was not aliased (common case), we have to import it
// to see what the package name is.
// TODO: Can improve performance here a lot:
// 1. Do not import everything over and over again. Keep a cache.
// 2. Exempt the standard library; their directories always match the package name.
// 3. Can use build.FindOnly and then use parser.ParseDir with mode PackageClauseOnly
if pkgAlias == "" {
utils.Logger.Debug("Reading from build", "path", fullPath, "srcPath", srcDir, "gopath", build.Default.GOPATH)
pkg, err := build.Import(fullPath, srcDir, 0)
if err != nil {
// We expect this to happen for apps using reverse routing (since we
// have not yet generated the routes). Don't log that.
if !strings.HasSuffix(fullPath, "/app/routes") {
utils.Logger.Warn("Could not find import:", "path", fullPath, "srcPath", srcDir, "error", err)
}
continue
} else {
utils.Logger.Debug("Found package in dir", "dir", pkg.Dir, "name", pkg.ImportPath)
}
pkgAlias = pkg.Name
}
imports[pkgAlias] = fullPath
}
}
// Returns a valid import string from the path
// using the build.Defaul.GOPATH to determine the root.
func importPathFromPath(root, basePath string) string {
vendorTest := filepath.Join(basePath, "vendor")
if len(root) > len(vendorTest) && root[:len(vendorTest)] == vendorTest {
return filepath.ToSlash(root[len(vendorTest)+1:])
}
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
srcPath := filepath.Join(gopath, "src")
if strings.HasPrefix(root, srcPath) {
return filepath.ToSlash(root[len(srcPath)+1:])
}
}
srcPath := filepath.Join(build.Default.GOROOT, "src", "pkg")
if strings.HasPrefix(root, srcPath) {
utils.Logger.Warn("Code path should be in GOPATH, but is in GOROOT:", "path", root)
return filepath.ToSlash(root[len(srcPath)+1:])
}
utils.Logger.Error("Unexpected! Code path is not in GOPATH:", "path", root)
return ""
}

View File

@@ -8,8 +8,8 @@ package parser
// It catalogs the controllers, their methods, and their arguments.
import (
"errors"
"go/ast"
"go/build"
"go/parser"
"go/scanner"
"go/token"
@@ -21,6 +21,13 @@ import (
"github.com/revel/cmd/utils"
)
// A container used to support the reflection package.
type processContainer struct {
root, rootImportPath string // The paths
paths *model.RevelContainer // The Revel paths
srcInfo *model.SourceInfo // The source information container
}
// Maps a controller simple name (e.g. "Login") to the methods for which it is a
// receiver.
type methodMap map[string][]*model.MethodSpec
@@ -28,130 +35,120 @@ type methodMap map[string][]*model.MethodSpec
// ProcessSource parses the app controllers directory and
// returns a list of the controller types found.
// Otherwise CompileError if the parsing fails.
func ProcessSource(paths *model.RevelContainer) (*model.SourceInfo, *utils.Error) {
var (
srcInfo *model.SourceInfo
compileError *utils.Error
)
func ProcessSource(paths *model.RevelContainer) (_ *model.SourceInfo, compileError error) {
pc := &processContainer{paths: paths}
for _, root := range paths.CodePaths {
rootImportPath := importPathFromPath(root)
rootImportPath := importPathFromPath(root, paths.BasePath)
if rootImportPath == "" {
utils.Logger.Info("Skipping empty code path", "path", root)
continue
}
pc.root, pc.rootImportPath = root, rootImportPath
// Start walking the directory tree.
_ = utils.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
utils.Logger.Error("Error scanning app source:", "error", err)
return nil
}
if !info.IsDir() || info.Name() == "tmp" {
return nil
}
// Get the import path of the package.
pkgImportPath := rootImportPath
if root != path {
pkgImportPath = rootImportPath + "/" + filepath.ToSlash(path[len(root)+1:])
}
// Parse files within the path.
var pkgs map[string]*ast.Package
fset := token.NewFileSet()
pkgs, err = parser.ParseDir(fset, path, func(f os.FileInfo) bool {
return !f.IsDir() && !strings.HasPrefix(f.Name(), ".") && strings.HasSuffix(f.Name(), ".go")
}, 0)
if err != nil {
if errList, ok := err.(scanner.ErrorList); ok {
var pos = errList[0].Pos
compileError = &utils.Error{
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 := paths.Config.StringDefault("error.link", "")
if errorLink != "" {
compileError.SetLink(errorLink)
}
return compileError
}
// This is exception, err already checked above. Here just a print
ast.Print(nil, err)
utils.Logger.Fatal("Failed to parse dir", "error", err)
}
// Skip "main" packages.
delete(pkgs, "main")
// Ignore packages that end with _test
// These cannot be included in source code that is not generated specifically as a test
for i := range pkgs {
if len(i) > 6 {
if string(i[len(i)-5:]) == "_test" {
delete(pkgs, i)
}
}
}
// If there is no code in this directory, skip it.
if len(pkgs) == 0 {
return nil
}
// There should be only one package in this directory.
if len(pkgs) > 1 {
for i := range pkgs {
println("Found package ", i)
}
utils.Logger.Fatal("Most unexpected! Multiple packages in a single directory:", "packages", pkgs)
}
var pkg *ast.Package
for _, v := range pkgs {
pkg = v
}
if pkg != nil {
srcInfo = appendSourceInfo(srcInfo, processPackage(fset, pkgImportPath, path, pkg))
} else {
utils.Logger.Info("Ignoring package, because it contained no packages", "path", path)
}
return nil
})
}
return srcInfo, compileError
}
func appendSourceInfo(srcInfo1, srcInfo2 *model.SourceInfo) *model.SourceInfo {
if srcInfo1 == nil {
return srcInfo2
}
srcInfo1.StructSpecs = append(srcInfo1.StructSpecs, srcInfo2.StructSpecs...)
srcInfo1.InitImportPaths = append(srcInfo1.InitImportPaths, srcInfo2.InitImportPaths...)
for k, v := range srcInfo2.ValidationKeys {
if _, ok := srcInfo1.ValidationKeys[k]; ok {
utils.Logger.Warn("Warn: Key conflict when scanning validation calls:", "key", k)
continue
compileError = utils.Walk(root, pc.processPath)
if compileError != nil {
return
}
srcInfo1.ValidationKeys[k] = v
}
return srcInfo1
return pc.srcInfo, compileError
}
// Called during the "walk process".
func (pc *processContainer) processPath(path string, info os.FileInfo, err error) error {
if err != nil {
utils.Logger.Error("Error scanning app source:", "error", err)
return nil
}
if !info.IsDir() || info.Name() == "tmp" {
return nil
}
// Get the import path of the package.
pkgImportPath := pc.rootImportPath
if pc.root != path {
pkgImportPath = pc.rootImportPath + "/" + filepath.ToSlash(path[len(pc.root)+1:])
}
// Parse files within the path.
var pkgs map[string]*ast.Package
fset := token.NewFileSet()
pkgs, err = parser.ParseDir(
fset,
path,
func(f os.FileInfo) bool {
return !f.IsDir() && !strings.HasPrefix(f.Name(), ".") && strings.HasSuffix(f.Name(), ".go")
},
0)
if err != nil {
var errList scanner.ErrorList
if errors.As(err, &errList) {
pos := errList[0].Pos
newError := &utils.SourceError{
SourceType: ".go source",
Title: "Go Compilation Error",
Path: pos.Filename,
Description: errList[0].Msg,
Line: pos.Line,
Column: pos.Column,
SourceLines: utils.MustReadLines(pos.Filename),
}
errorLink := pc.paths.Config.StringDefault("error.link", "")
if errorLink != "" {
newError.SetLink(errorLink)
}
return newError
}
// This is exception, err already checked above. Here just a print
ast.Print(nil, err)
utils.Logger.Fatal("Failed to parse dir", "error", err)
}
// Skip "main" packages.
delete(pkgs, "main")
// Ignore packages that end with _test
// These cannot be included in source code that is not generated specifically as a test
for i := range pkgs {
if len(i) > 6 {
if i[len(i)-5:] == "_test" {
delete(pkgs, i)
}
}
}
// If there is no code in this directory, skip it.
if len(pkgs) == 0 {
return nil
}
// There should be only one package in this directory.
if len(pkgs) > 1 {
for i := range pkgs {
println("Found package ", i)
}
utils.Logger.Fatal("Most unexpected! Multiple packages in a single directory:", "packages", pkgs)
}
var pkg *ast.Package
for _, v := range pkgs {
pkg = v
}
if pkg != nil {
pc.srcInfo = appendSourceInfo(pc.srcInfo, processPackage(fset, pkgImportPath, path, pkg))
} else {
utils.Logger.Info("Ignoring package, because it contained no packages", "path", path)
}
return nil
}
// Process a single package within a file.
func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast.Package) *model.SourceInfo {
var (
structSpecs []*model.TypeInfo
@@ -228,355 +225,6 @@ func getFuncName(funcDecl *ast.FuncDecl) string {
return prefix + funcDecl.Name.Name
}
func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
genDecl, ok := decl.(*ast.GenDecl)
if !ok {
return
}
if genDecl.Tok != token.IMPORT {
return
}
for _, spec := range genDecl.Specs {
importSpec := spec.(*ast.ImportSpec)
var pkgAlias string
if importSpec.Name != nil {
pkgAlias = importSpec.Name.Name
if pkgAlias == "_" {
continue
}
}
quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\""
fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes
// If the package was not aliased (common case), we have to import it
// to see what the package name is.
// TODO: Can improve performance here a lot:
// 1. Do not import everything over and over again. Keep a cache.
// 2. Exempt the standard library; their directories always match the package name.
// 3. Can use build.FindOnly and then use parser.ParseDir with mode PackageClauseOnly
if pkgAlias == "" {
pkg, err := build.Import(fullPath, srcDir, 0)
if err != nil {
// We expect this to happen for apps using reverse routing (since we
// have not yet generated the routes). Don't log that.
if !strings.HasSuffix(fullPath, "/app/routes") {
utils.Logger.Info("Debug: Could not find import:", "path", fullPath)
}
continue
}
pkgAlias = pkg.Name
}
imports[pkgAlias] = fullPath
}
}
// If this Decl is a struct type definition, it is summarized and added to specs.
// Else, specs is returned unchanged.
func appendStruct(fileName string, specs []*model.TypeInfo, pkgImportPath string, pkg *ast.Package, decl ast.Decl, imports map[string]string, fset *token.FileSet) []*model.TypeInfo {
// Filter out non-Struct type declarations.
spec, found := getStructTypeDecl(decl, fset)
if !found {
return specs
}
structType := spec.Type.(*ast.StructType)
// At this point we know it's a type declaration for a struct.
// Fill in the rest of the info by diving into the fields.
// Add it provisionally to the Controller list -- it's later filtered using field info.
controllerSpec := &model.TypeInfo{
StructName: spec.Name.Name,
ImportPath: pkgImportPath,
PackageName: pkg.Name,
}
for _, field := range structType.Fields.List {
// If field.Names is set, it's not an embedded type.
if field.Names != nil {
continue
}
// A direct "sub-type" has an ast.Field as either:
// Ident { "AppController" }
// SelectorExpr { "rev", "Controller" }
// Additionally, that can be wrapped by StarExprs.
fieldType := field.Type
pkgName, typeName := func() (string, string) {
// Drill through any StarExprs.
for {
if starExpr, ok := fieldType.(*ast.StarExpr); ok {
fieldType = starExpr.X
continue
}
break
}
// If the embedded type is in the same package, it's an Ident.
if ident, ok := fieldType.(*ast.Ident); ok {
return "", ident.Name
}
if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
return pkgIdent.Name, selectorExpr.Sel.Name
}
}
return "", ""
}()
// If a typename wasn't found, skip it.
if typeName == "" {
continue
}
// Find the import path for this type.
// If it was referenced without a package name, use the current package import path.
// Else, look up the package's import path by name.
var importPath string
if pkgName == "" {
importPath = pkgImportPath
} else {
var ok bool
if importPath, ok = imports[pkgName]; !ok {
utils.Logger.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName)
continue
}
}
controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{
ImportPath: importPath,
StructName: typeName,
})
}
return append(specs, controllerSpec)
}
// If decl is a Method declaration, it is summarized and added to the array
// underneath its receiver type.
// e.g. "Login" => {MethodSpec, MethodSpec, ..}
func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPath, pkgName string, imports map[string]string) {
// Func declaration?
funcDecl, ok := decl.(*ast.FuncDecl)
if !ok {
return
}
// Have a receiver?
if funcDecl.Recv == nil {
return
}
// Is it public?
if !funcDecl.Name.IsExported() {
return
}
// Does it return a Result?
if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 {
return
}
selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
if !ok {
return
}
if selExpr.Sel.Name != "Result" {
return
}
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != model.RevelImportPath {
return
}
method := &model.MethodSpec{
Name: funcDecl.Name.Name,
}
// Add a description of the arguments to the method.
for _, field := range funcDecl.Type.Params.List {
for _, name := range field.Names {
var importPath string
typeExpr := model.NewTypeExprFromAst(pkgName, field.Type)
if !typeExpr.Valid {
utils.Logger.Warn("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, getFuncName(funcDecl))
return // We didn't understand one of the args. Ignore this action.
}
// Local object
if typeExpr.PkgName == pkgName {
importPath = pkgImportPath
} else if typeExpr.PkgName != "" {
var ok bool
if importPath, ok = imports[typeExpr.PkgName]; !ok {
utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
}
}
method.Args = append(method.Args, &model.MethodArg{
Name: name.Name,
TypeExpr: typeExpr,
ImportPath: importPath,
})
}
}
// Add a description of the calls to Render from the method.
// Inspect every node (e.g. always return true).
method.RenderCalls = []*model.MethodCall{}
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
// Is it a function call?
callExpr, ok := node.(*ast.CallExpr)
if !ok {
return true
}
// Is it calling (*Controller).Render?
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
// The type of the receiver is not easily available, so just store every
// call to any method called Render.
if selExpr.Sel.Name != "Render" {
return true
}
// Add this call's args to the renderArgs.
pos := fset.Position(callExpr.Lparen)
methodCall := &model.MethodCall{
Line: pos.Line,
Names: []string{},
}
for _, arg := range callExpr.Args {
argIdent, ok := arg.(*ast.Ident)
if !ok {
continue
}
methodCall.Names = append(methodCall.Names, argIdent.Name)
}
method.RenderCalls = append(method.RenderCalls, methodCall)
return true
})
var recvTypeName string
var recvType = funcDecl.Recv.List[0].Type
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
recvTypeName = recvStarType.X.(*ast.Ident).Name
} else {
recvTypeName = recvType.(*ast.Ident).Name
}
mm[recvTypeName] = append(mm[recvTypeName], method)
}
// Scan app source code for calls to X.Y(), where X is of type *Validation.
//
// Recognize these scenarios:
// - "Y" = "Validation" and is a member of the receiver.
// (The common case for inline validation)
// - "X" is passed in to the func as a parameter.
// (For structs implementing Validated)
//
// The line number to which a validation call is attributed is that of the
// surrounding ExprStmt. This is so that it matches what runtime.Callers()
// reports.
//
// The end result is that we can set the default validation key for each call to
// be the same as the local variable.
func GetValidationKeys(fname string, fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[string]string) map[int]string {
var (
lineKeys = make(map[int]string)
// Check the func parameters and the receiver's members for the *revel.Validation type.
validationParam = getValidationParameter(funcDecl, imports)
)
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
// e.g. c.Validation.Required(arg) or v.Required(arg)
callExpr, ok := node.(*ast.CallExpr)
if !ok {
return true
}
// e.g. c.Validation.Required or v.Required
funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
switch x := funcSelector.X.(type) {
case *ast.SelectorExpr: // e.g. c.Validation
if x.Sel.Name != "Validation" {
return true
}
case *ast.Ident: // e.g. v
if validationParam == nil || x.Obj != validationParam {
return true
}
default:
return true
}
if len(callExpr.Args) == 0 {
return true
}
// Given the validation expression, extract the key.
key := callExpr.Args[0]
switch expr := key.(type) {
case *ast.BinaryExpr:
// If the argument is a binary expression, take the first expression.
// (e.g. c.Validation.Required(myName != ""))
key = expr.X
case *ast.UnaryExpr:
// If the argument is a unary expression, drill in.
// (e.g. c.Validation.Required(!myBool)
key = expr.X
case *ast.BasicLit:
// If it's a literal, skip it.
return true
}
if typeExpr := model.NewTypeExprFromAst("", key); typeExpr.Valid {
lineKeys[fset.Position(callExpr.End()).Line] = typeExpr.TypeName("")
} else {
utils.Logger.Error("Error: Failed to generate key for field validation. Make sure the field name is valid.", "file", fname,
"line", fset.Position(callExpr.End()).Line, "function", funcDecl.Name.String())
}
return true
})
return lineKeys
}
// Check to see if there is a *revel.Validation as an argument.
func getValidationParameter(funcDecl *ast.FuncDecl, imports map[string]string) *ast.Object {
for _, field := range funcDecl.Type.Params.List {
starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation
if !ok {
continue
}
selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation
if !ok {
continue
}
xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev
if !ok {
continue
}
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == model.RevelImportPath {
return field.Names[0].Obj
}
}
return nil
}
// getStructTypeDecl checks if the given decl is a type declaration for a
// struct. If so, the TypeSpec is returned.
func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) {
@@ -599,24 +247,3 @@ func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec,
return
}
func importPathFromPath(root string) string {
if vendorIdx := strings.Index(root, "/vendor/"); vendorIdx != -1 {
return filepath.ToSlash(root[vendorIdx+8:])
}
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
srcPath := filepath.Join(gopath, "src")
if strings.HasPrefix(root, srcPath) {
return filepath.ToSlash(root[len(srcPath)+1:])
}
}
srcPath := filepath.Join(build.Default.GOROOT, "src", "pkg")
if strings.HasPrefix(root, srcPath) {
utils.Logger.Warn("Code path should be in GOPATH, but is in GOROOT:", "path", root)
return filepath.ToSlash(root[len(srcPath)+1:])
}
utils.Logger.Error("Unexpected! Code path is not in GOPATH:", "path", root)
return ""
}

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.
func TestGetValidationKeys(t *testing.T) {
fset := token.NewFileSet()

116
parser/validation.go Normal file
View File

@@ -0,0 +1,116 @@
package parser
import (
"go/ast"
"go/token"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
)
// Scan app source code for calls to X.Y(), where X is of type *Validation.
//
// Recognize these scenarios:
// - "Y" = "Validation" and is a member of the receiver.
// (The common case for inline validation)
// - "X" is passed in to the func as a parameter.
// (For structs implementing Validated)
//
// The line number to which a validation call is attributed is that of the
// surrounding ExprStmt. This is so that it matches what runtime.Callers()
// reports.
//
// The end result is that we can set the default validation key for each call to
// be the same as the local variable.
func GetValidationKeys(fname string, fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[string]string) map[int]string {
var (
lineKeys = make(map[int]string)
// Check the func parameters and the receiver's members for the *revel.Validation type.
validationParam = getValidationParameter(funcDecl, imports)
)
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
// e.g. c.Validation.Required(arg) or v.Required(arg)
callExpr, ok := node.(*ast.CallExpr)
if !ok {
return true
}
// e.g. c.Validation.Required or v.Required
funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
switch x := funcSelector.X.(type) {
case *ast.SelectorExpr: // e.g. c.Validation
if x.Sel.Name != "Validation" {
return true
}
case *ast.Ident: // e.g. v
if validationParam == nil || x.Obj != validationParam {
return true
}
default:
return true
}
if len(callExpr.Args) == 0 {
return true
}
// Given the validation expression, extract the key.
key := callExpr.Args[0]
switch expr := key.(type) {
case *ast.BinaryExpr:
// If the argument is a binary expression, take the first expression.
// (e.g. c.Validation.Required(myName != ""))
key = expr.X
case *ast.UnaryExpr:
// If the argument is a unary expression, drill in.
// (e.g. c.Validation.Required(!myBool)
key = expr.X
case *ast.BasicLit:
// If it's a literal, skip it.
return true
}
if typeExpr := model.NewTypeExprFromAst("", key); typeExpr.Valid {
lineKeys[fset.Position(callExpr.End()).Line] = typeExpr.TypeName("")
} else {
utils.Logger.Error("Error: Failed to generate key for field validation. Make sure the field name is valid.", "file", fname,
"line", fset.Position(callExpr.End()).Line, "function", funcDecl.Name.String())
}
return true
})
return lineKeys
}
// Check to see if there is a *revel.Validation as an argument.
func getValidationParameter(funcDecl *ast.FuncDecl, imports map[string]string) *ast.Object {
for _, field := range funcDecl.Type.Params.List {
starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation
if !ok {
continue
}
selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation
if !ok {
continue
}
xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev
if !ok {
continue
}
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == model.RevelImportPath {
return field.Names[0].Obj
}
}
return nil
}

View File

@@ -0,0 +1,431 @@
package parser2
import (
"go/ast"
"go/token"
"path/filepath"
"strings"
"github.com/revel/cmd/logger"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"golang.org/x/tools/go/packages"
)
type (
SourceInfoProcessor struct {
sourceProcessor *SourceProcessor
}
)
func NewSourceInfoProcessor(sourceProcessor *SourceProcessor) *SourceInfoProcessor {
return &SourceInfoProcessor{sourceProcessor: sourceProcessor}
}
func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *model.SourceInfo) {
sourceInfo = &model.SourceInfo{
ValidationKeys: map[string]map[int]string{},
}
var (
isController = strings.HasSuffix(p.PkgPath, "/controllers") ||
strings.Contains(p.PkgPath, "/controllers/")
isTest = strings.HasSuffix(p.PkgPath, "/tests") ||
strings.Contains(p.PkgPath, "/tests/")
methodMap = map[string][]*model.MethodSpec{}
)
localImportMap := map[string]string{}
log := s.sourceProcessor.log.New("package", p.PkgPath)
log.Info("Processing package")
for _, tree := range p.Syntax {
for _, decl := range tree.Decls {
s.sourceProcessor.packageMap[p.PkgPath] = filepath.Dir(p.Fset.Position(decl.Pos()).Filename)
if !s.addImport(decl, p, localImportMap, log) {
continue
}
spec, found := s.getStructTypeDecl(decl, p.Fset)
// log.Info("Checking file","filename", p.Fset.Position(decl.Pos()).Filename,"found",found)
if found {
if isController || isTest {
controllerSpec := s.getControllerSpec(spec, p, localImportMap)
sourceInfo.StructSpecs = append(sourceInfo.StructSpecs, controllerSpec)
}
} else {
// Not a type definition, this could be a method for a controller try to extract that
// Func declaration?
funcDecl, ok := decl.(*ast.FuncDecl)
if !ok {
continue
}
// This could be a controller action endpoint, check and add if needed
if isController &&
funcDecl.Recv != nil && // Must have a receiver
funcDecl.Name.IsExported() && // be public
funcDecl.Type.Results != nil && len(funcDecl.Type.Results.List) == 1 {
// return one result
if m, receiver := s.getControllerFunc(funcDecl, p, localImportMap); m != nil {
methodMap[receiver] = append(methodMap[receiver], m)
log.Info("Added method map to ", "receiver", receiver, "method", m.Name)
}
}
// Check for validation
if lineKeyMap := s.getValidation(funcDecl, p); len(lineKeyMap) > 1 {
sourceInfo.ValidationKeys[p.PkgPath+"."+s.getFuncName(funcDecl)] = lineKeyMap
}
if funcDecl.Name.Name == "init" {
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, p.PkgPath)
}
}
}
}
// Add the method specs to the struct specs.
for _, spec := range sourceInfo.StructSpecs {
spec.MethodSpecs = methodMap[spec.StructName]
}
return
}
// Scan app source code for calls to X.Y(), where X is of type *Validation.
//
// Recognize these scenarios:
// - "Y" = "Validation" and is a member of the receiver.
// (The common case for inline validation)
// - "X" is passed in to the func as a parameter.
// (For structs implementing Validated)
//
// The line number to which a validation call is attributed is that of the
// surrounding ExprStmt. This is so that it matches what runtime.Callers()
// reports.
//
// The end result is that we can set the default validation key for each call to
// be the same as the local variable.
func (s *SourceInfoProcessor) getValidation(funcDecl *ast.FuncDecl, p *packages.Package) map[int]string {
var (
lineKeys = make(map[int]string)
// Check the func parameters and the receiver's members for the *revel.Validation type.
validationParam = s.getValidationParameter(funcDecl)
)
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
// e.g. c.Validation.Required(arg) or v.Required(arg)
callExpr, ok := node.(*ast.CallExpr)
if !ok {
return true
}
// e.g. c.Validation.Required or v.Required
funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
switch x := funcSelector.X.(type) {
case *ast.SelectorExpr: // e.g. c.Validation
if x.Sel.Name != "Validation" {
return true
}
case *ast.Ident: // e.g. v
if validationParam == nil || x.Obj != validationParam {
return true
}
default:
return true
}
if len(callExpr.Args) == 0 {
return true
}
// Given the validation expression, extract the key.
key := callExpr.Args[0]
switch expr := key.(type) {
case *ast.BinaryExpr:
// If the argument is a binary expression, take the first expression.
// (e.g. c.Validation.Required(myName != ""))
key = expr.X
case *ast.UnaryExpr:
// If the argument is a unary expression, drill in.
// (e.g. c.Validation.Required(!myBool)
key = expr.X
case *ast.BasicLit:
// If it's a literal, skip it.
return true
}
if typeExpr := model.NewTypeExprFromAst("", key); typeExpr.Valid {
lineKeys[p.Fset.Position(callExpr.End()).Line] = typeExpr.TypeName("")
} else {
s.sourceProcessor.log.Error("Error: Failed to generate key for field validation. Make sure the field name is valid.", "file", p.PkgPath,
"line", p.Fset.Position(callExpr.End()).Line, "function", funcDecl.Name.String())
}
return true
})
return lineKeys
}
// Check to see if there is a *revel.Validation as an argument.
func (s *SourceInfoProcessor) getValidationParameter(funcDecl *ast.FuncDecl) *ast.Object {
for _, field := range funcDecl.Type.Params.List {
starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation
if !ok {
continue
}
selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation
if !ok {
continue
}
xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev
if !ok {
continue
}
if selExpr.Sel.Name == "Validation" && s.sourceProcessor.importMap[xIdent.Name] == model.RevelImportPath {
return field.Names[0].Obj
}
}
return nil
}
func (s *SourceInfoProcessor) getControllerFunc(funcDecl *ast.FuncDecl, p *packages.Package, localImportMap map[string]string) (method *model.MethodSpec, recvTypeName string) {
selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
if !ok {
return
}
if selExpr.Sel.Name != "Result" {
return
}
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || s.sourceProcessor.importMap[pkgIdent.Name] != model.RevelImportPath {
return
}
method = &model.MethodSpec{
Name: funcDecl.Name.Name,
}
// Add a description of the arguments to the method.
for _, field := range funcDecl.Type.Params.List {
for _, name := range field.Names {
var importPath string
typeExpr := model.NewTypeExprFromAst(p.Name, field.Type)
if !typeExpr.Valid {
utils.Logger.Warn("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, s.getFuncName(funcDecl))
return // We didn't understand one of the args. Ignore this action.
}
// Local object
if typeExpr.PkgName == p.Name {
importPath = p.PkgPath
} else if typeExpr.PkgName != "" {
var ok bool
if importPath, ok = localImportMap[typeExpr.PkgName]; !ok {
if importPath, ok = s.sourceProcessor.importMap[typeExpr.PkgName]; !ok {
utils.Logger.Error("Unable to find import", "importMap", s.sourceProcessor.importMap, "localimport", localImportMap)
utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
}
}
}
method.Args = append(method.Args, &model.MethodArg{
Name: name.Name,
TypeExpr: typeExpr,
ImportPath: importPath,
})
}
}
// Add a description of the calls to Render from the method.
// Inspect every node (e.g. always return true).
method.RenderCalls = []*model.MethodCall{}
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
// Is it a function call?
callExpr, ok := node.(*ast.CallExpr)
if !ok {
return true
}
// Is it calling (*Controller).Render?
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
// The type of the receiver is not easily available, so just store every
// call to any method called Render.
if selExpr.Sel.Name != "Render" {
return true
}
// Add this call's args to the renderArgs.
pos := p.Fset.Position(callExpr.Lparen)
methodCall := &model.MethodCall{
Line: pos.Line,
Names: []string{},
}
for _, arg := range callExpr.Args {
argIdent, ok := arg.(*ast.Ident)
if !ok {
continue
}
methodCall.Names = append(methodCall.Names, argIdent.Name)
}
method.RenderCalls = append(method.RenderCalls, methodCall)
return true
})
recvType := funcDecl.Recv.List[0].Type
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
recvTypeName = recvStarType.X.(*ast.Ident).Name
} else {
recvTypeName = recvType.(*ast.Ident).Name
}
return
}
func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages.Package, localImportMap map[string]string) (controllerSpec *model.TypeInfo) {
structType := spec.Type.(*ast.StructType)
// At this point we know it's a type declaration for a struct.
// Fill in the rest of the info by diving into the fields.
// Add it provisionally to the Controller list -- it's later filtered using field info.
controllerSpec = &model.TypeInfo{
StructName: spec.Name.Name,
ImportPath: p.PkgPath,
PackageName: p.Name,
}
log := s.sourceProcessor.log.New("file", p.Fset.Position(spec.Pos()).Filename, "position", p.Fset.Position(spec.Pos()).Line)
for _, field := range structType.Fields.List {
// If field.Names is set, it's not an embedded type.
if field.Names != nil {
continue
}
// A direct "sub-type" has an ast.Field as either:
// Ident { "AppController" }
// SelectorExpr { "rev", "Controller" }
// Additionally, that can be wrapped by StarExprs.
fieldType := field.Type
pkgName, typeName := func() (string, string) {
// Drill through any StarExprs.
for {
if starExpr, ok := fieldType.(*ast.StarExpr); ok {
fieldType = starExpr.X
continue
}
break
}
// If the embedded type is in the same package, it's an Ident.
if ident, ok := fieldType.(*ast.Ident); ok {
return "", ident.Name
}
if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
return pkgIdent.Name, selectorExpr.Sel.Name
}
}
return "", ""
}()
// If a typename wasn't found, skip it.
if typeName == "" {
continue
}
// Find the import path for this type.
// If it was referenced without a package name, use the current package import path.
// Else, look up the package's import path by name.
var importPath string
if pkgName == "" {
importPath = p.PkgPath
} else {
var ok bool
if importPath, ok = localImportMap[pkgName]; !ok {
log.Debug("Debug: Unusual, failed to find package locally ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin")
if importPath, ok = s.sourceProcessor.importMap[pkgName]; !ok {
log.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin")
continue
}
}
}
controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{
ImportPath: importPath,
StructName: typeName,
})
}
s.sourceProcessor.log.Info("Added controller spec", "name", controllerSpec.StructName, "package", controllerSpec.ImportPath)
return
}
func (s *SourceInfoProcessor) getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) {
genDecl, ok := decl.(*ast.GenDecl)
if !ok {
return
}
if genDecl.Tok != token.TYPE {
return
}
if len(genDecl.Specs) == 0 {
utils.Logger.Warn("Warn: Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
return
}
spec = genDecl.Specs[0].(*ast.TypeSpec)
_, found = spec.Type.(*ast.StructType)
return
}
func (s *SourceInfoProcessor) getFuncName(funcDecl *ast.FuncDecl) string {
prefix := ""
if funcDecl.Recv != nil {
recvType := funcDecl.Recv.List[0].Type
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
prefix = "(*" + recvStarType.X.(*ast.Ident).Name + ")"
} else {
prefix = recvType.(*ast.Ident).Name
}
prefix += "."
}
return prefix + funcDecl.Name.Name
}
func (s *SourceInfoProcessor) addImport(decl ast.Decl, p *packages.Package, localImportMap map[string]string, log logger.MultiLogger) (shouldContinue bool) {
shouldContinue = true
genDecl, ok := decl.(*ast.GenDecl)
if !ok {
return
}
if genDecl.Tok == token.IMPORT {
shouldContinue = false
for _, spec := range genDecl.Specs {
importSpec := spec.(*ast.ImportSpec)
// fmt.Printf("*** import specification %#v\n", importSpec)
var pkgAlias string
if importSpec.Name != nil {
pkgAlias = importSpec.Name.Name
if pkgAlias == "_" {
continue
}
}
quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\""
fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes
if pkgAlias == "" {
pkgAlias = fullPath
if index := strings.LastIndex(pkgAlias, "/"); index > 0 {
pkgAlias = pkgAlias[index+1:]
}
}
localImportMap[pkgAlias] = fullPath
}
}
return
}

254
parser2/source_processor.go Normal file
View File

@@ -0,0 +1,254 @@
package parser2
import (
"errors"
"go/ast"
"go/parser"
"go/scanner"
"go/token"
"os"
"path/filepath"
"strings"
"github.com/revel/cmd/logger"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"golang.org/x/tools/go/packages"
)
type (
SourceProcessor struct {
revelContainer *model.RevelContainer
log logger.MultiLogger
packageList []*packages.Package
importMap map[string]string
packageMap map[string]string
sourceInfoProcessor *SourceInfoProcessor
sourceInfo *model.SourceInfo
}
)
func ProcessSource(revelContainer *model.RevelContainer) (sourceInfo *model.SourceInfo, compileError error) {
utils.Logger.Info("ProcessSource")
processor := NewSourceProcessor(revelContainer)
compileError = processor.parse()
sourceInfo = processor.sourceInfo
if compileError == nil {
processor.log.Infof("From parsers : Structures:%d InitImports:%d ValidationKeys:%d %v", len(sourceInfo.StructSpecs), len(sourceInfo.InitImportPaths), len(sourceInfo.ValidationKeys), sourceInfo.PackageMap)
}
return
}
func NewSourceProcessor(revelContainer *model.RevelContainer) *SourceProcessor {
s := &SourceProcessor{revelContainer: revelContainer, log: utils.Logger.New("parser", "SourceProcessor")}
s.sourceInfoProcessor = NewSourceInfoProcessor(s)
return s
}
func (s *SourceProcessor) parse() (compileError error) {
print("Parsing packages, (may require download if not cached)...")
if compileError = s.addPackages(); compileError != nil {
return
}
println(" Completed")
if compileError = s.addImportMap(); compileError != nil {
return
}
if compileError = s.addSourceInfo(); compileError != nil {
return
}
s.sourceInfo.PackageMap = map[string]string{}
getImportFromMap := func(packagePath string) string {
for path := range s.packageMap {
if strings.Index(path, packagePath) == 0 {
fullPath := s.packageMap[path]
return fullPath[:(len(fullPath) - len(path) + len(packagePath))]
}
}
return ""
}
s.sourceInfo.PackageMap[model.RevelImportPath] = getImportFromMap(model.RevelImportPath)
s.sourceInfo.PackageMap[s.revelContainer.ImportPath] = getImportFromMap(s.revelContainer.ImportPath)
for _, module := range s.revelContainer.ModulePathMap {
s.sourceInfo.PackageMap[module.ImportPath] = getImportFromMap(module.ImportPath)
}
return
}
// Using the packages.Load function load all the packages and type specifications (forces compile).
// this sets the SourceProcessor.packageList []*packages.Package.
func (s *SourceProcessor) addPackages() (err error) {
allPackages := []string{model.RevelImportPath + "/..."}
for _, module := range s.revelContainer.ModulePathMap {
allPackages = append(allPackages, module.ImportPath+"/...") // +"/app/controllers/...")
}
s.log.Info("Reading packages", "packageList", allPackages)
// allPackages = []string{s.revelContainer.ImportPath + "/..."} //+"/app/controllers/..."}
config := &packages.Config{
// ode: packages.NeedSyntax | packages.NeedCompiledGoFiles,
Mode: packages.NeedTypes | // For compile error
packages.NeedDeps | // To load dependent files
packages.NeedName | // Loads the full package name
packages.NeedSyntax, // To load ast tree (for end points)
// Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles |
// packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile |
// packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo |
// packages.NeedTypesSizes,
// Mode: packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedFiles |
// packages.NeedCompiledGoFiles | packages.NeedTypesSizes |
// packages.NeedSyntax | packages.NeedCompiledGoFiles ,
// Mode: packages.NeedSyntax | packages.NeedCompiledGoFiles | packages.NeedName | packages.NeedFiles |
// packages.LoadTypes | packages.NeedTypes | packages.NeedDeps, //, // |
// packages.NeedTypes, // packages.LoadTypes | packages.NeedSyntax | packages.NeedTypesInfo,
// packages.LoadSyntax | packages.NeedDeps,
Dir: s.revelContainer.AppPath,
}
config.Env = utils.ReducedEnv(false)
s.packageList, err = packages.Load(config, allPackages...)
s.log.Info("Loaded modules ", "len results", len(s.packageList), "error", err)
// Now process the files in the aap source folder s.revelContainer.ImportPath + "/...",
err = utils.Walk(s.revelContainer.BasePath, s.processPath)
s.log.Info("Loaded apps and modules ", "len results", len(s.packageList), "error", err)
return
}
// This callback is used to build the packages for the "app" package. This allows us to
// parse the source files without doing a full compile on them
// This callback only processes folders, so any files passed to this will return a nil.
func (s *SourceProcessor) processPath(path string, info os.FileInfo, err error) error {
if err != nil {
s.log.Error("Error scanning app source:", "error", err)
return nil
}
// Ignore files and folders not marked tmp (since those are generated)
// Also ignore files in the vendor folder.
if !info.IsDir() || info.Name() == "tmp" || strings.HasPrefix(path, filepath.Join(s.revelContainer.BasePath, "vendor")) {
return nil
}
// Real work for processing the folder
pkgImportPath := s.revelContainer.ImportPath
appPath := s.revelContainer.BasePath
if appPath != path {
pkgImportPath = s.revelContainer.ImportPath + "/" + filepath.ToSlash(path[len(appPath)+1:])
}
s.log.Info("Processing source package folder", "package", pkgImportPath, "path", path)
// Parse files within the path.
var pkgMap map[string]*ast.Package
fset := token.NewFileSet()
pkgMap, err = parser.ParseDir(
fset,
path,
func(f os.FileInfo) bool {
return !f.IsDir() && !strings.HasPrefix(f.Name(), ".") && strings.HasSuffix(f.Name(), ".go")
},
0)
if err != nil {
var errList scanner.ErrorList
if errors.As(err, &errList) {
pos := errList[0].Pos
newError := &utils.SourceError{
SourceType: ".go source",
Title: "Go Compilation Error",
Path: pos.Filename,
Description: errList[0].Msg,
Line: pos.Line,
Column: pos.Column,
SourceLines: utils.MustReadLines(pos.Filename),
}
errorLink := s.revelContainer.Config.StringDefault("error.link", "")
if errorLink != "" {
newError.SetLink(errorLink)
}
return newError
}
// This is exception, err already checked above. Here just a print
ast.Print(nil, err)
s.log.Fatal("Failed to parse dir", "error", err)
}
// Skip "main" packages.
delete(pkgMap, "main")
// Ignore packages that end with _test
// These cannot be included in source code that is not generated specifically as a test
for i := range pkgMap {
if len(i) > 6 {
if i[len(i)-5:] == "_test" {
delete(pkgMap, i)
}
}
}
// If there is no code in this directory, skip it.
if len(pkgMap) == 0 {
return nil
}
// There should be only one package in this directory.
if len(pkgMap) > 1 {
for i := range pkgMap {
println("Found duplicate packages in single directory ", i)
}
utils.Logger.Fatal("Most unexpected! Multiple packages in a single directory:", "packages", pkgMap)
}
// At this point there is only one package in the pkgs map,
p := &packages.Package{}
p.PkgPath = pkgImportPath
p.Fset = fset
for _, pkg := range pkgMap {
p.Name = pkg.Name
s.log.Info("Found package", "pkg.Name", pkg.Name, "p.Name", p.PkgPath)
for filename, astFile := range pkg.Files {
p.Syntax = append(p.Syntax, astFile)
p.GoFiles = append(p.GoFiles, filename)
}
}
s.packageList = append(s.packageList, p)
return nil
}
// This function is used to populate a map so that we can lookup controller embedded types in order to determine
// if a Struct inherits from from revel.Controller.
func (s *SourceProcessor) addImportMap() (err error) {
s.importMap = map[string]string{}
s.packageMap = map[string]string{}
for _, p := range s.packageList {
if len(p.Errors) > 0 {
// Generate a compile error
for _, e := range p.Errors {
s.log.Info("While reading packages encountered import error ignoring ", "PkgPath", p.PkgPath, "error", e)
}
}
for _, tree := range p.Syntax {
s.importMap[tree.Name.Name] = p.PkgPath
}
}
return
}
func (s *SourceProcessor) addSourceInfo() (err error) {
for _, p := range s.packageList {
if sourceInfo := s.sourceInfoProcessor.processPackage(p); sourceInfo != nil {
if s.sourceInfo != nil {
s.sourceInfo.Merge(sourceInfo)
} else {
s.sourceInfo = sourceInfo
}
}
}
return
}

View File

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

View File

@@ -5,19 +5,18 @@
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"fmt"
"github.com/revel/cmd/harness"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"go/build"
)
var cmdBuild = &Command{
UsageLine: "build -i [import path] -t [target path] -r [run mode]",
UsageLine: "revel build [-r [run mode]] [import path] [target path] ",
Short: "build a Revel application (e.g. for deployment)",
Long: `
Build the Revel web application named by the given import path.
@@ -25,7 +24,7 @@ This allows it to be deployed and run on a machine that lacks a Go installation.
For example:
revel build -a github.com/revel/examples/chat -t /tmp/chat
revel build github.com/revel/examples/chat /tmp/chat
`,
}
@@ -35,13 +34,21 @@ func init() {
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 {
c.Index = model.BUILD
if c.Build.TargetPath == "" {
c.Build.TargetPath = "target"
}
if len(args) == 0 && c.Build.ImportPath != "" {
return true
}
// If arguments were passed in then there must be two
if len(args) < 2 {
fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long)
return false
}
c.Build.ImportPath = args[0]
c.Build.TargetPath = args[1]
if len(args) > 2 {
@@ -50,82 +57,111 @@ func updateBuildConfig(c *model.CommandConfig, args []string) bool {
return true
}
// The main entry point to build application from command line
func buildApp(c *model.CommandConfig) {
// The main entry point to build application from command line.
func buildApp(c *model.CommandConfig) (err error) {
appImportPath, destPath, mode := c.ImportPath, c.Build.TargetPath, DefaultRunMode
if len(c.Build.Mode) > 0 {
mode = c.Build.Mode
}
// Convert target to absolute path
destPath, _ = filepath.Abs(destPath)
c.Build.TargetPath, _ = filepath.Abs(destPath)
c.Build.Mode = mode
c.Build.ImportPath = appImportPath
revel_paths := model.NewRevelPaths(mode, appImportPath, "", model.DoNothingRevelCallback)
// First, verify that it is either already empty or looks like a previous
// build (to avoid clobbering anything)
if utils.Exists(destPath) && !utils.Empty(destPath) && !utils.Exists(filepath.Join(destPath, "run.sh")) {
utils.Logger.Errorf("Abort: %s exists and does not look like a build directory.", destPath)
revelPaths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
if err != nil {
return
}
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
utils.Logger.Error("Remove all error", "error", err)
if err = buildSafetyCheck(destPath); err != nil {
return
}
if err := os.MkdirAll(destPath, 0777); err != nil {
utils.Logger.Error("makedir error", "error", err)
return
// Ensure the application can be built, this generates the main file
app, err := harness.Build(c, revelPaths)
if err != nil {
return err
}
app, reverr := harness.Build(c, revel_paths)
if reverr != nil {
utils.Logger.Error("Failed to build application", "error", reverr)
return
}
// Copy files
// Included are:
// - run scripts
// - binary
// - revel
// - app
packageFolders, err := buildCopyFiles(c, app, revelPaths)
if err != nil {
return
}
err = buildCopyModules(c, revelPaths, packageFolders, app)
if err != nil {
return
}
err = buildWriteScripts(c, app)
if err != nil {
return
}
return
}
// Copy the files to the target.
func buildCopyFiles(c *model.CommandConfig, app *harness.App, revelPaths *model.RevelContainer) (packageFolders []string, err error) {
appImportPath, destPath := c.ImportPath, c.Build.TargetPath
// Revel and the app are in a directory structure mirroring import path
srcPath := filepath.Join(destPath, "src")
destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath))
tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(model.RevelImportPath))
utils.MustCopyFile(destBinaryPath, app.BinaryPath)
if err = utils.CopyFile(destBinaryPath, filepath.Join(revelPaths.BasePath, app.BinaryPath)); err != nil {
return
}
utils.MustChmod(destBinaryPath, 0755)
// Copy the templates from the revel
_ = utils.MustCopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revel_paths.RevelPath, "conf"), nil)
_ = utils.MustCopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revel_paths.RevelPath, "templates"), nil)
if err = utils.CopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revelPaths.RevelPath, "conf"), nil); err != nil {
return
}
if err = utils.CopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revelPaths.RevelPath, "templates"), nil); err != nil {
return
}
// Get the folders to be packaged
packageFolders := strings.Split(revel_paths.Config.StringDefault("package.folders", "conf,public,app/views"), ",")
for i,p:=range packageFolders {
packageFolders = strings.Split(revelPaths.Config.StringDefault("package.folders", "conf,public,app/views"), ",")
for i, p := range packageFolders {
// Clean spaces, reformat slash to filesystem
packageFolders[i]=filepath.FromSlash(strings.TrimSpace(p))
packageFolders[i] = filepath.FromSlash(strings.TrimSpace(p))
}
if c.Build.CopySource {
_ = utils.MustCopyDir(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 {
return
}
} else {
for _, folder := range packageFolders {
_ = utils.MustCopyDir(
err = utils.CopyDir(
filepath.Join(srcPath, filepath.FromSlash(appImportPath), folder),
filepath.Join(revel_paths.BasePath, folder),
filepath.Join(revelPaths.BasePath, folder),
nil)
if err != nil {
return
}
}
}
return
}
// Based on the section copy over the build modules.
func buildCopyModules(c *model.CommandConfig, revelPaths *model.RevelContainer, packageFolders []string, app *harness.App) (err error) {
destPath := filepath.Join(c.Build.TargetPath, "src")
// Find all the modules used and copy them over.
config := revel_paths.Config.Raw()
modulePaths := make(map[string]string) // import path => filesystem path
config := revelPaths.Config.Raw()
// We should only copy over the section of options what the build is targeted for
// We will default to prod
moduleImportList := []string{}
for _, section := range config.Sections() {
// If the runmode is defined we will only import modules defined for that run mode
if c.Build.Mode != "" && c.Build.Mode != section {
@@ -140,51 +176,82 @@ func buildApp(c *model.CommandConfig) {
if moduleImportPath == "" {
continue
}
modPkg, err := build.Import(moduleImportPath, revel_paths.RevelPath, build.FindOnly)
if err != nil {
utils.Logger.Fatalf("Failed to load module %s (%s): %s", key[len("module."):], c.ImportPath, err)
}
modulePaths[moduleImportPath] = modPkg.Dir
moduleImportList = append(moduleImportList, moduleImportPath)
}
}
// Copy the the paths for each of the modules
for importPath, fsPath := range modulePaths {
utils.Logger.Info("Copy files ", "to", filepath.Join(srcPath, importPath), "from", fsPath)
for _, importPath := range moduleImportList {
fsPath := app.PackagePathMap[importPath]
utils.Logger.Info("Copy files ", "to", filepath.Join(destPath, importPath), "from", fsPath)
if c.Build.CopySource {
_ = utils.MustCopyDir(filepath.Join(srcPath, importPath), fsPath, nil)
err = utils.CopyDir(filepath.Join(destPath, importPath), fsPath, nil)
if err != nil {
return
}
} else {
for _, folder := range packageFolders {
_ = utils.MustCopyDir(
filepath.Join(srcPath, importPath, folder),
err = utils.CopyDir(
filepath.Join(destPath, importPath, folder),
filepath.Join(fsPath, folder),
nil)
if err != nil {
return
}
}
}
//
}
return
}
// Write the run scripts for the build.
func buildWriteScripts(c *model.CommandConfig, app *harness.App) (err error) {
tmplData := map[string]interface{}{
"BinName": filepath.Base(app.BinaryPath),
"ImportPath": appImportPath,
"Mode": mode,
"ImportPath": c.Build.ImportPath,
"Mode": c.Build.Mode,
}
utils.MustGenerateTemplate(
filepath.Join(destPath, "run.sh"),
err = utils.GenerateTemplate(
filepath.Join(c.Build.TargetPath, "run.sh"),
PACKAGE_RUN_SH,
tmplData,
)
utils.MustChmod(filepath.Join(destPath, "run.sh"), 0755)
utils.MustGenerateTemplate(
filepath.Join(destPath, "run.bat"),
if err != nil {
return
}
utils.MustChmod(filepath.Join(c.Build.TargetPath, "run.sh"), 0755)
err = utils.GenerateTemplate(
filepath.Join(c.Build.TargetPath, "run.bat"),
PACKAGE_RUN_BAT,
tmplData,
)
if err != nil {
return
}
fmt.Println("Your application has been built in:", destPath)
fmt.Println("Your application has been built in:", c.Build.TargetPath)
return
}
// Checks to see if the target folder exists and can be created.
func buildSafetyCheck(destPath string) error {
// First, verify that it is either already empty or looks like a previous
// build (to avoid clobbering anything)
if utils.Exists(destPath) && !utils.Empty(destPath) && !utils.Exists(filepath.Join(destPath, "run.sh")) {
return utils.NewBuildError("Abort: %s exists and does not look like a build directory.", "path", destPath)
}
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
return utils.NewBuildIfError(err, "Remove all error", "path", destPath)
}
if err := os.MkdirAll(destPath, 0777); err != nil {
return utils.NewBuildIfError(err, "MkDir all error", "path", destPath)
}
return nil
}
const PACKAGE_RUN_SH = `#!/bin/sh
@@ -192,6 +259,7 @@ const PACKAGE_RUN_SH = `#!/bin/sh
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}
`
const PACKAGE_RUN_BAT = `@echo off
{{.BinName}} -importPath {{.ImportPath}} -srcPath "%CD%\src" -runMode {{.Mode}}

50
revel/build_test.go Normal file
View File

@@ -0,0 +1,50 @@
package main_test
import (
"os"
"path/filepath"
"testing"
"github.com/revel/cmd/model"
main "github.com/revel/cmd/revel"
"github.com/revel/cmd/utils"
"github.com/stretchr/testify/assert"
)
// test the commands.
func TestBuild(t *testing.T) {
a := assert.New(t)
gopath := setup("revel-test-build", a)
t.Run("Build", func(t *testing.T) {
a := assert.New(t)
c := newApp("build-test", model.NEW, nil, a)
a.Nil(main.Commands[model.NEW].RunWith(c), "failed to run new")
c.Index = model.BUILD
c.Build.TargetPath = filepath.Join(gopath, "build-test", "target")
c.Build.ImportPath = c.ImportPath
a.Nil(main.Commands[model.BUILD].RunWith(c), "Failed to run build-test")
a.True(utils.Exists(filepath.Join(gopath, "build-test", "target")))
})
t.Run("Build-withFlags", func(t *testing.T) {
a := assert.New(t)
c := newApp("build-test-WithFlags", model.NEW, nil, a)
c.BuildFlags = []string{
"build-test-WithFlags/app.AppVersion=SomeValue",
"build-test-WithFlags/app.SomeOtherValue=SomeValue",
}
a.Nil(main.Commands[model.NEW].RunWith(c), "failed to run new")
c.Index = model.BUILD
c.Build.TargetPath = filepath.Join(gopath, "build-test", "target")
c.Build.ImportPath = c.ImportPath
a.Nil(main.Commands[model.BUILD].RunWith(c), "Failed to run build-test-withFlags")
a.True(utils.Exists(filepath.Join(gopath, "build-test", "target")))
})
if !t.Failed() {
if err := os.RemoveAll(gopath); err != nil {
a.Fail("Failed to remove test path")
}
}
}

View File

@@ -6,22 +6,22 @@ package main
import (
"fmt"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"go/build"
"os"
"path/filepath"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
)
var cmdClean = &Command{
UsageLine: "clean -i [import path]",
UsageLine: "clean [import path]",
Short: "clean a Revel application's temp files",
Long: `
Clean the Revel web application named by the given import path.
For example:
revel clean -a github.com/revel/examples/chat
revel clean github.com/revel/examples/chat
It removes the app/tmp and app/routes directory.
@@ -34,9 +34,12 @@ func init() {
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 {
c.Index = model.CLEAN
if len(args) == 0 && c.Clean.ImportPath != "" {
return true
}
if len(args) == 0 {
fmt.Fprintf(os.Stderr, cmdClean.Long)
return false
@@ -45,16 +48,11 @@ func updateCleanConfig(c *model.CommandConfig, args []string) bool {
return true
}
// Clean the source directory of generated files
func cleanApp(c *model.CommandConfig) {
appPkg, err := build.Import(c.ImportPath, "", build.FindOnly)
if err != nil {
utils.Logger.Fatal("Abort: Failed to find import path:", "error", err)
}
// Clean the source directory of generated files.
func cleanApp(c *model.CommandConfig) (err error) {
purgeDirs := []string{
filepath.Join(appPkg.Dir, "app", "tmp"),
filepath.Join(appPkg.Dir, "app", "routes"),
filepath.Join(c.AppPath, "app", "tmp"),
filepath.Join(c.AppPath, "app", "routes"),
}
for _, dir := range purgeDirs {
@@ -65,4 +63,5 @@ func cleanApp(c *model.CommandConfig) {
return
}
}
return err
}

40
revel/clean_test.go Normal file
View File

@@ -0,0 +1,40 @@
package main_test
import (
"os"
"path/filepath"
"testing"
"github.com/revel/cmd/model"
main "github.com/revel/cmd/revel"
"github.com/revel/cmd/utils"
"github.com/stretchr/testify/assert"
)
// test the commands.
func TestClean(t *testing.T) {
a := assert.New(t)
gopath := setup("revel-test-clean", a)
t.Run("Clean", func(t *testing.T) {
a := assert.New(t)
c := newApp("clean-test", model.NEW, nil, a)
a.Nil(main.Commands[model.NEW].RunWith(c), "failed to run new")
c.Index = model.TEST
a.Nil(main.Commands[model.TEST].RunWith(c), "failed to run test")
a.True(utils.Exists(filepath.Join(gopath, "clean-test", "app", "tmp", "main.go")),
"Missing main from path "+filepath.Join(gopath, "clean-test", "app", "tmp", "main.go"))
c.Clean.ImportPath = c.ImportPath
a.Nil(main.Commands[model.CLEAN].RunWith(c), "Failed to run clean-test")
a.False(utils.Exists(filepath.Join(gopath, "clean-test", "app", "tmp", "main.go")),
"Did not remove main from path "+filepath.Join(gopath, "clean-test", "app", "tmp", "main.go"))
})
if !t.Failed() {
if err := os.RemoveAll(gopath); err != nil {
a.Fail("Failed to remove test path")
}
}
}

95
revel/command_test.go Normal file
View File

@@ -0,0 +1,95 @@
package main_test
import (
"fmt"
"go/build"
"os"
"os/exec"
"path/filepath"
"github.com/revel/cmd/logger"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"github.com/stretchr/testify/assert"
)
// Test that the event handler can be attached and it dispatches the event received.
func setup(suffix string, a *assert.Assertions) string {
temp := os.TempDir()
wd, _ := os.Getwd()
utils.InitLogger(wd, logger.LvlInfo)
gopath := filepath.Join(temp, "revel-test", suffix)
if utils.Exists(gopath) {
utils.Logger.Info("Removing test path", "path", gopath)
if err := os.RemoveAll(gopath); err != nil {
a.Fail("Failed to remove test path")
}
}
err := os.MkdirAll(gopath, os.ModePerm)
a.Nil(err, "Failed to create gopath "+gopath)
// So this is the issue, on the mac when folders are created in a temp folder they are returned like
// /var/folders/nz/vv4_9tw56nv9k3tkvyszvwg80000gn/T/revel-test/revel-test-build
// But if you change into that directory and read the current folder it is
// /private/var/folders/nz/vv4_9tw56nv9k3tkvyszvwg80000gn/T/revel-test/revel-test-build
// So to make this work on darwin this code was added
if err := os.Chdir(gopath); err != nil {
panic(err)
}
newwd, _ := os.Getwd()
gopath = newwd
defaultBuild := build.Default
defaultBuild.GOPATH = gopath
build.Default = defaultBuild
utils.Logger.Info("Setup stats", "original wd", wd, "new wd", newwd, "gopath", gopath, "gopath exists", utils.DirExists(gopath), "wd exists", utils.DirExists(newwd))
return gopath
}
// Create a new app for the name.
func newApp(name string, command model.COMMAND, precall func(c *model.CommandConfig), a *assert.Assertions) *model.CommandConfig {
c := &model.CommandConfig{Vendored: true}
switch command {
case model.NEW:
c.New.ImportPath = name
c.New.Callback = func() error {
// On callback we will invoke a specific branch of revel so that it works
goModCmd := exec.Command("go", "mod", "tidy")
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
getOutput, _ := goModCmd.CombinedOutput()
fmt.Printf("Calling go mod tidy %s", string(getOutput))
goModCmd = exec.Command("go", "mod", "edit", "-replace=github.com/revel/revel=github.com/revel/revel@develop")
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
getOutput, _ = goModCmd.CombinedOutput()
fmt.Printf("Calling go mod edit %v", string(getOutput))
return nil
}
case model.BUILD:
c.Build.ImportPath = name
case model.TEST:
c.Test.ImportPath = name
case model.PACKAGE:
c.Package.ImportPath = name
case model.VERSION:
c.Version.ImportPath = name
case model.CLEAN:
c.Clean.ImportPath = name
default:
a.Fail("Unknown command ", command)
}
c.Index = command
if precall != nil {
precall(c)
}
if c.UpdateImportPath() != nil {
a.Fail("Unable to update import path")
}
c.InitPackageResolver()
return c
}

View File

@@ -5,10 +5,10 @@
package main
import (
"bytes"
"fmt"
"go/build"
"math/rand"
"net/url"
"os"
"os/exec"
"path/filepath"
@@ -18,8 +18,10 @@ import (
"github.com/revel/cmd/utils"
)
const ErrNoSkeleton Error = "failed to find skeleton in filepath"
var cmdNew = &Command{
UsageLine: "new -i [path] -s [skeleton]",
UsageLine: "new -i [path] -s [skeleton] -p [package name]",
Short: "create a skeleton Revel application",
Long: `
New creates a few files to get a new Revel application running quickly.
@@ -43,260 +45,245 @@ func init() {
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 {
c.Index = model.NEW
if len(c.New.Package) > 0 {
c.New.NotVendored = false
}
c.Vendored = !c.New.NotVendored
if len(args) == 0 {
fmt.Fprintf(os.Stderr, cmdNew.Long)
return false
if len(c.New.ImportPath) == 0 {
fmt.Fprintf(os.Stderr, cmdNew.Long)
return false
}
return true
}
c.New.ImportPath = args[0]
if len(args)>1 {
c.New.Skeleton = args[1]
if len(args) > 1 {
c.New.SkeletonPath = args[1]
}
return true
return true
}
// Call to create a new application
func newApp(c *model.CommandConfig) {
// check for proper args by count
c.SkeletonPath = c.New.Skeleton
// Check for an existing folder so we dont clober it
c.AppPath = filepath.Join(c.SrcRoot, filepath.FromSlash(c.ImportPath))
_, err := build.Import(c.ImportPath, "", build.FindOnly)
if err==nil || !utils.Empty(c.AppPath) {
utils.Logger.Fatal("Abort: Import path already exists.","path", c.ImportPath)
// Call to create a new application.
func newApp(c *model.CommandConfig) (err error) {
// Check for an existing folder so we don't clobber it
_, err = build.Import(c.ImportPath, "", build.FindOnly)
if err == nil || !utils.Empty(c.AppPath) {
return utils.NewBuildError("Abort: Import path already exists.", "path", c.ImportPath, "apppath", c.AppPath)
}
if c.New.Vendored {
depPath, err := exec.LookPath("dep")
if err != nil {
// Do not halt build unless a new package needs to be imported
utils.Logger.Fatal("New: `dep` executable not found in PATH, but vendor folder requested." +
"You must install the dep tool before creating a vendored project. " +
"You can install the `dep` tool by doing a `go get -u github.com/golang/dep/cmd/dep`")
}
vendorPath := filepath.Join(c.ImportPath,"vendor")
if !utils.DirExists(vendorPath) {
err := os.MkdirAll(vendorPath,os.ModePerm)
utils.PanicOnError(err, "Failed to create " + vendorPath)
}
// In order for dep to run there needs to be a source file in the folder
tempPath := filepath.Join(c.ImportPath,"tmp")
if !utils.DirExists(tempPath) {
err := os.MkdirAll(tempPath,os.ModePerm)
utils.PanicOnError(err, "Failed to create " + vendorPath)
err = utils.MustGenerateTemplate(filepath.Join(tempPath,"main.go"), NEW_MAIN_FILE, nil)
utils.PanicOnError(err, "Failed to create main file " + vendorPath)
}
packageFile := filepath.Join(c.ImportPath,"Gopkg.toml")
if !utils.Exists(packageFile) {
utils.MustGenerateTemplate(packageFile,VENDOR_GOPKG,nil)
} else {
utils.Logger.Info("Package file exists in skeleto, skipping adding")
}
getCmd := exec.Command(depPath, "ensure", "-v")
getCmd.Dir = c.ImportPath
utils.Logger.Info("Exec:", "args", getCmd.Args)
getCmd.Dir = c.ImportPath
getOutput, err := getCmd.CombinedOutput()
if err != nil {
utils.Logger.Fatal(string(getOutput))
}
}
// checking and setting application
setApplicationPath(c)
// checking and setting skeleton
setSkeletonPath(c)
if err = setSkeletonPath(c); err != nil {
return
}
// Create application path
if err := os.MkdirAll(c.AppPath, os.ModePerm); err != nil {
return utils.NewBuildError("Abort: Unable to create app path.", "path", c.AppPath)
}
// checking and setting application
if err = setApplicationPath(c); err != nil {
return err
}
// This kicked off the download of the revel app, not needed for vendor
if !c.Vendored {
// At this point the versions can be set
if err = c.SetVersions(); err != nil {
return
}
}
// copy files to new app directory
copyNewAppFiles(c)
if err = copyNewAppFiles(c); err != nil {
return
}
// Run the vendor tool if needed
if c.Vendored {
if err = createModVendor(c); err != nil {
return
}
}
// goodbye world
fmt.Fprintln(os.Stdout, "Your application is ready:\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
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 {
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
}
// Used to generate a new secret key
func createModVendor(c *model.CommandConfig) (err error) {
utils.Logger.Info("Creating a new mod app")
goModCmd := exec.Command("go", "mod", "init", filepath.Join(c.New.Package, c.AppName))
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
utils.Logger.Info("Exec:", "args", goModCmd.Args, "env", goModCmd.Env, "workingdir", goModCmd.Dir)
getOutput, err := goModCmd.CombinedOutput()
if c.New.Callback != nil {
err = c.New.Callback()
}
if err != nil {
return utils.NewBuildIfError(err, string(getOutput))
}
return
}
// Used to generate a new secret key.
const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
// Generate a secret key
// Generate a secret key.
func generateSecret() string {
chars := make([]byte, 64)
for i := 0; i < 64; i++ {
chars[i] = alphaNumeric[rand.Intn(len(alphaNumeric))]
}
return string(chars)
}
// Sets the applicaiton path
func setApplicationPath(c *model.CommandConfig) {
// Sets the application path.
func setApplicationPath(c *model.CommandConfig) (err error) {
// revel/revel#1014 validate relative path, we cannot use built-in functions
// since Go import path is valid relative path too.
// so check basic part of the path, which is "."
if filepath.IsAbs(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 !c.New.Vendored {
var err error
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)
if err != nil {
// Go get the revel project
getCmd := exec.Command(c.GoCmd, "get", model.RevelImportPath)
utils.Logger.Info("Exec:" + c.GoCmd, "args", getCmd.Args)
getOutput, err := getCmd.CombinedOutput()
err = c.PackageResolver(model.RevelImportPath)
if err != nil {
utils.Logger.Fatal("Failed to fetch revel " + model.RevelImportPath, "getOutput", string(getOutput))
return utils.NewBuildIfError(err, "Failed to fetch revel "+model.RevelImportPath)
}
}
}
c.AppName = filepath.Base(c.AppPath)
c.BasePath = filepath.ToSlash(filepath.Dir(c.ImportPath))
if c.BasePath == "." {
// we need to remove the a single '.' when
// the app is in the $GOROOT/src directory
c.BasePath = ""
} else {
// we need to append a '/' when the app is
// is a subdirectory such as $GOROOT/src/path/to/revelapp
c.BasePath += "/"
}
return nil
}
// Set the skeleton path
func setSkeletonPath(c *model.CommandConfig) {
var err error
if len(c.SkeletonPath) > 0 { // user specified
// Set the skeleton path.
func setSkeletonPath(c *model.CommandConfig) (err error) {
if len(c.New.SkeletonPath) == 0 {
c.New.SkeletonPath = "https://" + RevelSkeletonsImportPath + ":basic/bootstrap4"
}
_, err = build.Import(c.SkeletonPath, "", build.FindOnly)
if err != nil {
// Execute "go get <pkg>"
getCmd := exec.Command(c.GoCmd, "get", "-d", c.SkeletonPath)
fmt.Println("Exec:", getCmd.Args)
getOutput, err := getCmd.CombinedOutput()
// First check to see the protocol of the string
sp, err := url.Parse(c.New.SkeletonPath)
if err == nil {
utils.Logger.Info("Detected skeleton path", "path", sp)
// check getOutput for no buildible string
bpos := bytes.Index(getOutput, []byte("no buildable Go source files in"))
if err != nil && bpos == -1 {
utils.Logger.Fatalf("Abort: Could not find or 'go get' Skeleton source code: %s\n%s\n", getOutput, c.SkeletonPath)
}
}
// use the
c.SkeletonPath = filepath.Join(c.SrcRoot, c.SkeletonPath)
} else {
// use the revel default
revelCmdPkg, err := build.Import(RevelCmdImportPath, "", build.FindOnly)
if err != nil {
if err != nil {
// Go get the revel project
getCmd := exec.Command(c.GoCmd, "get", RevelCmdImportPath + "/revel")
utils.Logger.Info("Exec:" + c.GoCmd, "args", getCmd.Args)
getOutput, err := getCmd.CombinedOutput()
switch strings.ToLower(sp.Scheme) {
// TODO Add support for ftp, sftp, scp ??
case "":
sp.Scheme = "file"
fallthrough
case "file":
fullpath := sp.String()[7:]
if !filepath.IsAbs(fullpath) {
fullpath, err = filepath.Abs(fullpath)
if err != nil {
utils.Logger.Fatal("Failed to fetch revel cmd " + RevelCmdImportPath, "getOutput", string(getOutput))
}
revelCmdPkg, err = build.Import(RevelCmdImportPath, "", build.FindOnly)
if err!= nil {
utils.Logger.Fatal("Failed to find source of revel cmd " + RevelCmdImportPath, "getOutput", string(getOutput), "error",err, "dir", revelCmdPkg.Dir)
return
}
}
c.New.SkeletonPath = fullpath
utils.Logger.Info("Set skeleton path to ", fullpath)
if !utils.DirExists(fullpath) {
return fmt.Errorf("%w %s %s", ErrNoSkeleton, fullpath, sp.String())
}
case "git":
fallthrough
case "http":
fallthrough
case "https":
if err := newLoadFromGit(c, sp); err != nil {
return err
}
default:
utils.Logger.Fatal("Unsupported skeleton schema ", "path", c.New.SkeletonPath)
}
c.SkeletonPath = filepath.Join(revelCmdPkg.Dir, "revel", "skeleton")
// TODO check to see if the path needs to be extracted
} else {
utils.Logger.Fatal("Invalid skeleton path format", "path", c.New.SkeletonPath)
}
return
}
func copyNewAppFiles(c *model.CommandConfig) {
var err error
err = os.MkdirAll(c.AppPath, 0777)
utils.PanicOnError(err, "Failed to create directory "+c.AppPath)
// Load skeleton from git.
func newLoadFromGit(c *model.CommandConfig, sp *url.URL) (err error) {
// This method indicates we need to fetch from a repository using git
// Execute "git clone get <pkg>"
targetPath := filepath.Join(os.TempDir(), "revel", "skeleton")
os.RemoveAll(targetPath)
pathpart := strings.Split(sp.Path, ":")
getCmd := exec.Command("git", "clone", sp.Scheme+"://"+sp.Host+pathpart[0], targetPath)
utils.Logger.Info("Exec:", "args", getCmd.Args)
getOutput, err := getCmd.CombinedOutput()
if err != nil {
utils.Logger.Fatal("Abort: could not clone the Skeleton source code: ", "output", string(getOutput), "path", c.New.SkeletonPath)
}
outputPath := targetPath
if len(pathpart) > 1 {
outputPath = filepath.Join(targetPath, filepath.Join(strings.Split(pathpart[1], string('/'))...))
}
outputPath, _ = filepath.Abs(outputPath)
if !strings.HasPrefix(outputPath, targetPath) {
utils.Logger.Fatal("Unusual target path outside root path", "target", outputPath, "root", targetPath)
}
_ = utils.MustCopyDir(c.AppPath, c.SkeletonPath, map[string]interface{}{
c.New.SkeletonPath = outputPath
return
}
func copyNewAppFiles(c *model.CommandConfig) (err error) {
err = os.MkdirAll(c.AppPath, 0777)
if err != nil {
return utils.NewBuildIfError(err, "MKDIR failed")
}
err = utils.CopyDir(c.AppPath, c.New.SkeletonPath, map[string]interface{}{
// app.conf
"AppName": c.AppName,
"BasePath": c.BasePath,
"BasePath": c.AppPath,
"Secret": generateSecret(),
})
if err != nil {
fmt.Printf("err %v", err)
return utils.NewBuildIfError(err, "Copy Dir failed")
}
// Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore.
gitignore := ".gitignore"
utils.MustCopyFile(filepath.Join(c.AppPath, gitignore), filepath.Join(c.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/cmd/revel"]
[[override]]
branch = "master"
name = "github.com/revel/modules"
[[override]]
branch = "master"
name = "github.com/revel/revel"
[[override]]
branch = "master"
name = "github.com/revel/cmd"
[[override]]
branch = "master"
name = "github.com/revel/log15"
[[override]]
branch = "master"
name = "github.com/revel/cron"
[[override]]
branch = "master"
name = "github.com/xeonx/timeago"
`
NEW_MAIN_FILE = `package main
`
)

55
revel/new_test.go Normal file
View File

@@ -0,0 +1,55 @@
package main_test
import (
"os"
"testing"
"github.com/revel/cmd/model"
main "github.com/revel/cmd/revel"
"github.com/stretchr/testify/assert"
)
// test the commands.
func TestNew(t *testing.T) {
a := assert.New(t)
gopath := setup("revel-test-new", a)
t.Run("New", func(t *testing.T) {
a := assert.New(t)
c := newApp("new-test", model.NEW, nil, a)
a.Nil(main.Commands[model.NEW].RunWith(c), "New failed")
})
t.Run("New-NotVendoredmode", func(t *testing.T) {
a := assert.New(t)
c := newApp("new-notvendored", model.NEW, nil, a)
c.New.NotVendored = true
a.Nil(main.Commands[model.NEW].RunWith(c), "New failed")
})
t.Run("Path", func(t *testing.T) {
a := assert.New(t)
c := newApp("new/test/a", model.NEW, nil, a)
a.Nil(main.Commands[model.NEW].RunWith(c), "New path failed")
})
t.Run("Path-Duplicate", func(t *testing.T) {
a := assert.New(t)
c := newApp("new/test/b", model.NEW, nil, a)
a.Nil(main.Commands[model.NEW].RunWith(c), "New path failed")
c = newApp("new/test/b", model.NEW, nil, a)
a.NotNil(main.Commands[model.NEW].RunWith(c), "Duplicate path Did Not failed")
})
t.Run("Skeleton-Git", func(t *testing.T) {
a := assert.New(t)
c := newApp("new/test/c/1", model.NEW, nil, a)
c.New.SkeletonPath = "git://github.com/revel/skeletons:basicnsadnsak"
a.NotNil(main.Commands[model.NEW].RunWith(c), "Expected Failed to run with new")
// We need to pick a different path
c = newApp("new/test/c/2", model.NEW, nil, a)
c.New.SkeletonPath = "git://github.com/revel/skeletons:basic/bootstrap4"
a.Nil(main.Commands[model.NEW].RunWith(c), "Failed to run with new skeleton git")
})
if !t.Failed() {
if err := os.RemoveAll(gopath); err != nil {
a.Fail("Failed to remove test path")
}
}
}

View File

@@ -15,7 +15,7 @@ import (
)
var cmdPackage = &Command{
UsageLine: "package -i [import path] -r [run mode]",
UsageLine: "package [-r [run mode]] [application] ",
Short: "package a Revel application (e.g. for deployment)",
Long: `
Package the Revel web application named by the given import path.
@@ -28,7 +28,7 @@ Run mode defaults to "dev".
For example:
revel package -i github.com/revel/examples/chat
revel package github.com/revel/examples/chat
`,
}
@@ -37,53 +37,62 @@ func init() {
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 {
c.Index = model.PACKAGE
if len(args) == 0 {
fmt.Fprintf(os.Stderr, cmdPackage.Long)
return false
if len(args) == 0 && c.Package.ImportPath != "" {
return true
}
c.Package.ImportPath = args[0]
if len(args)>1 {
if len(args) > 1 {
c.Package.Mode = args[1]
}
return true
}
func packageApp(c *model.CommandConfig) {
// Called to package the app.
func packageApp(c *model.CommandConfig) (err error) {
// Determine the run mode.
mode := DefaultRunMode
if len(c.Package.Mode) >= 0 {
mode = c.Package.Mode
}
mode := c.Package.Mode
appImportPath := c.ImportPath
revel_paths := model.NewRevelPaths(mode, appImportPath, "", model.DoNothingRevelCallback)
revelPaths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
if err != nil {
return
}
// Remove the archive if it already exists.
destFile := filepath.Base(revel_paths.BasePath) + ".tar.gz"
destFile := filepath.Join(c.AppPath, filepath.Base(revelPaths.BasePath)+".tar.gz")
if c.Package.TargetPath != "" {
if filepath.IsAbs(c.Package.TargetPath) {
destFile = c.Package.TargetPath
} else {
destFile = filepath.Join(c.AppPath, c.Package.TargetPath)
}
}
if err := os.Remove(destFile); err != nil && !os.IsNotExist(err) {
utils.Logger.Error("Unable to remove target file","error",err,"file",destFile)
os.Exit(1)
return utils.NewBuildError("Unable to remove target file", "error", err, "file", destFile)
}
// 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")
// 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.CopySource = c.Package.CopySource
buildApp(c)
if err = buildApp(c); err != nil {
return
}
// Create the zip file.
archiveName := utils.MustTarGzDir(destFile, tmpDir)
archiveName, err := utils.TarGzDir(destFile, tmpDir)
if err != nil {
return
}
fmt.Println("Your archive is ready:", archiveName)
return
}

31
revel/package_test.go Normal file
View File

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

View File

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

View File

@@ -5,40 +5,34 @@
package main
import (
"encoding/json"
"fmt"
"os"
"strconv"
"fmt"
"github.com/revel/cmd/harness"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"go/build"
)
var cmdRun = &Command{
UsageLine: "run [import path] [run mode] [port]",
UsageLine: "run [-m [run mode] -p [port]] [import path] ",
Short: "run a Revel application",
Long: `
Run the Revel web application named by the given import path.
For example, to run the chat room sample application:
revel run github.com/revel/examples/chat dev
revel run -m dev github.com/revel/examples/chat
The run mode is used to select which set of app.conf configuration should
apply and may be used to determine logic in the application itself.
Run mode defaults to "dev".
You can set a port as an optional third parameter. For example:
You can set a port as well. For example:
revel run github.com/revel/examples/chat prod 8080`,
}
// RunArgs holds revel run parameters
type RunArgs struct {
ImportPath string
Mode string
Port int
revel run -m prod -p 8080 github.com/revel/examples/chat `,
}
func init() {
@@ -47,14 +41,23 @@ func init() {
}
func updateRunConfig(c *model.CommandConfig, args []string) bool {
convertPort := func(value string) int {
if value != "" {
port, err := strconv.Atoi(value)
if err != nil {
utils.Logger.Fatalf("Failed to parse port as integer: %s", c.Run.Port)
}
return port
}
return 0
}
switch len(args) {
case 3:
// Possible combinations
// revel run [import-path] [run-mode] [port]
c.Run.ImportPath = args[0]
c.Run.Mode = args[1]
c.Run.Port = args[2]
c.Run.Port = convertPort(args[2])
case 2:
// Possible combinations
// 1. revel run [import-path] [run-mode]
@@ -62,13 +65,13 @@ func updateRunConfig(c *model.CommandConfig, args []string) bool {
// 3. revel run [run-mode] [port]
// Check to see if the import path evaluates out to something that may be on a gopath
if _, err := build.Import(args[0], "", build.FindOnly); err == nil {
if runIsImportPath(args[0]) {
// 1st arg is the import path
c.Run.ImportPath = args[0]
if _, err := strconv.Atoi(args[1]); err == nil {
// 2nd arg is the port number
c.Run.Port = args[1]
c.Run.Port = convertPort(args[1])
} else {
// 2nd arg is the run mode
c.Run.Mode = args[1]
@@ -76,73 +79,84 @@ func updateRunConfig(c *model.CommandConfig, args []string) bool {
} else {
// 1st arg is the run mode
c.Run.Mode = args[0]
c.Run.Port = args[1]
c.Run.Port = convertPort(args[1])
}
case 1:
// Possible combinations
// 1. revel run [import-path]
// 2. revel run [port]
// 3. revel run [run-mode]
_, err := build.Import(args[0], "", build.FindOnly)
if err != nil {
utils.Logger.Warn("Unable to run using an import path, assuming import path is working directory %s %s", "Argument", args[0], "error", err.Error())
}
utils.Logger.Info("Trying to build with", args[0], err)
if err == nil {
if runIsImportPath(args[0]) {
// 1st arg is the import path
c.Run.ImportPath = args[0]
} else if _, err := strconv.Atoi(args[0]); err == nil {
// 1st arg is the port number
c.Run.Port = args[0]
c.Run.Port = convertPort(args[0])
} else {
// 1st arg is the run mode
c.Run.Mode = args[0]
}
case 0:
return false
// Attempt to set the import path to the current working director.
if c.Run.ImportPath == "" {
c.Run.ImportPath, _ = os.Getwd()
}
}
c.Index = model.RUN
return true
}
func runApp(c *model.CommandConfig) {
// Returns true if this is an absolute path or a relative gopath.
func runIsImportPath(pathToCheck string) bool {
return utils.DirExists(pathToCheck)
}
// Called to run the app.
func runApp(c *model.CommandConfig) (err error) {
if c.Run.Mode == "" {
c.Run.Mode = "dev"
}
revel_path := model.NewRevelPaths(c.Run.Mode, c.ImportPath, "", model.DoNothingRevelCallback)
if c.Run.Port != "" {
port, err := strconv.Atoi(c.Run.Port)
if err != nil {
utils.Logger.Fatalf("Failed to parse port as integer: %s", c.Run.Port)
}
revel_path.HTTPPort = port
revelPath, err := model.NewRevelPaths(c.Run.Mode, c.ImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
if err != nil {
return utils.NewBuildIfError(err, "Revel paths")
}
if c.Run.Port > -1 {
revelPath.HTTPPort = c.Run.Port
} else {
c.Run.Port = revelPath.HTTPPort
}
utils.Logger.Infof("Running %s (%s) in %s mode\n", revel_path.AppName, revel_path.ImportPath, revel_path.RunMode)
utils.Logger.Debug("Base path:", "path", revel_path.BasePath)
utils.Logger.Infof("Running %s (%s) in %s mode\n", revelPath.AppName, revelPath.ImportPath, revelPath.RunMode)
utils.Logger.Debug("Base path:", "path", revelPath.BasePath)
// If the app is run in "watched" mode, use the harness to run it.
if 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.")
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 {
runMode = revel_path.RunMode
runMode = revelPath.RunMode
}
// **** 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.
utils.Logger.Debug("Running in live build mode.")
app, err := harness.Build(c, revel_path)
app, err := harness.Build(c, revelPath)
if err != nil {
utils.Logger.Errorf("Failed to build app: %s", err)
}
app.Port = revel_path.HTTPPort
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v}`, app.Paths.RunMode, c.Verbose)
if c.HistoricMode {
runMode = revel_path.RunMode
app.Port = revelPath.HTTPPort
var paths []byte
if len(app.PackagePathMap) > 0 {
paths, _ = json.Marshal(app.PackagePathMap)
}
app.Cmd(runMode).Run()
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, app.Paths.RunMode, c.GetVerbose(), string(paths))
if c.HistoricMode {
runMode = revelPath.RunMode
}
app.Cmd(runMode).Run(c)
return
}

22
revel/run_test.go Normal file
View File

@@ -0,0 +1,22 @@
package main_test
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
// test the commands.
func TestRun(t *testing.T) {
a := assert.New(t)
gopath := setup("revel-test-run", a)
// TODO Testing run
if !t.Failed() {
if err := os.RemoveAll(gopath); err != nil {
a.Fail("Failed to remove test path")
}
}
}

View File

@@ -1,3 +0,0 @@
test-results/
tmp/
routes/

View File

@@ -1,43 +0,0 @@
# Welcome to Revel
A high-productivity web framework for the [Go language](http://www.golang.org/).
### Start the web server:
revel run myapp
### Go to http://localhost:9000/ and you'll see:
"It works"
## Code Layout
The directory structure of a generated Revel application:
conf/ Configuration directory
app.conf Main app configuration file
routes Routes definition file
app/ App sources
init.go Interceptor registration
controllers/ App controllers go here
views/ Templates directory
messages/ Message files
public/ Public static assets
css/ CSS files
js/ Javascript files
images/ Image files
tests/ Test suites
## Help
* The [Getting Started with Revel](http://revel.github.io/tutorial/gettingstarted.html).
* The [Revel guides](http://revel.github.io/manual/index.html).
* The [Revel sample apps](http://revel.github.io/examples/index.html).
* The [API documentation](https://godoc.org/github.com/revel/revel).

View File

@@ -1,13 +0,0 @@
package controllers
import (
"github.com/revel/revel"
)
type App struct {
*revel.Controller
}
func (c App) Index() revel.Result {
return c.Render()
}

View File

@@ -1,59 +0,0 @@
package app
import (
"github.com/revel/revel"
)
var (
// AppVersion revel app version (ldflags)
AppVersion string
// BuildTime revel app build-time (ldflags)
BuildTime string
)
func init() {
// Filters is the default set of global filters.
revel.Filters = []revel.Filter{
revel.PanicFilter, // Recover from panics and display an error page instead.
revel.RouterFilter, // Use the routing table to select the right Action
revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
revel.ParamsFilter, // Parse parameters into Controller.Params.
revel.SessionFilter, // Restore and write the session cookie.
revel.FlashFilter, // Restore and write the flash cookie.
revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
revel.I18nFilter, // Resolve the requested language
HeaderFilter, // Add some security based headers
revel.InterceptorFilter, // Run interceptors around the action.
revel.CompressFilter, // Compress the result.
revel.BeforeAfterFilter, // Call the before and after filter functions
revel.ActionInvoker, // Invoke the action.
}
// Register startup functions with OnAppStart
// revel.DevMode and revel.RunMode only work inside of OnAppStart. See Example Startup Script
// ( order dependent )
// revel.OnAppStart(ExampleStartupScript)
// revel.OnAppStart(InitDB)
// revel.OnAppStart(FillCache)
}
// HeaderFilter adds common security headers
// There is a full implementation of a CSRF filter in
// https://github.com/revel/modules/tree/master/csrf
var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) {
c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN")
c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block")
c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff")
c.Response.Out.Header().Add("Referrer-Policy", "strict-origin-when-cross-origin")
fc[0](c, fc[1:]) // Execute the next filter stage.
}
//func ExampleStartupScript() {
// // revel.DevMod and revel.RunMode work here
// // Use this script to check for dev mode and set dev/prod startup scripts here!
// if revel.DevMode == true {
// // Dev mode
// }
//}

View File

@@ -1,21 +0,0 @@
{{set . "title" "Home"}}
{{template "header.html" .}}
<header class="jumbotron" style="background-color:#A9F16C">
<div class="container">
<div class="row">
<h1>It works!</h1>
<p></p>
</div>
</div>
</header>
<div class="container">
<div class="row">
<div class="span6">
{{template "flash.html" .}}
</div>
</div>
</div>
{{template "footer.html" .}}

View File

@@ -1,64 +0,0 @@
<style type="text/css">
#sidebar {
position: absolute;
right: 0px;
top:69px;
max-width: 75%;
z-index: 1000;
background-color: #fee;
border: thin solid grey;
padding: 10px;
}
#toggleSidebar {
position: absolute;
right: 0px;
top: 50px;
background-color: #fee;
}
</style>
<div id="sidebar" style="display:none;">
<h4>Available pipelines</h4>
<dl>
{{ range $index, $value := .}}
<dt>{{$index}}</dt>
<dd>{{$value}}</dd>
{{end}}
</dl>
<h4>Flash</h4>
<dl>
{{ range $index, $value := .flash}}
<dt>{{$index}}</dt>
<dd>{{$value}}</dd>
{{end}}
</dl>
<h4>Errors</h4>
<dl>
{{ range $index, $value := .errors}}
<dt>{{$index}}</dt>
<dd>{{$value}}</dd>
{{end}}
</dl>
</div>
<a id="toggleSidebar" href="#" class="toggles"><i class="glyphicon glyphicon-chevron-left"></i></a>
<script>
$sidebar = 0;
$('#toggleSidebar').click(function() {
if ($sidebar === 1) {
$('#sidebar').hide();
$('#toggleSidebar i').addClass('glyphicon-chevron-left');
$('#toggleSidebar i').removeClass('glyphicon-chevron-right');
$sidebar = 0;
}
else {
$('#sidebar').show();
$('#toggleSidebar i').addClass('glyphicon-chevron-right');
$('#toggleSidebar i').removeClass('glyphicon-chevron-left');
$sidebar = 1;
}
return false;
});
</script>

View File

@@ -1,20 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Not found</title>
</head>
<body>
{{if eq .RunMode "dev"}}
{{template "errors/404-dev.html" .}}
{{else}}
{{with .Error}}
<h1>
{{.Title}}
</h1>
<p>
{{.Description}}
</p>
{{end}}
{{end}}
</body>
</html>

View File

@@ -1,16 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Application error</title>
</head>
<body>
{{if eq .RunMode "dev"}}
{{template "errors/500-dev.html" .}}
{{else}}
<h1>Oops, an error occured</h1>
<p>
This exception has been logged.
</p>
{{end}}
</body>
</html>

View File

@@ -1,18 +0,0 @@
{{if .flash.success}}
<div class="alert alert-success">
{{.flash.success}}
</div>
{{end}}
{{if or .errors .flash.error}}
<div class="alert alert-danger">
{{if .flash.error}}
{{.flash.error}}
{{end}}
<ul style="margin-top:10px;">
{{range .errors}}
<li>{{.}}</li>
{{end}}
</ul>
</div>
{{end}}

View File

@@ -1,5 +0,0 @@
{{if .DevMode}}
{{template "debug.html" .}}
{{end}}
</body>
</html>

View File

@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>{{.title}}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/public/css/bootstrap-3.3.6.min.css">
<link rel="shortcut icon" type="image/png" href="/public/img/favicon.png">
<script src="/public/js/jquery-2.2.4.min.js"></script>
<script src="/public/js/bootstrap-3.3.6.min.js"></script>
{{range .moreStyles}}
<link rel="stylesheet" type="text/css" href="/public/{{.}}">
{{end}}
{{range .moreScripts}}
<script src="/public/{{.}}" type="text/javascript" charset="utf-8"></script>
{{end}}
</head>
<body>

View File

@@ -1,223 +0,0 @@
################################################################################
# Revel configuration file
# More info at http://revel.github.io/manual/appconf.html
################################################################################
# Revel build section
# This section contains values that are not reloadable
################################################################################
# Comma delimited list of folders that are included with the package, or build commands
# If you want to not include folders within these ones prefix the folder with a . to make it hidden
package.folders = conf, public, app/views
# Revel reconfigurable section
#
################################################################################
# Sets `revel.AppName` for use in-app.
# Example:
# `if revel.AppName {...}`
app.name = {{ .AppName }}
# A secret string which is passed to cryptographically sign the cookie to prevent
# (and detect) user modification.
# Keep this string secret or users will be able to inject arbitrary cookie values
# into your application
app.secret = {{ .Secret }}
# Revel running behind proxy like nginx, haproxy, etc.
app.behind.proxy = false
# The IP address on which to listen.
http.addr =
# The port on which to listen.
http.port = 9000
# Whether to use SSL or not.
http.ssl = false
# Path to an X509 certificate file, if using SSL.
#http.sslcert =
# Path to an X509 certificate key, if using SSL.
#http.sslkey =
# Timeout specifies a time limit for request (in seconds) made by a single client.
# A Timeout of zero means no timeout.
http.timeout.read = 90
http.timeout.write = 60
# For any cookies set by Revel (Session,Flash,Error) these properties will set
# the fields of:
# http://golang.org/pkg/net/http/#Cookie
#
# Each cookie set by Revel is prefixed with this string.
cookie.prefix = REVEL
# A secure cookie has the secure attribute enabled and is only used via HTTPS,
# ensuring that the cookie is always encrypted when transmitting from client to
# server. This makes the cookie less likely to be exposed to cookie theft via
# eavesdropping.
#
# Defaults to false. If 'http.ssl' is enabled, this will be defaulted to true.
# This should only be true when Revel is handling SSL connections. If you are
# using a proxy in front of revel (Nginx, Apache, etc), then this should be left
# as false.
# cookie.secure = false
# Limit cookie access to a given domain.
#cookie.domain =
# Define when your session cookie expires.
# Values:
# "720h"
# A time duration (http://golang.org/pkg/time/#ParseDuration) after which
# the cookie expires and the session is invalid.
# "session"
# Sets a session cookie which invalidates the session when the user close
# the browser.
session.expires = 720h
# The date format used by Revel. Possible formats defined by the Go `time`
# package (http://golang.org/pkg/time/#Parse)
format.date = 2006-01-02
format.datetime = 2006-01-02 15:04
# Determines whether the template rendering should use chunked encoding.
# Chunked encoding can decrease the time to first byte on the client side by
# sending data before the entire template has been fully rendered.
results.chunked = false
# The default language of this application.
i18n.default_language = en
# The default format when message is missing.
# The original message shows in %s
#i18n.unknown_format = "??? %s ???"
# Module to serve static content such as CSS, JavaScript and Media files
# Allows Routes like this:
# `Static.ServeModule("modulename","public")`
module.static = github.com/revel/modules/static
################################################################################
# Section: dev
# This section is evaluated when running Revel in dev mode. Like so:
# `revel run path/to/myapp`
[dev]
# This sets `revel.DevMode` for use in-app.
# Example:
# `if revel.DevMode {...}`
# or in your templates with
# `{{.DevMode}}`
# Values:
# "true"
# Sets `DevMode` to `true`.
# "false"
# Sets `DevMode` to `false`.
mode.dev = true
# Pretty print JSON/XML when calling RenderJSON/RenderXML
# Values:
# "true"
# Enables pretty printing.
# "false"
# Disables pretty printing.
results.pretty = true
# Watch your applicaton files for changes and automatically rebuild
# Values:
# "true"
# Enables auto rebuilding.
# "false"
# Disables auto rebuilding.
watch = true
# Define when to rebuild new changes.
# Values:
# "normal"
# Rebuild when a new request is received and changes have been detected.
# "eager"
# Rebuild as soon as changes are detected.
watch.mode = eager
# Watch the entire `$GOPATH` for changes.
# Values:
# "true"
# Includes `$GOPATH` in watch path.
# "false"
# Excludes `$GOPATH` from watch path. Default value.
#watch.gopath = true
# Module to run code tests in the browser
# See:
# http://revel.github.io/manual/testing.html
module.testrunner = github.com/revel/modules/testrunner
# Where to log the various Revel logs
# Values:
# "off"
# Disable log output.
# "stdout"
# Log to OS's standard output.
# "stderr"
# Log to Os's standard error output. Default value.
# "relative/path/to/log"
# Log to file.
log.all.filter.module.app = stdout # Log all loggers for the application to the stdout
log.error.nfilter.module.app = stderr # Everything else that logs an error to stderr
log.crit.output = stderr # Everything that logs something as critical goes to this
# Revel request access log
# Access log line format:
# INFO 21:53:55 static server-engine.go:169: Request Stats ip=127.0.0.1 path=/public/vendors/datatables.net-buttons/js/buttons.html5.min.js method=GET start=2017/08/31 21:53:55 status=200 duration_seconds=0.0002583 section=requestlog
log.request.output = stdout
################################################################################
# Section: prod
# This section is evaluated when running Revel in production mode. Like so:
# `revel run path/to/myapp prod`
# See:
# [dev] section for documentation of the various settings
[prod]
mode.dev = false
results.pretty = false
watch = false
module.testrunner =
log.warn.output = log/%(app.name)s-warn.json # Log all warn messages to file
log.error.output = log/%(app.name)s-error.json # Log all errors to file
log.crit.output = log/%(app.name)s-critical.json # Log all critical to file
# Revel request access log (json format)
# Example:
# log.request.output = %(app.name)s-request.json
log.request.output = log/%(app.name)s-requests.json

View File

@@ -1,26 +0,0 @@
# Routes Config
#
# This file defines all application routes (Higher priority routes first)
#
module:testrunner
# module:jobs
GET / App.Index
# Ignore favicon requests
GET /favicon.ico 404
# Map static resources from the /app/public folder to the /public path
GET /public/*filepath Static.Serve("public")
# Catch all, this will route any request into the controller path
#
# **** WARNING ****
# Enabling this exposes any controller and function to the web.
# ** This is a serious security issue if used online **
#
# For rapid development uncomment the following to add new controller.action endpoints
# without having to add them to the routes table.
# * /:controller/:action :controller.:action

View File

@@ -1,8 +0,0 @@
# Sample messages file for the English language (en)
# Message file extensions should be ISO 639-1 codes (http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
# Sections within each message file can optionally override the defaults using ISO 3166-1 alpha-2 codes (http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
# See also:
# - http://www.rfc-editor.org/rfc/bcp/bcp47.txt
# - http://www.w3.org/International/questions/qa-accept-lang-locales
[DEFAULT]

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,23 +0,0 @@
package tests
import (
"github.com/revel/revel/testing"
)
type AppTest struct {
testing.TestSuite
}
func (t *AppTest) Before() {
println("Set up")
}
func (t *AppTest) TestThatIndexPageWorks() {
t.Get("/")
t.AssertOk()
t.AssertContentType("text/html; charset=utf-8")
}
func (t *AppTest) After() {
println("Tear down")
}

View File

@@ -22,7 +22,7 @@ import (
)
var cmdTest = &Command{
UsageLine: "test [import path] [run mode] [suite.method]",
UsageLine: "test <import path> [<run mode> <suite.method>]",
Short: "run all tests from the command-line",
Long: `
Run all tests for the Revel app named by the given import path.
@@ -52,9 +52,13 @@ func init() {
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 {
c.Index = model.TEST
if len(args) == 0 && c.Test.ImportPath != "" {
return true
}
// The full test runs
// revel test <import path> (run mode) (suite(.function))
if len(args) < 1 {
@@ -70,69 +74,74 @@ func updateTestConfig(c *model.CommandConfig, args []string) bool {
return true
}
// Called to test the application
func testApp(c *model.CommandConfig) {
var err error
// Called to test the application.
func testApp(c *model.CommandConfig) (err error) {
mode := DefaultRunMode
if c.Test.Mode != "" {
mode = c.Test.Mode
}
// Find and parse app.conf
revel_path := model.NewRevelPaths(mode, c.ImportPath, "", model.DoNothingRevelCallback)
revelPath, err := model.NewRevelPaths(mode, c.ImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
if err != nil {
return
}
// Ensure that the testrunner is loaded in this mode.
// todo checkTestRunner()
// todo Ensure that the testrunner is loaded in this mode.
// 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 {
utils.Logger.Errorf("Failed to remove test result directory %s: %s", resultPath, err)
return utils.NewBuildError("Failed to remove test result directory ", "path", resultPath, "error", err)
}
if err = os.Mkdir(resultPath, 0777); err != nil {
utils.Logger.Errorf("Failed to create test result directory %s: %s", resultPath, err)
return utils.NewBuildError("Failed to create test result directory ", "path", resultPath, "error", err)
}
// Direct all the output into a file in the test-results directory.
file, err := os.OpenFile(filepath.Join(resultPath, "app.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
utils.Logger.Errorf("Failed to create test result log file: %s", err)
return utils.NewBuildError("Failed to create test result log file: ", "error", err)
}
app, reverr := harness.Build(c, revel_path)
app, reverr := harness.Build(c, revelPath)
if reverr != nil {
utils.Logger.Errorf("Error building: %s", reverr)
return utils.NewBuildIfError(reverr, "Error building: ")
}
runMode := fmt.Sprintf(`{"mode":"%s","testModeFlag":true, "specialUseFlag":%v}`, app.Paths.RunMode, c.Verbose)
var paths []byte
if len(app.PackagePathMap) > 0 {
paths, _ = json.Marshal(app.PackagePathMap)
}
runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, app.Paths.RunMode, c.GetVerbose(), string(paths))
if c.HistoricMode {
runMode = app.Paths.RunMode
}
cmd := app.Cmd(runMode)
cmd.Dir = c.AppPath
cmd.Stderr = io.MultiWriter(cmd.Stderr, file)
cmd.Stdout = io.MultiWriter(cmd.Stderr, file)
// Start the app...
if err := cmd.Start(c); err != nil {
utils.Logger.Errorf("%s", err)
return utils.NewBuildError("Unable to start server", "error", err)
}
defer cmd.Kill()
var httpAddr = revel_path.HTTPAddr
httpAddr := revelPath.HTTPAddr
if httpAddr == "" {
httpAddr = "localhost"
}
var httpProto = "http"
if revel_path.HTTPSsl {
httpProto := "http"
if revelPath.HTTPSsl {
httpProto = "https"
}
// 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)
// If a specific TestSuite[.Method] is specified, only run that suite/test
@@ -145,7 +154,7 @@ func testApp(c *model.CommandConfig) {
fmt.Println()
// Run each suite.
failedResults, overallSuccess := runTestSuites(revel_path, baseURL, resultPath, testSuites)
failedResults, overallSuccess := runTestSuites(revelPath, baseURL, resultPath, testSuites)
fmt.Println()
if overallSuccess {
@@ -164,16 +173,18 @@ func testApp(c *model.CommandConfig) {
writeResultFile(resultPath, "result.failed", "failed")
utils.Logger.Errorf("Some tests failed. See file://%s for results.", resultPath)
}
return
}
// Outputs the results to a file
// Outputs the results to a file.
func writeResultFile(resultPath, name, content string) {
if err := ioutil.WriteFile(filepath.Join(resultPath, name), []byte(content), 0666); err != nil {
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 {
if num == 1 {
return singular
@@ -182,7 +193,7 @@ func pluralize(num int, singular, plural string) string {
}
// 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 {
var suiteName, testName string
argArray := strings.Split(suiteArgument, ".")
@@ -252,11 +263,10 @@ func getTestsList(baseURL string) (*[]tests.TestSuiteDesc, error) {
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) {
// We can determine the testsuite location by finding the test module and extracting the data from it
resultFilePath := filepath.Join(paths.ModulePathMap["testrunner"], "app", "views", "TestRunner/SuiteResult.html")
resultFilePath := filepath.Join(paths.ModulePathMap["testrunner"].Path, "app", "views", "TestRunner/SuiteResult.html")
var (
overallSuccess = true
@@ -287,7 +297,7 @@ func runTestSuites(paths *model.RevelContainer, baseURL, resultPath string, test
err = json.NewDecoder(resp.Body).Decode(&testResult)
if err == nil && !testResult.Passed {
suiteResult.Passed = false
utils.Logger.Error("Test Failed","suite", suite.Name, "test", test.Name)
utils.Logger.Error("Test Failed", "suite", suite.Name, "test", test.Name)
fmt.Printf(" %s.%s : FAILED\n", suite.Name, test.Name)
} else {
fmt.Printf(" %s.%s : PASSED\n", suite.Name, test.Name)
@@ -306,7 +316,9 @@ func runTestSuites(paths *model.RevelContainer, baseURL, resultPath string, test
// Create the result HTML file.
suiteResultFilename := filepath.Join(resultPath,
fmt.Sprintf("%s.%s.html", suite.Name, strings.ToLower(suiteResultStr)))
utils.MustRenderTemplate(suiteResultFilename, resultFilePath, suiteResult)
if err := utils.RenderTemplate(suiteResultFilename, resultFilePath, suiteResult); err != nil {
utils.Logger.Error("Failed to render template", "error", err)
}
}
return &failedResults, overallSuccess

31
revel/test_test.go Normal file
View File

@@ -0,0 +1,31 @@
package main_test
import (
"os"
"testing"
"github.com/revel/cmd/model"
main "github.com/revel/cmd/revel"
"github.com/stretchr/testify/assert"
)
// test the commands.
func TestRevelTest(t *testing.T) {
a := assert.New(t)
gopath := setup("revel-test-test", a)
t.Run("Test", func(t *testing.T) {
a := assert.New(t)
c := newApp("test-test", model.NEW, nil, a)
a.Nil(main.Commands[model.NEW].RunWith(c), "Failed to run test-test")
c.Index = model.TEST
c.Test.ImportPath = c.ImportPath
a.Nil(main.Commands[model.TEST].RunWith(c), "Failed to run test-test")
})
if !t.Failed() {
if err := os.RemoveAll(gopath); err != nil {
a.Fail("Failed to remove test path")
}
}
}

View File

@@ -9,92 +9,229 @@
package main
import (
"bytes"
"fmt"
"runtime"
"github.com/revel/cmd/model"
"go/build"
"go/token"
"go/parser"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"github.com/revel/cmd/utils"
"strings"
"github.com/revel/cmd"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
)
type (
// The version container.
VersionCommand struct {
Command *model.CommandConfig // The command
revelVersion *model.Version // The Revel framework version
modulesVersion *model.Version // The Revel modules version
cmdVersion *model.Version // The tool version
}
)
var cmdVersion = &Command{
UsageLine: "version",
UsageLine: "revel version",
Short: "displays the Revel Framework and Go version",
Long: `
Displays the Revel Framework and Go version.
For example:
revel version
revel version [<application path>]
`,
}
func init() {
cmdVersion.RunWith = versionApp
v := &VersionCommand{}
cmdVersion.UpdateConfig = v.UpdateConfig
cmdVersion.RunWith = v.RunWith
}
// Displays the version of go and Revel
func versionApp(c *model.CommandConfig) {
// Update the version.
func (v *VersionCommand) UpdateConfig(c *model.CommandConfig, args []string) bool {
if len(args) > 0 {
c.Version.ImportPath = args[0]
}
return true
}
// Displays the version of go and Revel.
func (v *VersionCommand) RunWith(c *model.CommandConfig) (err error) {
utils.Logger.Info("Requesting version information", "config", c)
v.Command = c
// Update the versions with the local values
v.updateLocalVersions()
needsUpdates := true
versionInfo := ""
for x := 0; x < 2 && needsUpdates; x++ {
versionInfo, needsUpdates = v.doRepoCheck(x == 0)
}
fmt.Printf("%s\n\nGo Location:%s\n\n", versionInfo, c.GoCmd)
cmd := exec.Command(c.GoCmd, "version")
cmd.Stdout = os.Stdout
if e := cmd.Start(); e != nil {
fmt.Println("Go command error ", e)
} else {
if err = cmd.Wait(); err != nil {
return
}
}
return
}
// Checks the Revel repos for the latest version.
func (v *VersionCommand) doRepoCheck(updateLibs bool) (versionInfo string, needsUpdate bool) {
var (
revelPkg *build.Package
err error
title string
localVersion *model.Version
)
if len(c.ImportPath)>0 {
appPkg, err := build.Import(c.ImportPath, "", build.FindOnly)
if err != nil {
utils.Logger.Fatal("Failed to import " + c.ImportPath + " with error:", "error", err)
}
revelPkg, err = build.Import(model.RevelImportPath, appPkg.Dir, build.FindOnly)
} else {
revelPkg, err = build.Import(model.RevelImportPath, "" , build.FindOnly)
}
fmt.Println("\nRevel Framework")
if err != nil {
utils.Logger.Info("Failed to find Revel in GOPATH with error:", "error", err, "gopath", build.Default.GOPATH)
fmt.Println("Information not available (not on GOPATH)")
} else {
utils.Logger.Info("Fullpath to revel", revelPkg.Dir)
fset := token.NewFileSet() // positions are relative to fset
version, err := ioutil.ReadFile(filepath.Join(revelPkg.Dir, "version.go"))
for _, repo := range []string{"revel", "cmd", "modules"} {
versonFromRepo, err := v.versionFromRepo(repo, "", "version.go")
if err != nil {
utils.Logger.Errorf("Failed to find Revel version:", "error", err)
utils.Logger.Info("Failed to get version from repo", "repo", repo, "error", err)
}
switch repo {
case "revel":
title, repo, localVersion = "Revel Framework", "github.com/revel/revel", v.revelVersion
case "cmd":
title, repo, localVersion = "Revel Cmd", "github.com/revel/cmd/revel", v.cmdVersion
case "modules":
title, repo, localVersion = "Revel Modules", "github.com/revel/modules", v.modulesVersion
}
// Parse src but stop after processing the imports.
f, err := parser.ParseFile(fset, "", version, parser.ParseComments)
if err != nil {
utils.Logger.Errorf("Failed to parse Revel version error:", "error", err)
}
// Print the imports from the file's AST.
for _, s := range f.Decls {
genDecl, ok := s.(*ast.GenDecl)
if !ok {
continue
}
if genDecl.Tok != token.CONST {
continue
}
for _, a := range genDecl.Specs {
spec := a.(*ast.ValueSpec)
r := spec.Values[0].(*ast.BasicLit)
fmt.Printf("Revel %s = %s\n", spec.Names[0].Name, r.Value)
}
}
// Only do an update on the first loop, and if specified to update
versionInfo += v.outputVersion(title, repo, localVersion, versonFromRepo)
}
return
}
// Prints out the local and remote versions, calls update if needed.
func (v *VersionCommand) outputVersion(title, repo string, local, remote *model.Version) (output string) {
buffer := &bytes.Buffer{}
remoteVersion := "Unknown"
if remote != nil {
remoteVersion = remote.VersionString()
}
localVersion := "Unknown"
if local != nil {
localVersion = local.VersionString()
}
fmt.Fprintf(buffer, "%s\t:\t%s\t(%s remote master branch)\n", title, localVersion, remoteVersion)
return buffer.String()
}
// Returns the version from the repository.
func (v *VersionCommand) versionFromRepo(repoName, branchName, fileName string) (version *model.Version, err error) {
if branchName == "" {
branchName = "master"
}
// Try to download the version of file from the repo, just use an http connection to retrieve the source
// Assuming that the repo is github
fullurl := "https://raw.githubusercontent.com/revel/" + repoName + "/" + branchName + "/" + fileName
resp, err := http.Get(fullurl)
if err != nil {
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
utils.Logger.Info("Got version file", "from", fullurl, "content", string(body))
return v.versionFromBytes(body)
}
func (v *VersionCommand) versionFromFilepath(sourcePath string) (version *model.Version, err error) {
utils.Logger.Info("Fullpath to revel", "dir", sourcePath)
sourceStream, err := ioutil.ReadFile(filepath.Join(sourcePath, "version.go"))
if err != nil {
return
}
return v.versionFromBytes(sourceStream)
}
// Returns version information from a file called version on the gopath.
func (v *VersionCommand) versionFromBytes(sourceStream []byte) (version *model.Version, err error) {
fset := token.NewFileSet() // positions are relative to fset
// Parse src but stop after processing the imports.
f, err := parser.ParseFile(fset, "", sourceStream, parser.ParseComments)
if err != nil {
err = utils.NewBuildError("Failed to parse Revel version error:", "error", err)
return
}
version = &model.Version{}
// Print the imports from the file's AST.
for _, s := range f.Decls {
genDecl, ok := s.(*ast.GenDecl)
if !ok {
continue
}
if genDecl.Tok != token.CONST {
continue
}
for _, a := range genDecl.Specs {
spec := a.(*ast.ValueSpec)
r := spec.Values[0].(*ast.BasicLit)
switch spec.Names[0].Name {
case "Version":
if err = version.ParseVersion(strings.ReplaceAll(r.Value, `"`, "")); err != nil {
return
}
case "BuildDate":
version.BuildDate = r.Value
case "MinimumGoVersion":
version.MinGoVersion = r.Value
}
}
}
return
}
// Fetch the local version of revel from the file system.
func (v *VersionCommand) updateLocalVersions() {
v.cmdVersion = &model.Version{}
if err := v.cmdVersion.ParseVersion(cmd.Version); err != nil {
utils.Logger.Warn("Error parsing version", "error", err, "version", cmd.Version)
return
}
v.cmdVersion.BuildDate = cmd.BuildDate
v.cmdVersion.MinGoVersion = cmd.MinimumGoVersion
if v.Command.Version.ImportPath == "" {
return
}
pathMap, err := utils.FindSrcPaths(v.Command.AppPath, []string{model.RevelImportPath, model.RevelModulesImportPath}, v.Command.PackageResolver)
if err != nil {
utils.Logger.Warn("Unable to extract version information from Revel library", "path", pathMap[model.RevelImportPath], "error", err)
return
}
utils.Logger.Info("Fullpath to revel modules", "dir", pathMap[model.RevelImportPath])
v.revelVersion, err = v.versionFromFilepath(pathMap[model.RevelImportPath])
if err != nil {
utils.Logger.Warn("Unable to extract version information from Revel", "error,err")
}
v.modulesVersion, err = v.versionFromFilepath(pathMap[model.RevelModulesImportPath])
if err != nil {
utils.Logger.Warn("Unable to extract version information from Revel Modules", "path", pathMap[model.RevelModulesImportPath], "error", err)
}
fmt.Println("\nRevel Command Utility Tool")
fmt.Println("Version", cmd.Version)
fmt.Println("Build Date", cmd.BuildDate)
fmt.Println("Minimum Go Version", cmd.MinimumGoVersion)
fmt.Printf("\n %s %s/%s\n\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
}

44
revel/version_test.go Normal file
View File

@@ -0,0 +1,44 @@
package main_test
import (
"errors"
"os"
"path/filepath"
"testing"
"github.com/revel/cmd/model"
main "github.com/revel/cmd/revel"
"github.com/stretchr/testify/assert"
)
// test the commands.
func TestVersion(t *testing.T) {
a := assert.New(t)
gopath := setup("revel-test-version", a)
t.Run("Version", func(t *testing.T) {
a := assert.New(t)
c := newApp("version-test", model.NEW, nil, a)
a.Nil(main.Commands[model.NEW].RunWith(c), "Check new")
c.Build.ImportPath = c.ImportPath
c.Build.TargetPath = filepath.Join(gopath, "build-test", "target")
a.Nil(main.Commands[model.BUILD].RunWith(c), "Failed to run build")
c.Index = model.VERSION
c.Version.ImportPath = c.ImportPath
a.Nil(main.Commands[model.VERSION].RunWith(c), "Failed to run version-test")
})
t.Run("Version-Nobuild", func(t *testing.T) {
a := assert.New(t)
c := newApp("version-test2", model.NEW, nil, a)
a.Nil(main.Commands[model.NEW].RunWith(c), "Check new")
c.Index = model.VERSION
c.Version.ImportPath = c.ImportPath
a.Nil(main.Commands[model.VERSION].RunWith(c), "Failed to run version-test")
})
if !t.Failed() {
if err := os.RemoveAll(gopath); err != nil && !errors.Is(err, os.ErrNotExist) {
a.Fail("Failed to remove test path", err.Error())
}
}
}

View File

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

107
utils/build_error.go Normal file
View File

@@ -0,0 +1,107 @@
package utils
import (
"errors"
"fmt"
"regexp"
"strconv"
"github.com/revel/cmd/logger"
)
type (
BuildError struct {
Stack interface{}
Message string
Args []interface{}
}
)
// Returns a new builed error.
func NewBuildError(message string, args ...interface{}) (b *BuildError) {
Logger.Info(message, args...)
b = &BuildError{}
b.Message = message
b.Args = args
b.Stack = logger.NewCallStack()
Logger.Info("Stack", "stack", b.Stack)
return b
}
// Returns a new BuildError if err is not nil.
func NewBuildIfError(err error, message string, args ...interface{}) (b error) {
if err != nil {
var berr *BuildError
if errors.As(err, &berr) {
// This is already a build error so just append the args
berr.Args = append(berr.Args, args...)
return berr
}
args = append(args, "error", err.Error())
b = NewBuildError(message, args...)
}
return
}
// BuildError implements Error() string.
func (b *BuildError) Error() string {
return fmt.Sprint(b.Message, b.Args)
}
// Parse the output of the "go build" command.
// Return a detailed Error.
func NewCompileError(importPath, errorLink string, err error) *SourceError {
// Get the stack from the error
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
FindSubmatch([]byte(err.Error()))
if errorMatch == nil {
errorMatch = regexp.MustCompile(`(?m)^(.*?):(\d+):\s(.*?)$`).FindSubmatch([]byte(err.Error()))
if errorMatch == nil {
Logger.Error("Failed to parse build errors", "error", err)
return &SourceError{
SourceType: "Go code",
Title: "Go Compilation Error",
Description: "See console for build error.",
}
}
errorMatch = append(errorMatch, errorMatch[3])
Logger.Error("Build errors", "errors", err)
}
// Read the source for the offending file.
var (
relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go"
absFilename = relFilename
line, _ = strconv.Atoi(string(errorMatch[2]))
description = string(errorMatch[4])
compileError = &SourceError{
SourceType: "Go code",
Title: "Go Compilation Error",
Path: relFilename,
Description: description,
Line: line,
}
)
// errorLink := paths.Config.StringDefault("error.link", "")
if errorLink != "" {
compileError.SetLink(errorLink)
}
fileStr, err := ReadLines(absFilename)
if err != nil {
compileError.MetaError = absFilename + ": " + err.Error()
Logger.Info("Unable to readlines "+compileError.MetaError, "error", err)
return compileError
}
compileError.SourceLines = fileStr
return compileError
}

49
utils/command.go Normal file
View File

@@ -0,0 +1,49 @@
package utils
import (
"bytes"
"go/build"
"os"
"os/exec"
"path/filepath"
"strings"
)
// Initialize the command based on the GO environment.
func CmdInit(c *exec.Cmd, addGoPath bool, basePath string) {
c.Dir = basePath
// Dep does not like paths that are not real, convert all paths in go to real paths
// Fetch the rest of the env variables
c.Env = ReducedEnv(addGoPath)
}
// ReducedEnv returns a list of environment vairables by using os.Env
// it will remove the GOPATH, GOROOT if addGoPath is true
func ReducedEnv(addGoPath bool) []string {
realPath := &bytes.Buffer{}
env := []string{}
if addGoPath {
for _, p := range filepath.SplitList(build.Default.GOPATH) {
rp, _ := filepath.EvalSymlinks(p)
if realPath.Len() > 0 {
realPath.WriteString(string(filepath.ListSeparator))
}
realPath.WriteString(rp)
}
// Go 1.8 fails if we do not include the GOROOT
env = []string{"GOPATH=" + realPath.String(), "GOROOT=" + os.Getenv("GOROOT")}
}
for _, e := range os.Environ() {
pair := strings.Split(e, "=")
// Always exclude gomodcache
if pair[0] == "GOMODCACHE" {
continue
} else if !addGoPath && (pair[0] == "GOPATH" || pair[0] == "GOROOT") {
continue
}
env = append(env, e)
}
return env
}

View File

@@ -6,29 +6,46 @@ import (
"strings"
)
// The error is a wrapper for the
type Error struct {
SourceType string // The type of source that failed to build.
Title, Path, Description string // Description of the error, as presented to the user.
Line, Column int // Where the error was encountered.
SourceLines []string // The entire source file, split into lines.
Stack string // The raw stack trace string from debug.Stack().
MetaError string // Error that occurred producing the error page.
Link string // A configurable link to wrap the error source in
// The error is a wrapper for the.
type (
SourceError struct {
SourceType string // The type of source that failed to build.
Title, Path, Description string // Description of the error, as presented to the user.
Line, Column int // Where the error was encountered.
SourceLines []string // The entire source file, split into lines.
Stack string // The raw stack trace string from debug.Stack().
MetaError string // Error that occurred producing the error page.
Link string // A configurable link to wrap the error source in
}
SourceLine struct {
Source string
Line int
IsError bool
}
)
// Return a new error object.
func NewError(source, title, path, description string) *SourceError {
return &SourceError{
SourceType: source,
Title: title,
Path: path,
Description: description,
}
}
// Creates a link based on the configuration setting "errors.link"
func (e *Error) SetLink(errorLink string) {
errorLink = strings.Replace(errorLink, "{{Path}}", e.Path, -1)
errorLink = strings.Replace(errorLink, "{{Line}}", strconv.Itoa(e.Line), -1)
// Creates a link based on the configuration setting "errors.link".
func (e *SourceError) SetLink(errorLink string) {
errorLink = strings.ReplaceAll(errorLink, "{{Path}}", e.Path)
errorLink = strings.ReplaceAll(errorLink, "{{Line}}", strconv.Itoa(e.Line))
e.Link = "<a href=" + errorLink + ">" + e.Path + ":" + strconv.Itoa(e.Line) + "</a>"
}
// Error method constructs a plaintext version of the error, taking
// account that fields are optionally set. Returns e.g. Compilation Error
// (in views/header.html:51): expected right delim in end; got "}"
func (e *Error) Error() string {
// (in views/header.html:51): expected right delim in end; got "}".
func (e *SourceError) Error() string {
if e == nil {
panic("opps")
}
@@ -50,9 +67,10 @@ func (e *Error) Error() string {
}
return fmt.Sprintf("%s%s", header, e.Description)
}
// ContextSource method returns a snippet of the source around
// where the error occurred.
func (e *Error) ContextSource() []SourceLine {
func (e *SourceError) ContextSource() []SourceLine {
if e.SourceLines == nil {
return nil
}
@@ -72,10 +90,3 @@ func (e *Error) ContextSource() []SourceLine {
}
return lines
}
// SourceLine structure to hold the per-source-line details.
type SourceLine struct {
Source string
Line int
IsError bool
}

View File

@@ -1,21 +1,22 @@
package utils
// DirExists returns true if the given path exists and is a directory.
import (
"os"
"archive/tar"
"strings"
"io"
"path/filepath"
"bytes"
"compress/gzip"
"errors"
"fmt"
"html/template"
"compress/gzip"
"go/build"
"io"
"io/ioutil"
"bytes"
"os"
"path/filepath"
"strings"
"golang.org/x/tools/go/packages"
)
// DirExists returns true if the given path exists and is a directory.
func DirExists(filename string) bool {
fileInfo, err := os.Stat(filename)
return err == nil && fileInfo.IsDir()
@@ -39,49 +40,58 @@ func ReadLines(filename string) ([]string, error) {
return strings.Split(string(dataBytes), "\n"), nil
}
func MustCopyFile(destFilename, srcFilename string) {
// Copy file returns error.
func CopyFile(destFilename, srcFilename string) (err error) {
destFile, err := os.Create(destFilename)
PanicOnError(err, "Failed to create file "+destFilename)
if err != nil {
return NewBuildIfError(err, "Failed to create file", "file", destFilename)
}
srcFile, err := os.Open(srcFilename)
PanicOnError(err, "Failed to open file "+srcFilename)
if err != nil {
return NewBuildIfError(err, "Failed to open file", "file", srcFilename)
}
_, err = io.Copy(destFile, srcFile)
PanicOnError(err,
fmt.Sprintf("Failed to copy data from %s to %s", srcFile.Name(), destFile.Name()))
if err != nil {
return NewBuildIfError(err, "Failed to copy data", "fromfile", srcFilename, "tofile", destFilename)
}
err = destFile.Close()
PanicOnError(err, "Failed to close file "+destFile.Name())
if err != nil {
return NewBuildIfError(err, "Failed to close file", "file", destFilename)
}
err = srcFile.Close()
PanicOnError(err, "Failed to close file "+srcFile.Name())
}
if err != nil {
return NewBuildIfError(err, "Failed to close file", "file", srcFilename)
}
return
}
// GenerateTemplate renders the given template to produce source code, which it writes
// to the given file.
func MustGenerateTemplate(filename, templateSource string, args map[string]interface{}) (err error) {
func GenerateTemplate(filename, templateSource string, args map[string]interface{}) (err error) {
tmpl := template.Must(template.New("").Parse(templateSource))
var b bytes.Buffer
if err = tmpl.Execute(&b, args); err != nil {
Logger.Fatal("ExecuteTemplate: Execute failed", "error", err)
return
return NewBuildIfError(err, "ExecuteTemplate: Execute failed")
}
sourceCode := b.String()
filePath := filepath.Dir(filename)
if !DirExists(filePath) {
err = os.MkdirAll(filePath, 0777)
if err != nil && !os.IsExist(err) {
Logger.Fatal("Failed to make directory","dir", filePath, "error", err)
return NewBuildIfError(err, "Failed to make directory", "dir", filePath)
}
}
// Create the file
file, err := os.Create(filename)
if err != nil {
Logger.Fatal("Failed to create file","error", err)
Logger.Fatal("Failed to create file", "error", err)
return
}
defer func() {
@@ -95,28 +105,42 @@ func MustGenerateTemplate(filename, templateSource string, args map[string]inter
return
}
// Given the target path and source path and data. A template
func MustRenderTemplate(destPath, srcPath string, data interface{}) {
// Given the target path and source path and data. A template.
func RenderTemplate(destPath, srcPath string, data interface{}) (err error) {
tmpl, err := template.ParseFiles(srcPath)
PanicOnError(err, "Failed to parse template "+srcPath)
if err != nil {
return NewBuildIfError(err, "Failed to parse template "+srcPath)
}
f, err := os.Create(destPath)
PanicOnError(err, "Failed to create "+destPath)
if err != nil {
return NewBuildIfError(err, "Failed to create ", "path", destPath)
}
err = tmpl.Execute(f, data)
PanicOnError(err, "Failed to render template "+srcPath)
if err != nil {
return NewBuildIfError(err, "Failed to Render template "+srcPath)
}
err = f.Close()
PanicOnError(err, "Failed to close "+f.Name())
if err != nil {
return NewBuildIfError(err, "Failed to close file stream "+destPath)
}
return
}
// Given the target path and source path and data. A template
func MustRenderTemplateToStream(output io.Writer, srcPath []string, data interface{}) {
// Given the target path and source path and data. A template.
func RenderTemplateToStream(output io.Writer, srcPath []string, data interface{}) (err error) {
tmpl, err := template.ParseFiles(srcPath...)
PanicOnError(err, "Failed to parse template "+srcPath[0])
if err != nil {
return NewBuildIfError(err, "Failed to parse template "+srcPath[0])
}
err = tmpl.Execute(output, data)
PanicOnError(err, "Failed to render template "+srcPath[0])
if err != nil {
return NewBuildIfError(err, "Failed to render template "+srcPath[0])
}
return
}
func MustChmod(filename string, mode os.FileMode) {
@@ -124,11 +148,11 @@ func MustChmod(filename string, mode os.FileMode) {
PanicOnError(err, fmt.Sprintf("Failed to chmod %d %q", mode, filename))
}
// Called if panic
// Called if panic.
func PanicOnError(err error, msg string) {
if revErr, ok := err.(*Error); (ok && revErr != nil) || (!ok && err != nil) {
Logger.Fatalf("Abort: %s: %s %s\n", msg, revErr, err)
//panic(NewLoggedError(err))
var serr *SourceError
if (errors.As(err, &serr) && serr != nil) || err != nil {
Logger.Panicf("Abort: %s: %s %s", msg, serr, err)
}
}
@@ -136,7 +160,10 @@ func PanicOnError(err error, msg string) {
// ".template" are treated as a Go template and rendered using the given data.
// Additionally, the trailing ".template" is stripped from the file name.
// Also, dot files and dot directories are skipped.
func MustCopyDir(destDir, srcDir string, data map[string]interface{}) error {
func CopyDir(destDir, srcDir string, data map[string]interface{}) error {
if !DirExists(srcDir) {
return nil
}
return fsWalk(srcDir, srcDir, func(srcPath string, info os.FileInfo, err error) error {
// Get the relative path from the source base, and the corresponding path in
// the dest directory.
@@ -155,26 +182,29 @@ func MustCopyDir(destDir, srcDir string, data map[string]interface{}) error {
if info.IsDir() {
err := os.MkdirAll(filepath.Join(destDir, relSrcPath), 0777)
if !os.IsExist(err) {
PanicOnError(err, "Failed to create directory")
return NewBuildIfError(err, "Failed to create directory", "path", destDir+"/"+relSrcPath)
}
return nil
}
// If this file ends in ".template", render it as a template.
if strings.HasSuffix(relSrcPath, ".template") {
MustRenderTemplate(destPath[:len(destPath)-len(".template")], srcPath, data)
return nil
return RenderTemplate(destPath[:len(destPath)-len(".template")], srcPath, data)
}
// Else, just copy it over.
MustCopyFile(destPath, srcPath)
return nil
return CopyFile(destPath, srcPath)
})
}
// Shortcut to fsWalk.
func Walk(root string, walkFn filepath.WalkFunc) error {
return fsWalk(root,root,walkFn)
return fsWalk(root, root, walkFn)
}
// Walk the path tree using the function
// Every file found will call the function.
func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
fsWalkFunc := func(path string, info os.FileInfo, err error) error {
if err != nil {
@@ -214,9 +244,13 @@ func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
return err
}
func MustTarGzDir(destFilename, srcDir string) string {
// Tar gz the folder.
func TarGzDir(destFilename, srcDir string) (name string, err error) {
zipFile, err := os.Create(destFilename)
PanicOnError(err, "Failed to create archive")
if err != nil {
return "", NewBuildIfError(err, "Failed to create archive", "file", destFilename)
}
defer func() {
_ = zipFile.Close()
}()
@@ -231,13 +265,20 @@ func MustTarGzDir(destFilename, srcDir string) string {
_ = tarWriter.Close()
}()
_ = 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() {
return nil
}
srcFile, err := os.Open(srcPath)
PanicOnError(err, "Failed to read source file")
if err != nil {
return NewBuildIfError(err, "Failed to read file", "file", srcPath)
}
defer func() {
_ = srcFile.Close()
}()
@@ -248,17 +289,22 @@ func MustTarGzDir(destFilename, srcDir string) string {
Mode: int64(info.Mode()),
ModTime: info.ModTime(),
})
PanicOnError(err, "Failed to write tar entry header")
if err != nil {
return NewBuildIfError(err, "Failed to write tar entry header", "file", srcPath)
}
_, err = io.Copy(tarWriter, srcFile)
PanicOnError(err, "Failed to copy")
if err != nil {
return NewBuildIfError(err, "Failed to copy file", "file", srcPath)
}
return nil
})
return zipFile.Name()
return zipFile.Name(), err
}
// Return true if the file exists.
func Exists(filename string) bool {
_, err := os.Stat(filename)
return err == nil
@@ -267,9 +313,13 @@ func Exists(filename string) bool {
// empty returns true if the given directory is empty.
// the directory must exist.
func Empty(dirname string) bool {
if !DirExists(dirname) {
return true
}
dir, err := os.Open(dirname)
if err != nil {
Logger.Infof("error opening directory: %s", err)
return false
}
defer func() {
_ = dir.Close()
@@ -278,8 +328,82 @@ func Empty(dirname string) bool {
return len(results) == 0
}
func ImportPathFromCurrentDir() string {
pwd, _ := os.Getwd()
importPath, _ := filepath.Rel(filepath.Join(build.Default.GOPATH, "src"), pwd)
return filepath.ToSlash(importPath)
// Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory.
func FindSrcPaths(appPath string, packageList []string, packageResolver func(pkgName string) error) (sourcePathsmap map[string]string, err error) {
sourcePathsmap, missingList, err := findSrcPaths(appPath, packageList)
if err != nil && packageResolver != nil || len(missingList) > 0 {
Logger.Info("Failed to find package, attempting to call resolver for missing packages", "missing packages", missingList)
for _, item := range missingList {
if err = packageResolver(item); err != nil {
return
}
}
sourcePathsmap, missingList, err = findSrcPaths(appPath, packageList)
}
if err != nil && len(missingList) > 0 {
for _, missing := range missingList {
Logger.Error("Unable to import this package", "package", missing)
}
}
return
}
// Error is used for constant errors.
type Error string
// Error implements the error interface.
func (e Error) Error() string {
return string(e)
}
var (
ErrNoApp Error = "no app found"
ErrNoRevel Error = "no revel found"
)
// Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory.
func findSrcPaths(appPath string, packagesList []string) (sourcePathsmap map[string]string, missingList []string, err error) {
// Use packages to fetch
// by not specifying env, we will use the default env
config := &packages.Config{
Mode: packages.NeedName | packages.NeedFiles | packages.NeedDeps,
Dir: appPath,
}
config.Env = ReducedEnv(false)
sourcePathsmap = map[string]string{}
Logger.Infof("Environment path %s root %s config env %s", os.Getenv("GOPATH"), os.Getenv("GOROOT"), config.Env)
pkgs, err := packages.Load(config, packagesList...)
Logger.Infof("Environment path %s root %s config env %s", os.Getenv("GOPATH"), os.Getenv("GOROOT"), config.Env)
Logger.Info("Loaded packages ", "len results", len(pkgs), "error", err, "basedir", appPath)
for _, packageName := range packagesList {
found := false
log := Logger.New("seeking", packageName)
for _, pck := range pkgs {
log.Info("Found package", "package", pck.ID)
if pck.ID == packageName {
if pck.Errors != nil && len(pck.Errors) > 0 {
log.Error("Error ", "count", len(pck.Errors), "App Import Path", pck.ID, "filesystem path", pck.PkgPath, "errors", pck.Errors)
// continue
}
// a,_ := pck.MarshalJSON()
log.Info("Found ", "count", len(pck.GoFiles), "App Import Path", pck.ID, "apppath", appPath)
if len(pck.GoFiles) > 0 {
sourcePathsmap[packageName] = filepath.Dir(pck.GoFiles[0])
found = true
}
}
}
if !found {
if packageName == "github.com/revel/revel" {
err = ErrNoRevel
} else {
err = ErrNoApp
}
missingList = append(missingList, packageName)
}
}
return
}

View File

@@ -1,33 +1,39 @@
package utils
import (
"github.com/revel/cmd/logger"
"github.com/revel/config"
"fmt"
"os"
"strings"
"github.com/revel/cmd/logger"
"github.com/revel/config"
)
var Logger = logger.New()
func InitLogger(basePath string, logLevel logger.LogLevel) {
newContext := config.NewContext()
if logLevel<logger.LvlInfo {
newContext.SetOption("log.info.output", "none")
newContext.SetOption("log.debug.output", "none")
} else {
newContext.SetOption("log.info.output", "stdout")
if logLevel == logger.LvlDebug {
newContext.SetOption("log.debug.output", "stdout")
println("Debug on")
} else {
newContext.SetOption("log.debug.output", "off")
}
newContext.SetOption("log.warn.output","stderr")
newContext.SetOption("log.error.output","stderr")
newContext.SetOption("log.crit.output","stderr")
if logLevel >= logger.LvlInfo {
newContext.SetOption("log.info.output", "stdout")
} else {
newContext.SetOption("log.inf.output", "off")
}
newContext.SetOption("log.warn.output", "stderr")
newContext.SetOption("log.error.output", "stderr")
newContext.SetOption("log.crit.output", "stderr")
Logger.SetHandler(logger.InitializeFromConfig(basePath, newContext))
}
// This function is to throw a panic that may be caught by the packger so it can perform the needed
// imports
func Retry(format string, args ...interface{}) {
// imports.
func Retryf(format string, args ...interface{}) {
// Ensure the user's command prompt starts on the next line.
if !strings.HasSuffix(format, "\n") {
format += "\n"

View File

@@ -1,6 +1,6 @@
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 {
for _, el := range list {
if el == target {

View File

@@ -1,16 +1,16 @@
// Copyright (c) 2012-2017 The Revel Framework Authors, All rights reserved.
// Copyright (c) 2012-2018 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package cmd
const (
// Version current Revel Command version
Version = "0.20.0-dev"
// Version current Revel version
Version = "1.1.2"
// BuildDate latest commit/release date
BuildDate = "2018-02-06"
BuildDate = "2022-04-12"
// MinimumGoVersion minimum required Go version for Revel
MinimumGoVersion = ">= go1.8"
MinimumGoVersion = ">= go1.17"
)

View File

@@ -9,18 +9,18 @@ import (
"path/filepath"
"strings"
"sync"
"time"
"github.com/fsnotify/fsnotify"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"gopkg.in/fsnotify/fsnotify.v1"
"time"
)
// Listener is an interface for receivers of filesystem events.
type Listener interface {
// Refresh is invoked by the watcher on relevant filesystem events.
// If the listener returns an error, it is served to the user on the current request.
Refresh() *utils.Error
Refresh() *utils.SourceError
}
// DiscerningListener allows the receiver to selectively watch files.
@@ -44,24 +44,24 @@ type Watcher struct {
paths *model.RevelContainer
refreshTimer *time.Timer // The timer to countdown the next refresh
timerMutex *sync.Mutex // A mutex to prevent concurrent updates
refreshChannel chan *utils.Error
refreshChannel chan *utils.SourceError
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 {
return &Watcher{
forceRefresh: true,
lastError: -1,
paths: paths,
refreshTimerMS: time.Duration(paths.Config.IntDefault("watch.rebuild.delay", 10)),
forceRefresh: true,
lastError: -1,
paths: paths,
refreshInterval: time.Duration(paths.Config.IntDefault("watch.rebuild.delay", 1000)) * time.Millisecond,
eagerRefresh: eagerRefresh ||
paths.DevMode &&
paths.Config.BoolDefault("watch", true) &&
paths.Config.StringDefault("watch.mode", "normal") == "eager",
timerMutex: &sync.Mutex{},
refreshChannel: make(chan *utils.Error, 10),
refreshChannel: make(chan *utils.SourceError, 10),
refreshChannelCount: 0,
}
}
@@ -109,9 +109,7 @@ func (w *Watcher) Listen(listener Listener, roots ...string) {
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 {
utils.Logger.Fatal("Watcher: Error walking path:", "error", err)
return nil
@@ -150,7 +148,6 @@ func (w *Watcher) Listen(listener Listener, roots ...string) {
// NotifyWhenUpdated notifies the watcher when a file event is received.
func (w *Watcher) NotifyWhenUpdated(listener Listener, watcher *fsnotify.Watcher) {
for {
select {
case ev := <-watcher.Events:
@@ -166,7 +163,10 @@ func (w *Watcher) NotifyWhenUpdated(listener Listener, watcher *fsnotify.Watcher
} else {
// Run refresh in parallel
go func() {
w.notifyInProcess(listener)
if err := w.notifyInProcess(listener); err != nil {
utils.Logger.Error("failed to notify",
"error", err)
}
}()
}
}
@@ -178,7 +178,7 @@ func (w *Watcher) NotifyWhenUpdated(listener Listener, watcher *fsnotify.Watcher
// Notify causes the watcher to forward any change events to listeners.
// It returns the first (if any) error returned.
func (w *Watcher) Notify() *utils.Error {
func (w *Watcher) Notify() *utils.SourceError {
if w.serial {
// Serialize Notify() calls.
w.notifyMutex.Lock()
@@ -205,9 +205,10 @@ func (w *Watcher) Notify() *utils.Error {
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 {
var err *utils.Error
var err *utils.SourceError
if w.serial {
err = listener.Refresh()
} else {
@@ -217,10 +218,10 @@ func (w *Watcher) Notify() *utils.Error {
w.lastError = i
w.forceRefresh = true
return err
} else {
w.lastError = -1
w.forceRefresh = false
}
w.lastError = -1
w.forceRefresh = false
}
}
@@ -228,8 +229,8 @@ func (w *Watcher) Notify() *utils.Error {
}
// Build a queue for refresh notifications
// this will not return until one of the queue completes
func (w *Watcher) notifyInProcess(listener Listener) (err *utils.Error) {
// this will not return until one of the queue completes.
func (w *Watcher) notifyInProcess(listener Listener) (err *utils.SourceError) {
shouldReturn := false
// This code block ensures that either a timer is created
// or that a process would be added the the h.refreshChannel
@@ -240,11 +241,11 @@ func (w *Watcher) notifyInProcess(listener Listener) (err *utils.Error) {
w.forceRefresh = true
if w.refreshTimer != nil {
utils.Logger.Info("Found existing timer running, resetting")
w.refreshTimer.Reset(time.Millisecond * w.refreshTimerMS)
w.refreshTimer.Reset(w.refreshInterval)
shouldReturn = true
w.refreshChannelCount++
} 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
}
/*
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:])
}
*/