Initial rewrite of revel/cmd to provide vendor support and enhanced CLI options

This commit is contained in:
NotZippy
2018-09-13 13:21:10 -07:00
parent d2ac018544
commit d0baaeb9e9
40 changed files with 4435 additions and 1125 deletions

View File

@@ -14,11 +14,12 @@ import (
"path/filepath"
"strings"
"github.com/revel/revel"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
)
var cmdNew = &Command{
UsageLine: "new [path] [skeleton]",
UsageLine: "new -i [path] -s [skeleton]",
Short: "create a skeleton Revel application",
Long: `
New creates a few files to get a new Revel application running quickly.
@@ -30,61 +31,113 @@ Skeleton is an optional argument, provided as an import path
For example:
revel new import/path/helloworld
revel new -a import/path/helloworld
revel new -a import/path/helloworld -s import/path/skeleton
revel new import/path/helloworld import/path/skeleton
`,
}
func init() {
cmdNew.Run = newApp
cmdNew.RunWith = newApp
cmdNew.UpdateConfig = updateNewConfig
}
var (
// go related paths
gopath string
gocmd string
srcRoot string
// revel related paths
revelPkg *build.Package
revelCmdPkg *build.Package
appPath string
appName string
basePath string
importPath string
skeletonPath string
)
func newApp(args []string) {
// check for proper args by count
// Called when unable to parse the command line automatically and assumes an old launch
func updateNewConfig(c *model.CommandConfig, args []string) bool {
c.Index = NEW
if len(args) == 0 {
errorf("No import path given.\nRun 'revel help new' for usage.\n")
fmt.Fprintf(os.Stderr, cmdNew.Long)
return false
}
if len(args) > 2 {
errorf("Too many arguments provided.\nRun 'revel help new' for usage.\n")
c.New.ImportPath = args[0]
if len(args)>1 {
c.New.Skeleton = args[1]
}
return true
}
// Call to create a new application
func newApp(c *model.CommandConfig) {
// check for proper args by count
c.ImportPath = c.New.ImportPath
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)
}
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))
}
// TODO build.Default.GOPATH = build.Default.GOPATH + string(os.PathListSeparator) + c.ImportPath
}
// checking and setting go paths
initGoPaths()
// checking and setting application
setApplicationPath(args)
setApplicationPath(c)
// checking and setting skeleton
setSkeletonPath(args)
setSkeletonPath(c)
// copy files to new app directory
copyNewAppFiles()
copyNewAppFiles(c)
// goodbye world
fmt.Fprintln(os.Stdout, "Your application is ready:\n ", appPath)
fmt.Fprintln(os.Stdout, "\nYou can run it with:\n revel run", importPath)
fmt.Fprintln(os.Stdout, "Your application is ready:\n ", c.AppPath)
// Check to see if it should be run right off
if c.New.Run {
c.Run.ImportPath = c.ImportPath
runApp(c)
} else {
fmt.Fprintln(os.Stdout, "\nYou can run it with:\n revel run -a ", c.ImportPath)
}
}
// Used to generate a new secret key
const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
// Generate a secret key
func generateSecret() string {
chars := make([]byte, 64)
for i := 0; i < 64; i++ {
@@ -93,129 +146,145 @@ func generateSecret() string {
return string(chars)
}
// lookup and set Go related variables
func initGoPaths() {
// lookup go path
gopath = build.Default.GOPATH
if gopath == "" {
errorf("Abort: GOPATH environment variable is not set. " +
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
}
// check for go executable
var err error
gocmd, err = exec.LookPath("go")
if err != nil {
errorf("Go executable not found in PATH.")
}
// revel/revel#1004 choose go path relative to current working directory
workingDir, _ := os.Getwd()
goPathList := filepath.SplitList(gopath)
for _, path := range goPathList {
if strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
srcRoot = path
break
}
path, _ = filepath.EvalSymlinks(path)
if len(path) > 0 && strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
srcRoot = path
break
}
}
if len(srcRoot) == 0 {
revel.RevelLog.Fatal("Abort: could not create a Revel application outside of GOPATH.")
}
// set go src path
srcRoot = filepath.Join(srcRoot, "src")
}
func setApplicationPath(args []string) {
var err error
importPath = args[0]
// Sets the applicaiton path
func setApplicationPath(c *model.CommandConfig) {
// 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(importPath) || strings.HasPrefix(importPath, ".") {
errorf("Abort: '%s' looks like a directory. Please provide a Go import path instead.",
importPath)
if filepath.IsAbs(c.ImportPath) || strings.HasPrefix(c.ImportPath, ".") {
utils.Logger.Fatalf("Abort: '%s' looks like a directory. Please provide a Go import path instead.",
c.ImportPath)
}
appPath = filepath.Join(srcRoot, filepath.FromSlash(importPath))
_, err = build.Import(importPath, "", build.FindOnly)
if err == nil && !empty(appPath) {
errorf("Abort: Import path %s already exists.\n", 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
_, err = build.Import(model.RevelImportPath, "", build.FindOnly)
if err != nil {
// Go get the revel project
utils.Logger.Fatal("Abort: Could not find Revel source code:", "error", err)
}
}
revelPkg, err = build.Import(revel.RevelImportPath, "", build.FindOnly)
if err != nil {
errorf("Abort: Could not find Revel source code: %s\n", err)
}
c.AppName = filepath.Base(c.AppPath)
c.BasePath = filepath.ToSlash(filepath.Dir(c.ImportPath))
appName = filepath.Base(appPath)
basePath = filepath.ToSlash(filepath.Dir(importPath))
if basePath == "." {
if c.BasePath == "." {
// we need to remove the a single '.' when
// the app is in the $GOROOT/src directory
basePath = ""
c.BasePath = ""
} else {
// we need to append a '/' when the app is
// is a subdirectory such as $GOROOT/src/path/to/revelapp
basePath += "/"
c.BasePath += "/"
}
}
func setSkeletonPath(args []string) {
// Set the skeleton path
func setSkeletonPath(c *model.CommandConfig) {
var err error
if len(args) == 2 { // user specified
skeletonName := args[1]
_, err = build.Import(skeletonName, "", build.FindOnly)
if len(c.SkeletonPath) > 0 { // user specified
_, err = build.Import(c.SkeletonPath, "", build.FindOnly)
if err != nil {
// Execute "go get <pkg>"
getCmd := exec.Command(gocmd, "get", "-d", skeletonName)
getCmd := exec.Command(c.GoCmd, "get", "-d", c.SkeletonPath)
fmt.Println("Exec:", getCmd.Args)
getOutput, err := getCmd.CombinedOutput()
// check getOutput for no buildible string
bpos := bytes.Index(getOutput, []byte("no buildable Go source files in"))
if err != nil && bpos == -1 {
errorf("Abort: Could not find or 'go get' Skeleton source code: %s\n%s\n", getOutput, skeletonName)
utils.Logger.Fatalf("Abort: Could not find or 'go get' Skeleton source code: %s\n%s\n", getOutput, c.SkeletonPath)
}
}
// use the
skeletonPath = filepath.Join(srcRoot, skeletonName)
c.SkeletonPath = filepath.Join(c.SrcRoot, c.SkeletonPath)
} else {
// use the revel default
revelCmdPkg, err = build.Import(RevelCmdImportPath, "", build.FindOnly)
revelCmdPkg, err := build.Import(RevelCmdImportPath, "", build.FindOnly)
if err != nil {
errorf("Abort: Could not find Revel Cmd source code: %s\n", err)
utils.Logger.Fatalf("Abort: Could not find Revel Cmd source code: %s\n", err)
}
skeletonPath = filepath.Join(revelCmdPkg.Dir, "revel", "skeleton")
c.SkeletonPath = filepath.Join(revelCmdPkg.Dir, "revel", "skeleton")
}
}
func copyNewAppFiles() {
func copyNewAppFiles(c *model.CommandConfig) {
var err error
err = os.MkdirAll(appPath, 0777)
panicOnError(err, "Failed to create directory "+appPath)
err = os.MkdirAll(c.AppPath, 0777)
utils.PanicOnError(err, "Failed to create directory "+c.AppPath)
_ = mustCopyDir(appPath, skeletonPath, map[string]interface{}{
_ = utils.MustCopyDir(c.AppPath, c.SkeletonPath, map[string]interface{}{
// app.conf
"AppName": appName,
"BasePath": basePath,
"AppName": c.AppName,
"BasePath": c.BasePath,
"Secret": generateSecret(),
})
// Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore.
gitignore := ".gitignore"
mustCopyFile(filepath.Join(appPath, gitignore), filepath.Join(skeletonPath, gitignore))
utils.MustCopyFile(filepath.Join(c.AppPath, gitignore), filepath.Join(c.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
`
)