diff --git a/.codebeatsettings b/.codebeatsettings index edce06a..a99f39e 100644 --- a/.codebeatsettings +++ b/.codebeatsettings @@ -1,6 +1,7 @@ { "GOLANG": { "ABC":[25, 35, 50, 70], + "ARITY":[5,6,7,8], "BLOCK_NESTING":[7, 9, 11, 13], "CYCLO":[20, 30, 45, 60], "TOO_MANY_IVARS": [20, 25, 40, 45], diff --git a/model/command_config.go b/model/command_config.go index 41f82d6..e46366a 100644 --- a/model/command_config.go +++ b/model/command_config.go @@ -44,6 +44,7 @@ type ( SrcRoot string // The source root AppPath string // The application path (absolute) AppName string // The application name + Vendored bool // True if the application is vendored 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 @@ -87,6 +88,7 @@ type ( // The version command Version struct { ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` + Update bool `short:"u" long:"Update the framework and modules" required:"false"` } `command:"version"` } ) @@ -124,7 +126,6 @@ func (c *CommandConfig) UpdateImportPath() error { } // For an absolute path currentPath, _ = filepath.Abs(importPath) - } if err == nil { @@ -136,7 +137,10 @@ func (c *CommandConfig) UpdateImportPath() error { if len(importPath) > 4 && strings.ToLower(importPath[0:4]) == "src/" { importPath = importPath[4:] } else if importPath == "src" { - return fmt.Errorf("Invlaid import path, working dir is in GOPATH root") + if c.Index != VERSION { + return fmt.Errorf("Invlaid import path, working dir is in GOPATH root") + } + importPath = "" } utils.Logger.Info("Updated import path", "path", importPath) } @@ -167,18 +171,19 @@ func (c *CommandConfig) UpdateImportPath() error { // Used to initialize the package resolver func (c *CommandConfig) InitPackageResolver() { - useVendor := utils.DirExists(filepath.Join(c.AppPath, "vendor")) + c.Vendored = utils.DirExists(filepath.Join(c.AppPath, "vendor")) if c.Index == NEW && c.New.Vendored { - useVendor = true + c.Vendored = true } - utils.Logger.Info("InitPackageResolver", "useVendor", useVendor, "path", c.AppPath) + + utils.Logger.Info("InitPackageResolver", "useVendor", c.Vendored, "path", c.AppPath) var ( depPath string err error ) - if useVendor { + if c.Vendored { utils.Logger.Info("Vendor folder detected, scanning for deps in path") depPath, err = exec.LookPath("dep") if err != nil { @@ -194,8 +199,8 @@ func (c *CommandConfig) InitPackageResolver() { //useVendor := utils.DirExists(filepath.Join(c.AppPath, "vendor")) var getCmd *exec.Cmd - utils.Logger.Info("Request for package ", "package", pkgName, "use vendor", useVendor) - if useVendor { + utils.Logger.Info("Request for package ", "package", pkgName, "use vendor", c.Vendored) + if c.Vendored { utils.Logger.Info("Using dependency manager to import package", "package", pkgName) if depPath == "" { @@ -206,7 +211,15 @@ func (c *CommandConfig) InitPackageResolver() { utils.Logger.Error("Missing package", "package", pkgName) return fmt.Errorf("Missing package %s", pkgName) } - getCmd = exec.Command(depPath, "ensure", "-add", pkgName) + // Check to see if the package exists locally + _, err := build.Import(pkgName, c.AppPath, build.FindOnly) + if err != nil { + getCmd = exec.Command(depPath, "ensure", "-add", pkgName) + } else { + getCmd = exec.Command(depPath, "ensure", "-update", pkgName) + } + + } 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) @@ -225,6 +238,7 @@ func (c *CommandConfig) InitPackageResolver() { // lookup and set Go related variables func (c *CommandConfig) InitGoPaths() { + utils.Logger.Info("InitGoPaths") // lookup go path c.GoPath = build.Default.GOPATH if c.GoPath == "" { diff --git a/model/revel_container.go b/model/revel_container.go index 1cba47c..f15a665 100644 --- a/model/revel_container.go +++ b/model/revel_container.go @@ -92,6 +92,7 @@ func (w *WrappedRevelCallback) PackageResolver(pkgName string) error { // RevelImportPath Revel framework import path var RevelImportPath = "github.com/revel/revel" +var 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. @@ -226,7 +227,7 @@ func (rp *RevelContainer) loadModules(callback RevelCallback) (err error) { modulePath, err := rp.ResolveImportPath(moduleImportPath) if err != nil { - utils.Logger.Info("Missing module ", "module", moduleImportPath, "error",err) + utils.Logger.Info("Missing module ", "module_import_path", moduleImportPath, "error",err) callback.PackageResolver(moduleImportPath) modulePath, err = rp.ResolveImportPath(moduleImportPath) if err != nil { diff --git a/model/version.go b/model/version.go index 502cbb4..60ee3b0 100644 --- a/model/version.go +++ b/model/version.go @@ -13,6 +13,8 @@ type Version struct { Minor int Maintenance int Suffix string + BuildDate string + MinGoVersion string } // The compatibility list @@ -27,6 +29,13 @@ 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) @@ -36,7 +45,7 @@ func ParseVersion(version string) (v *Version, err error) { 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]) @@ -45,7 +54,6 @@ func ParseVersion(version string) (v *Version, err error) { 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 != "" { @@ -105,6 +113,12 @@ func (v *Version) Newer(o *Version) bool { } // Convert the version to a string -func (v *Version) String() 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 Minimium Go Version: %s", + v.Prefix, v.Major, v.Minor, v.Maintenance, v.Suffix, v.BuildDate, v.MinGoVersion) +} diff --git a/revel/new.go b/revel/new.go index abe299d..84f848e 100644 --- a/revel/new.go +++ b/revel/new.go @@ -16,9 +16,6 @@ import ( "github.com/revel/cmd/model" "github.com/revel/cmd/utils" "net/url" - "github.com/kr/pty" - "io" - "bytes" ) var cmdNew = &Command{ @@ -117,16 +114,11 @@ func newApp(c *model.CommandConfig) (err error) { getCmd := exec.Command("dep", "ensure", "-v") utils.CmdInit(getCmd, c.AppPath) - f, err := pty.Start(getCmd); - stdout := new(bytes.Buffer) - io.Copy(io.MultiWriter(stdout, os.Stdout), f) - if err = getCmd.Wait(); err != nil { - } utils.Logger.Info("Exec:", "args", getCmd.Args, "env", getCmd.Env, "workingdir",getCmd.Dir) - // getOutput, err := getCmd.CombinedOutput() + getOutput, err := getCmd.CombinedOutput() if err != nil { - return utils.NewBuildIfError(err, stdout.String()) + return utils.NewBuildIfError(err, string(getOutput)) } } @@ -207,7 +199,7 @@ func setApplicationPath(c *model.CommandConfig) (err error) { // Set the skeleton path func setSkeletonPath(c *model.CommandConfig) (err error) { if len(c.New.SkeletonPath) == 0 { - c.New.SkeletonPath = "git://" + RevelSkeletonsImportPath + ":basic/bootstrap4" + c.New.SkeletonPath = "https://" + RevelSkeletonsImportPath + ":basic/bootstrap4" } // First check to see the protocol of the string @@ -216,7 +208,7 @@ func setSkeletonPath(c *model.CommandConfig) (err error) { utils.Logger.Info("Detected skeleton path", "path", sp) switch strings.ToLower(sp.Scheme) { - // TODO Add support for https, http, ftp + // TODO Add support for ftp, sftp, scp ?? case "" : sp.Scheme="file" fallthrough @@ -234,14 +226,13 @@ func setSkeletonPath(c *model.CommandConfig) (err error) { return fmt.Errorf("Failed to find skeleton in filepath %s %s", fullpath, sp.String()) } case "git": + fallthrough + case "http": + fallthrough + case "https": if err := newLoadFromGit(c, sp); err != nil { return err } - //case "": - - //if err := newLoadFromGo(c, sp); err != nil { - // return err - //} default: utils.Logger.Fatal("Unsupported skeleton schema ", "path", c.New.SkeletonPath) diff --git a/revel/version.go b/revel/version.go index de3e584..1000693 100644 --- a/revel/version.go +++ b/revel/version.go @@ -10,19 +10,30 @@ package main import ( "fmt" - "runtime" "github.com/revel/cmd" "github.com/revel/cmd/model" "github.com/revel/cmd/utils" "go/ast" - "go/build" "go/parser" "go/token" "io/ioutil" + "net/http" "os" "os/exec" "path/filepath" + "strings" + "bytes" +) + +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{ @@ -38,12 +49,13 @@ For example: } func init() { - cmdVersion.RunWith = versionApp - cmdVersion.UpdateConfig = updateVersionConfig + v := &VersionCommand{} + cmdVersion.UpdateConfig = v.UpdateConfig + cmdVersion.RunWith = v.RunWith } // Update the version -func updateVersionConfig(c *model.CommandConfig, args []string) bool { +func (v *VersionCommand) UpdateConfig(c *model.CommandConfig, args []string) bool { if len(args) > 0 { c.Version.ImportPath = args[0] } @@ -51,71 +63,200 @@ func updateVersionConfig(c *model.CommandConfig, args []string) bool { } // Displays the version of go and Revel -func versionApp(c *model.CommandConfig) (err error) { +func (v *VersionCommand) RunWith(c *model.CommandConfig) (err error) { + utils.Logger.Info("Requesting version information", "config", c) + v.Command = c - var revelPath, appPath string + // Update the versions with the local values + v.updateLocalVersions() - - appPath, revelPath, err = utils.FindSrcPaths(c.ImportPath, model.RevelImportPath, c.PackageResolver) - if err != nil { - return utils.NewBuildError("Failed to import "+c.ImportPath+" with error:", "error", err) + needsUpdates := true + versionInfo := "" + for x := 0; x < 2 && needsUpdates; x++ { + needsUpdates = false + versionInfo, needsUpdates = v.doRepoCheck(x==0) } - revelPath = revelPath + model.RevelImportPath - fmt.Println("\nRevel Framework",revelPath, appPath ) - 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)") + fmt.Println(versionInfo) + cmd := exec.Command(c.GoCmd, "version") + cmd.Stdout = os.Stdout + if e := cmd.Start(); e != nil { + fmt.Println("Go command error ", e) } else { - utils.Logger.Info("Fullpath to revel", "dir", revelPath) - fset := token.NewFileSet() // positions are relative to fset - - version, err := ioutil.ReadFile(filepath.Join(revelPath, "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, "", version, 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) - fmt.Printf("Revel %s = %s\n", spec.Names[0].Name, r.Value) - } - } - } - fmt.Println("\nRevel Command Utility Tool") - fmt.Println("Version", cmd.Version) - fmt.Println("Build Date", cmd.BuildDate) - fmt.Println("Minimum Go Version", cmd.MinimumGoVersion) - - fmt.Printf("Compiled By %s %s/%s\n\n", runtime.Version(), runtime.GOOS, runtime.GOARCH) - // Extract the goversion detected - if len(c.GoCmd) > 0 { - cmd := exec.Command(c.GoCmd, "version") - cmd.Stdout = os.Stdout - if e := cmd.Start(); e != nil { - fmt.Println("Go command error ", e) - } else { - cmd.Wait() - } - } else { - fmt.Println("Go command not found ") + cmd.Wait() } return } +// Checks the Revel repos for the latest version +func (v *VersionCommand) doRepoCheck(updateLibs bool) (versionInfo string, needsUpdate bool) { + var ( + title string + localVersion *model.Version + ) + for _, repo := range []string{"revel", "cmd", "modules"} { + versonFromRepo, err := v.versionFromRepo(repo, "", "version.go") + if err != nil { + utils.Logger.Info("Failed to get version from repo", "repo", repo, "error", err) + } + switch repo { + case "revel": + title, repo, localVersion = "Revel Framework", "github.com/revel/revel", v.revelVersion + case "cmd": + title, repo, localVersion = "Revel Cmd", "github.com/revel/cmd/revel", v.cmdVersion + case "modules": + title, repo, localVersion = "Revel Modules", "github.com/revel/modules", v.modulesVersion + } + + // Only do an update on the first loop, and if specified to update + shouldUpdate := updateLibs && v.Command.Version.Update + if v.Command.Version.Update { + if localVersion == nil || (versonFromRepo != nil && versonFromRepo.Newer(localVersion)) { + needsUpdate = true + if shouldUpdate { + v.doUpdate(title, repo, localVersion, versonFromRepo) + v.updateLocalVersions() + } + } + } + versionInfo = versionInfo + v.outputVersion(title, repo, localVersion, versonFromRepo) + } + return +} + +// Checks for updates if needed +func (v *VersionCommand) doUpdate(title, repo string, local, remote *model.Version) { + utils.Logger.Info("Updating package", "package", title, "repo",repo) + fmt.Println("Attempting to update package", title) + if err := v.Command.PackageResolver(repo); err != nil { + utils.Logger.Error("Unable to update repo", "repo", repo, "error", err) + } else if repo == "github.com/revel/cmd/revel" { + // One extra step required here to run the install for the command + utils.Logger.Fatal("Revel command tool was updated, you must manually run the following command before continuing\ngo install github.com/revel/cmd/revel") + } + return +} + +// Prints out the local and remote versions, calls update if needed +func (v *VersionCommand) outputVersion(title, repo string, local, remote *model.Version) (output string) { + 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) +} + +// Returns version information from a file called version on the gopath +func (v *VersionCommand) compareAndUpdateVersion(remoteVersion *model.Version, localVersion *model.Version) (err error) { + return +} +func (v *VersionCommand) versionFromFilepath(sourcePath string) (version *model.Version, err error) { + 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": + version.ParseVersion(strings.Replace(r.Value, `"`, "", -1)) + 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{} + v.cmdVersion.ParseVersion(cmd.Version) + v.cmdVersion.BuildDate = cmd.BuildDate + v.cmdVersion.MinGoVersion = cmd.MinimumGoVersion + + var modulePath, revelPath string + _, revelPath, err := utils.FindSrcPaths(v.Command.ImportPath, model.RevelImportPath, v.Command.PackageResolver) + if err != nil { + utils.Logger.Warn("Unable to extract version information from Revel library", "error,err") + return + } + revelPath = revelPath + model.RevelImportPath + utils.Logger.Info("Fullpath to revel", "dir", revelPath) + v.revelVersion, err = v.versionFromFilepath(revelPath) + if err != nil { + utils.Logger.Warn("Unable to extract version information from Revel", "error,err") + } + + _, modulePath, err = utils.FindSrcPaths(v.Command.ImportPath, model.RevelModulesImportPath, v.Command.PackageResolver) + if err != nil { + utils.Logger.Warn("Unable to extract version information from Revel library", "error,err") + return + } + modulePath = modulePath + model.RevelModulesImportPath + v.modulesVersion, err = v.versionFromFilepath(modulePath) + if err != nil { + utils.Logger.Warn("Unable to extract version information from Revel Modules", "error", err) + } + + return +} diff --git a/version.go b/version.go index c4793c7..f9c4944 100644 --- a/version.go +++ b/version.go @@ -6,7 +6,7 @@ package cmd const ( // Version current Revel Command version - Version = "0.20.2" + Version = "0.21.0-dev" // BuildDate latest commit/release date BuildDate = "2018-10-02"