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 )