github.com/jd-ly/cmd@v1.0.10/revel/new.go (about)

     1  // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
     2  // Revel Framework source code and usage is governed by a MIT style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"go/build"
    10  	"math/rand"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	"github.com/jd-ly/cmd/model"
    17  	"github.com/jd-ly/cmd/utils"
    18  	"net/url"
    19  )
    20  
    21  var cmdNew = &Command{
    22  	UsageLine: "new -i [path] -s [skeleton]",
    23  	Short:     "create a skeleton Revel application",
    24  	Long: `
    25  New creates a few files to get a new Revel application running quickly.
    26  
    27  It puts all of the files in the given import path, taking the final element in
    28  the path to be the app name.
    29  
    30  Skeleton is an optional argument, provided as an import path
    31  
    32  For example:
    33  
    34      revel new -a import/path/helloworld
    35  
    36      revel new -a import/path/helloworld -s import/path/skeleton
    37  
    38  `,
    39  }
    40  
    41  func init() {
    42  	cmdNew.RunWith = newApp
    43  	cmdNew.UpdateConfig = updateNewConfig
    44  }
    45  
    46  // Called when unable to parse the command line automatically and assumes an old launch
    47  func updateNewConfig(c *model.CommandConfig, args []string) bool {
    48  	c.Index = model.NEW
    49  	if len(c.New.Package) > 0 {
    50  		c.New.NotVendored = false
    51  	}
    52  	c.Vendored = !c.New.NotVendored
    53  
    54  	if len(args) == 0 {
    55  		if len(c.New.ImportPath) == 0 {
    56  			fmt.Fprintf(os.Stderr, cmdNew.Long)
    57  			return false
    58  		}
    59  		return true
    60  	}
    61  	c.New.ImportPath = args[0]
    62  	if len(args) > 1 {
    63  		c.New.SkeletonPath = args[1]
    64  	}
    65  
    66  	return true
    67  
    68  }
    69  
    70  // Call to create a new application
    71  func newApp(c *model.CommandConfig) (err error) {
    72  	// Check for an existing folder so we don't clobber it
    73  	_, err = build.Import(c.ImportPath, "", build.FindOnly)
    74  	if err == nil || !utils.Empty(c.AppPath) {
    75  		return utils.NewBuildError("Abort: Import path already exists.", "path", c.ImportPath, "apppath", c.AppPath)
    76  	}
    77  
    78  	// checking and setting skeleton
    79  	if err = setSkeletonPath(c); err != nil {
    80  		return
    81  	}
    82  
    83  	// Create application path
    84  	if err := os.MkdirAll(c.AppPath, os.ModePerm); err != nil {
    85  		return utils.NewBuildError("Abort: Unable to create app path.", "path", c.AppPath)
    86  	}
    87  
    88  	// checking and setting application
    89  	if err = setApplicationPath(c); err != nil {
    90  		return err
    91  	}
    92  
    93  	// This kicked off the download of the revel app, not needed for vendor
    94  	if !c.Vendored {
    95  		// At this point the versions can be set
    96  		c.SetVersions()
    97  	}
    98  
    99  	// copy files to new app directory
   100  	if err = copyNewAppFiles(c); err != nil {
   101  		return
   102  	}
   103  
   104  	// Run the vendor tool if needed
   105  	if c.Vendored {
   106  		if err = createModVendor(c); err != nil {
   107  			return
   108  		}
   109  	}
   110  
   111  	// goodbye world
   112  	fmt.Fprintln(os.Stdout, "Your application has been created in:\n  ", c.AppPath)
   113  	// Check to see if it should be run right off
   114  	if c.New.Run {
   115  		// Need to prep the run command
   116  		c.Run.ImportPath = c.ImportPath
   117  		updateRunConfig(c,nil)
   118  		c.UpdateImportPath()
   119  		runApp(c)
   120  	} else {
   121  		fmt.Fprintln(os.Stdout, "\nYou can run it with:\n   revel run -a ", c.ImportPath)
   122  	}
   123  	return
   124  }
   125  
   126  func createModVendor(c *model.CommandConfig) (err error) {
   127  
   128  	utils.Logger.Info("Creating a new mod app")
   129  	goModCmd := exec.Command("go", "mod", "init", filepath.Join(c.New.Package, c.AppName))
   130  
   131  	utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
   132  
   133  	utils.Logger.Info("Exec:", "args", goModCmd.Args, "env", goModCmd.Env, "workingdir", goModCmd.Dir)
   134  	getOutput, err := goModCmd.CombinedOutput()
   135  	if c.New.Callback != nil {
   136  		err = c.New.Callback()
   137  	}
   138  	if err != nil {
   139  		return utils.NewBuildIfError(err, string(getOutput))
   140  	}
   141  	return
   142  }
   143  
   144  func createDepVendor(c *model.CommandConfig) (err error) {
   145  
   146  	utils.Logger.Info("Creating a new vendor app")
   147  
   148  	vendorPath := filepath.Join(c.AppPath, "vendor")
   149  	if !utils.DirExists(vendorPath) {
   150  
   151  		if err := os.MkdirAll(vendorPath, os.ModePerm); err != nil {
   152  			return utils.NewBuildError("Failed to create " + vendorPath, "error", err)
   153  		}
   154  	}
   155  
   156  	// In order for dep to run there needs to be a source file in the folder
   157  	tempPath := filepath.Join(c.AppPath, "tmp")
   158  	utils.Logger.Info("Checking for temp folder for source code", "path", tempPath)
   159  	if !utils.DirExists(tempPath) {
   160  		if err := os.MkdirAll(tempPath, os.ModePerm); err != nil {
   161  			return utils.NewBuildIfError(err, "Failed to create " + vendorPath)
   162  		}
   163  
   164  		if err = utils.GenerateTemplate(filepath.Join(tempPath, "main.go"), NEW_MAIN_FILE, nil); err != nil {
   165  			return utils.NewBuildIfError(err, "Failed to create main file " + vendorPath)
   166  		}
   167  	}
   168  
   169  	// Create a package template file if it does not exist
   170  	packageFile := filepath.Join(c.AppPath, "Gopkg.toml")
   171  	utils.Logger.Info("Checking for Gopkg.toml", "path", packageFile)
   172  	if !utils.Exists(packageFile) {
   173  		utils.Logger.Info("Generating Gopkg.toml", "path", packageFile)
   174  		if err := utils.GenerateTemplate(packageFile, VENDOR_GOPKG, nil); err != nil {
   175  			return utils.NewBuildIfError(err, "Failed to generate template")
   176  		}
   177  	} else {
   178  		utils.Logger.Info("Package file exists in skeleto, skipping adding")
   179  	}
   180  
   181  	getCmd := exec.Command("dep", "ensure", "-v")
   182  	utils.CmdInit(getCmd, !c.Vendored, c.AppPath)
   183  
   184  	utils.Logger.Info("Exec:", "args", getCmd.Args, "env", getCmd.Env, "workingdir", getCmd.Dir)
   185  	getOutput, err := getCmd.CombinedOutput()
   186  	if err != nil {
   187  		return utils.NewBuildIfError(err, string(getOutput))
   188  	}
   189  	return
   190  }
   191  
   192  // Used to generate a new secret key
   193  const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
   194  
   195  // Generate a secret key
   196  func generateSecret() string {
   197  	chars := make([]byte, 64)
   198  	for i := 0; i < 64; i++ {
   199  		chars[i] = alphaNumeric[rand.Intn(len(alphaNumeric))]
   200  	}
   201  	return string(chars)
   202  }
   203  
   204  // Sets the applicaiton path
   205  func setApplicationPath(c *model.CommandConfig) (err error) {
   206  
   207  	// revel/revel#1014 validate relative path, we cannot use built-in functions
   208  	// since Go import path is valid relative path too.
   209  	// so check basic part of the path, which is "."
   210  
   211  	// If we are running a vendored version of Revel we do not need to check for it.
   212  	if !c.Vendored {
   213  		if filepath.IsAbs(c.ImportPath) || strings.HasPrefix(c.ImportPath, ".") {
   214  			utils.Logger.Fatalf("Abort: '%s' looks like a directory.  Please provide a Go import path instead.",
   215  				c.ImportPath)
   216  		}
   217  		_, err = build.Import(model.RevelImportPath, "", build.FindOnly)
   218  		if err != nil {
   219  			//// Go get the revel project
   220  			err = c.PackageResolver(model.RevelImportPath)
   221  			if err != nil {
   222  				return utils.NewBuildIfError(err, "Failed to fetch revel " + model.RevelImportPath)
   223  			}
   224  		}
   225  	}
   226  
   227  	c.AppName = filepath.Base(c.AppPath)
   228  
   229  	return nil
   230  }
   231  
   232  // Set the skeleton path
   233  func setSkeletonPath(c *model.CommandConfig) (err error) {
   234  	if len(c.New.SkeletonPath) == 0 {
   235  		c.New.SkeletonPath = "https://" + RevelSkeletonsImportPath + ":basic/bootstrap4"
   236  	}
   237  
   238  	// First check to see the protocol of the string
   239  	sp, err := url.Parse(c.New.SkeletonPath)
   240  	if err == nil {
   241  		utils.Logger.Info("Detected skeleton path", "path", sp)
   242  
   243  		switch strings.ToLower(sp.Scheme) {
   244  		// TODO Add support for ftp, sftp, scp ??
   245  		case "" :
   246  			sp.Scheme = "file"
   247  			fallthrough
   248  		case "file" :
   249  			fullpath := sp.String()[7:]
   250  			if !filepath.IsAbs(fullpath) {
   251  				fullpath, err = filepath.Abs(fullpath)
   252  				if err != nil {
   253  					return
   254  				}
   255  			}
   256  			c.New.SkeletonPath = fullpath
   257  			utils.Logger.Info("Set skeleton path to ", fullpath)
   258  			if !utils.DirExists(fullpath) {
   259  				return fmt.Errorf("Failed to find skeleton in filepath %s %s", fullpath, sp.String())
   260  			}
   261  		case "git":
   262  			fallthrough
   263  		case "http":
   264  			fallthrough
   265  		case "https":
   266  			if err := newLoadFromGit(c, sp); err != nil {
   267  				return err
   268  			}
   269  		default:
   270  			utils.Logger.Fatal("Unsupported skeleton schema ", "path", c.New.SkeletonPath)
   271  
   272  		}
   273  		// TODO check to see if the path needs to be extracted
   274  	} else {
   275  		utils.Logger.Fatal("Invalid skeleton path format", "path", c.New.SkeletonPath)
   276  	}
   277  	return
   278  }
   279  
   280  // Load skeleton from git
   281  func newLoadFromGit(c *model.CommandConfig, sp *url.URL) (err error) {
   282  	// This method indicates we need to fetch from a repository using git
   283  	// Execute "git clone get <pkg>"
   284  	targetPath := filepath.Join(os.TempDir(), "revel", "skeleton")
   285  	os.RemoveAll(targetPath)
   286  	pathpart := strings.Split(sp.Path, ":")
   287  	getCmd := exec.Command("git", "clone", sp.Scheme + "://" + sp.Host + pathpart[0], targetPath)
   288  	utils.Logger.Info("Exec:", "args", getCmd.Args)
   289  	getOutput, err := getCmd.CombinedOutput()
   290  	if err != nil {
   291  		utils.Logger.Fatal("Abort: could not clone the  Skeleton  source code: ", "output", string(getOutput), "path", c.New.SkeletonPath)
   292  	}
   293  	outputPath := targetPath
   294  	if len(pathpart) > 1 {
   295  		outputPath = filepath.Join(targetPath, filepath.Join(strings.Split(pathpart[1], string('/'))...))
   296  	}
   297  	outputPath, _ = filepath.Abs(outputPath)
   298  	if !strings.HasPrefix(outputPath, targetPath) {
   299  		utils.Logger.Fatal("Unusual target path outside root path", "target", outputPath, "root", targetPath)
   300  	}
   301  
   302  	c.New.SkeletonPath = outputPath
   303  	return
   304  }
   305  
   306  func copyNewAppFiles(c *model.CommandConfig) (err error) {
   307  	err = os.MkdirAll(c.AppPath, 0777)
   308  	if err != nil {
   309  		return utils.NewBuildIfError(err, "MKDIR failed")
   310  	}
   311  
   312  	err = utils.CopyDir(c.AppPath, c.New.SkeletonPath, map[string]interface{}{
   313  		// app.conf
   314  		"AppName":  c.AppName,
   315  		"BasePath": c.AppPath,
   316  		"Secret":   generateSecret(),
   317  	})
   318  	if err != nil {
   319  		fmt.Printf("err %v", err)
   320  		return utils.NewBuildIfError(err, "Copy Dir failed")
   321  	}
   322  
   323  	// Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore.
   324  	gitignore := ".gitignore"
   325  	return utils.CopyFile(filepath.Join(c.AppPath, gitignore), filepath.Join(c.New.SkeletonPath, gitignore))
   326  
   327  }
   328  
   329  const (
   330  	VENDOR_GOPKG = `#
   331  # Revel Gopkg.toml
   332  #
   333  # If you want to use a specific version of Revel change the branches below
   334  #
   335  # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
   336  # for detailed Gopkg.toml documentation.
   337  #
   338  # required = ["github.com/user/thing/cmd/thing"]
   339  # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
   340  #
   341  # [[constraint]]
   342  #   name = "github.com/user/project"
   343  #   version = "1.0.0"
   344  #
   345  # [[constraint]]
   346  #   name = "github.com/user/project2"
   347  #   branch = "dev"
   348  #   source = "github.com/myfork/project2"
   349  #
   350  # [[override]]
   351  #  name = "github.com/x/y"
   352  #  version = "2.4.0"
   353  required = ["github.com/revel/revel", "github.com/revel/modules"]
   354  
   355  # Note to use a specific version changes this to
   356  #
   357  # [[override]]
   358  #   version = "0.20.1"
   359  #   name = "github.com/revel/modules"
   360  
   361  [[override]]
   362    branch = "master"
   363    name = "github.com/revel/modules"
   364  
   365  # Note to use a specific version changes this to
   366  #
   367  # [[override]]
   368  #   version = "0.20.0"
   369  #   name = "github.com/revel/revel"
   370  [[override]]
   371    branch = "master"
   372    name = "github.com/revel/revel"
   373  
   374  [[override]]
   375    branch = "master"
   376    name = "github.com/revel/log15"
   377  
   378  [[override]]
   379    branch = "master"
   380    name = "github.com/revel/cron"
   381  
   382  [[override]]
   383    branch = "master"
   384    name = "github.com/xeonx/timeago"
   385  
   386  `
   387  	NEW_MAIN_FILE = `package main
   388  
   389  	`
   390  )