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