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 )