diff --git a/.codebeatsettings b/.codebeatsettings index 9fe4c72..edce06a 100644 --- a/.codebeatsettings +++ b/.codebeatsettings @@ -1,11 +1,12 @@ { "GOLANG": { - "ABC":[25, 35, 50, 70], - "BLOCK_NESTING":[5, 6, 7, 8], - "CYCLO":[20, 30, 45, 60], - "TOO_MANY_IVARS": [15, 18, 20, 25], + "ABC":[25, 35, 50, 70], + "BLOCK_NESTING":[7, 9, 11, 13], + "CYCLO":[20, 30, 45, 60], + "TOO_MANY_IVARS": [20, 25, 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] } } \ No newline at end of file diff --git a/model/command_config.go b/model/command_config.go index 3de36f9..c548cad 100644 --- a/model/command_config.go +++ b/model/command_config.go @@ -1,17 +1,22 @@ package model -// The constants import ( "fmt" + "github.com/revel/cmd" "github.com/revel/cmd/logger" "github.com/revel/cmd/utils" + "go/ast" "go/build" + "go/parser" + "go/token" + "io/ioutil" "os" "os/exec" "path/filepath" "strings" ) +// The constants const ( NEW COMMAND = iota + 1 RUN @@ -28,17 +33,19 @@ type ( // 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 (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 - PackageResolver func(pkgName string) error // a packge 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"` + 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 + PackageResolver func(pkgName string) error // a packge 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"` // The new command New struct { ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` @@ -57,7 +64,7 @@ type ( Run struct { ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` - Port int `short:"p" long:"port" default:"-1" description:"The port to listen" ` + Port int `short:"p" long:"port" default:"-1" description:"The port to listen" ` NoProxy bool `short:"n" long:"no-proxy" description:"True if proxy server should not be started. This will only update the main and routes files on change"` } `command:"run"` // The package command @@ -138,6 +145,16 @@ func (c *CommandConfig) UpdateImportPath() bool { c.ImportPath = importPath utils.Logger.Info("Returned import path", "path", importPath, "buildpath", build.Default.GOPATH) + 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()) + } return (len(importPath) > 0 || !required) } @@ -185,7 +202,7 @@ func (c *CommandConfig) InitPackageResolver() { getCmd = exec.Command(depPath, "ensure", "-add", pkgName) } else { utils.Logger.Info("No vendor folder detected, not using dependency manager to import package", "package", pkgName) - getCmd = exec.Command(c.GoCmd, "get", pkgName) + getCmd = exec.Command(c.GoCmd, "get", "-u", pkgName) } utils.CmdInit(getCmd, c.AppPath) @@ -237,7 +254,7 @@ func (c *CommandConfig) InitGoPaths() { } } - if len(c.SrcRoot)==0 && len(bestpath) > 0 { + if len(c.SrcRoot) == 0 && len(bestpath) > 0 { c.SrcRoot = bestpath } utils.Logger.Info("Source root", "path", c.SrcRoot, "cwd", workingDir, "gopath", c.GoPath) @@ -256,3 +273,48 @@ func (c *CommandConfig) InitGoPaths() { 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) SetVersions() (err error) { + c.CommandVersion, _ = ParseVersion(cmd.Version) + _, revelPath, err := utils.FindSrcPaths(c.ImportPath, RevelImportPath, c.PackageResolver) + if err == nil { + utils.Logger.Info("Fullpath to revel", "dir", revelPath) + fset := token.NewFileSet() // positions are relative to fset + + versionData, err := ioutil.ReadFile(filepath.Join(revelPath, RevelImportPath, "version.go")) + if err != nil { + utils.Logger.Error("Failed to find Revel version:", "error", err, "path", revelPath) + } + + // 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.Replace(r.Value, `"`, ``, -1)) + if err != nil { + utils.Logger.Errorf("Failed to parse version") + } else { + utils.Logger.Info("Parsed revel version", "version", c.FrameworkVersion.String()) + } + } + } + } + } + return +} diff --git a/model/event_test.go b/model/event_test.go index e3f063c..d1612aa 100644 --- a/model/event_test.go +++ b/model/event_test.go @@ -10,15 +10,15 @@ import ( func TestEventHandler(t *testing.T) { counter := 0 newListener := func(typeOf revel.Event, value interface{}) (responseOf revel.EventResponse) { - if typeOf == revel.REVEL_FAILURE { + if typeOf == revel.ENGINE_SHUTDOWN_REQUEST { counter++ } return } - // Attach the same handlder twice so we expect to see the response twice as well + // Attach the same handler twice so we expect to see the response twice as well revel.AddInitEventHandler(newListener) revel.AddInitEventHandler(newListener) - revel.RaiseEvent(revel.REVEL_AFTER_MODULES_LOADED, nil) - revel.RaiseEvent(revel.REVEL_FAILURE, nil) + revel.StopServer(1) assert.Equal(t, counter, 2, "Expected event handler to have been called") } + diff --git a/model/version.go b/model/version.go new file mode 100644 index 0000000..2b6a847 --- /dev/null +++ b/model/version.go @@ -0,0 +1,110 @@ +package model + +import ( + "fmt" + "github.com/pkg/errors" + "regexp" + "strconv" +) + +type Version struct { + Prefix string + Major int + Minor int + Maintenance int + Suffix string +} + +// The compatibility list +var frameworkCompatibleList = [][]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 +} + +// 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) { + + 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 = &Version{} + 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 frameworkCompatibleList { + 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("Unknown versions - do a 'go get -u github.com/revel/cmd/revel github.com/revel/modules github.com/revel/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 false +} + +// Convert the version to a string +func (v *Version) String() string { + return fmt.Sprintf("%s%d.%d.%d%s", v.Prefix, v.Major, v.Minor, v.Maintenance, v.Suffix) +} diff --git a/model/version_test.go b/model/version_test.go new file mode 100644 index 0000000..7998066 --- /dev/null +++ b/model/version_test.go @@ -0,0 +1,33 @@ +package model_test + +import ( + "github.com/stretchr/testify/assert" + "testing" + "github.com/revel/cmd/model" +) + +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") +} \ No newline at end of file diff --git a/revel/new.go b/revel/new.go index b2d557f..f74e8de 100644 --- a/revel/new.go +++ b/revel/new.go @@ -119,6 +119,8 @@ func newApp(c *model.CommandConfig) (err error) { if err = setApplicationPath(c); err != nil { return err } + // At this point the versions can be set + c.SetVersions() // checking and setting skeleton if err=setSkeletonPath(c);err!=nil { @@ -189,16 +191,6 @@ func setApplicationPath(c *model.CommandConfig) (err error) { c.AppName = filepath.Base(c.AppPath) - //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 } diff --git a/revel/run.go b/revel/run.go index 166518b..40b4be0 100644 --- a/revel/run.go +++ b/revel/run.go @@ -12,6 +12,7 @@ import ( "github.com/revel/cmd/model" "github.com/revel/cmd/utils" "go/build" + "os" ) var cmdRun = &Command{ @@ -108,7 +109,8 @@ func updateRunConfig(c *model.CommandConfig, args []string) bool { c.Run.Mode = args[0] } case 0: - return false + // Attempt to set the import path to the current working director. + c.Run.ImportPath,_ = os.Getwd() } c.Index = model.RUN return true diff --git a/revel/version.go b/revel/version.go index a9021e8..de3e584 100644 --- a/revel/version.go +++ b/revel/version.go @@ -118,3 +118,4 @@ func versionApp(c *model.CommandConfig) (err error) { return } +