github.com/wiselike/revel-cmd@v1.2.1/utils/file.go (about) 1 package utils 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "compress/gzip" 7 "errors" 8 "fmt" 9 "html/template" 10 "io" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "strings" 15 16 "golang.org/x/tools/go/packages" 17 ) 18 19 // DirExists returns true if the given path exists and is a directory. 20 func DirExists(filename string) bool { 21 fileInfo, err := os.Stat(filename) 22 return err == nil && fileInfo.IsDir() 23 } 24 25 // MustReadLines reads the lines of the given file. Panics in the case of error. 26 func MustReadLines(filename string) []string { 27 r, err := ReadLines(filename) 28 if err != nil { 29 panic(err) 30 } 31 return r 32 } 33 34 // ReadLines reads the lines of the given file. Panics in the case of error. 35 func ReadLines(filename string) ([]string, error) { 36 dataBytes, err := ioutil.ReadFile(filename) 37 if err != nil { 38 return nil, err 39 } 40 return strings.Split(string(dataBytes), "\n"), nil 41 } 42 43 // Copy file returns error. 44 func CopyFile(destFilename, srcFilename string) (err error) { 45 destFile, err := os.Create(destFilename) 46 if err != nil { 47 return NewBuildIfError(err, "Failed to create file", "file", destFilename) 48 } 49 50 srcFile, err := os.Open(srcFilename) 51 if err != nil { 52 return NewBuildIfError(err, "Failed to open file", "file", srcFilename) 53 } 54 55 _, err = io.Copy(destFile, srcFile) 56 if err != nil { 57 return NewBuildIfError(err, "Failed to copy data", "fromfile", srcFilename, "tofile", destFilename) 58 } 59 60 err = destFile.Close() 61 if err != nil { 62 return NewBuildIfError(err, "Failed to close file", "file", destFilename) 63 } 64 65 err = srcFile.Close() 66 if err != nil { 67 return NewBuildIfError(err, "Failed to close file", "file", srcFilename) 68 } 69 70 return 71 } 72 73 // GenerateTemplate renders the given template to produce source code, which it writes 74 // to the given file. 75 func GenerateTemplate(filename, templateSource string, args map[string]interface{}) (err error) { 76 tmpl := template.Must(template.New("").Parse(templateSource)) 77 78 var b bytes.Buffer 79 if err = tmpl.Execute(&b, args); err != nil { 80 return NewBuildIfError(err, "ExecuteTemplate: Execute failed") 81 } 82 sourceCode := b.String() 83 filePath := filepath.Dir(filename) 84 if !DirExists(filePath) { 85 err = os.MkdirAll(filePath, 0777) 86 if err != nil && !os.IsExist(err) { 87 return NewBuildIfError(err, "Failed to make directory", "dir", filePath) 88 } 89 } 90 91 // Create the file 92 file, err := os.Create(filename) 93 if err != nil { 94 Logger.Fatal("Failed to create file", "error", err) 95 return 96 } 97 defer func() { 98 _ = file.Close() 99 }() 100 101 if _, err = file.WriteString(sourceCode); err != nil { 102 Logger.Fatal("Failed to write to file: ", "error", err) 103 } 104 105 return 106 } 107 108 // Given the target path and source path and data. A template. 109 func RenderTemplate(destPath, srcPath string, data interface{}) (err error) { 110 tmpl, err := template.ParseFiles(srcPath) 111 if err != nil { 112 return NewBuildIfError(err, "Failed to parse template "+srcPath) 113 } 114 115 f, err := os.Create(destPath) 116 if err != nil { 117 return NewBuildIfError(err, "Failed to create ", "path", destPath) 118 } 119 120 err = tmpl.Execute(f, data) 121 if err != nil { 122 return NewBuildIfError(err, "Failed to Render template "+srcPath) 123 } 124 125 err = f.Close() 126 if err != nil { 127 return NewBuildIfError(err, "Failed to close file stream "+destPath) 128 } 129 return 130 } 131 132 // Given the target path and source path and data. A template. 133 func RenderTemplateToStream(output io.Writer, srcPath []string, data interface{}) (err error) { 134 tmpl, err := template.ParseFiles(srcPath...) 135 if err != nil { 136 return NewBuildIfError(err, "Failed to parse template "+srcPath[0]) 137 } 138 139 err = tmpl.Execute(output, data) 140 if err != nil { 141 return NewBuildIfError(err, "Failed to render template "+srcPath[0]) 142 } 143 return 144 } 145 146 func MustChmod(filename string, mode os.FileMode) { 147 err := os.Chmod(filename, mode) 148 PanicOnError(err, fmt.Sprintf("Failed to chmod %d %q", mode, filename)) 149 } 150 151 // Called if panic. 152 func PanicOnError(err error, msg string) { 153 var serr *SourceError 154 if (errors.As(err, &serr) && serr != nil) || err != nil { 155 Logger.Panicf("Abort: %s: %s %s", msg, serr, err) 156 } 157 } 158 159 // copyDir copies a directory tree over to a new directory. Any files ending in 160 // ".template" are treated as a Go template and rendered using the given data. 161 // Additionally, the trailing ".template" is stripped from the file name. 162 // Also, dot files and dot directories are skipped. 163 func CopyDir(destDir, srcDir string, data map[string]interface{}) error { 164 if !DirExists(srcDir) { 165 return nil 166 } 167 return fsWalk(srcDir, srcDir, func(srcPath string, info os.FileInfo, err error) error { 168 // Get the relative path from the source base, and the corresponding path in 169 // the dest directory. 170 relSrcPath := strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator)) 171 destPath := filepath.Join(destDir, relSrcPath) 172 173 // Skip dot files and dot directories. 174 if strings.HasPrefix(relSrcPath, ".") { 175 if info.IsDir() { 176 return filepath.SkipDir 177 } 178 return nil 179 } 180 181 // Create a subdirectory if necessary. 182 if info.IsDir() { 183 err := os.MkdirAll(filepath.Join(destDir, relSrcPath), 0777) 184 if !os.IsExist(err) { 185 return NewBuildIfError(err, "Failed to create directory", "path", destDir+"/"+relSrcPath) 186 } 187 return nil 188 } 189 190 // If this file ends in ".template", render it as a template. 191 if strings.HasSuffix(relSrcPath, ".template") { 192 return RenderTemplate(destPath[:len(destPath)-len(".template")], srcPath, data) 193 } 194 195 // Else, just copy it over. 196 197 return CopyFile(destPath, srcPath) 198 }) 199 } 200 201 // Shortcut to fsWalk. 202 func Walk(root string, walkFn filepath.WalkFunc) error { 203 return fsWalk(root, root, walkFn) 204 } 205 206 // Walk the path tree using the function 207 // Every file found will call the function. 208 func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error { 209 fsWalkFunc := func(path string, info os.FileInfo, err error) error { 210 if err != nil { 211 return err 212 } 213 214 var name string 215 name, err = filepath.Rel(fname, path) 216 if err != nil { 217 return err 218 } 219 220 path = filepath.Join(linkName, name) 221 222 if err == nil && info.Mode()&os.ModeSymlink == os.ModeSymlink { 223 var symlinkPath string 224 symlinkPath, err = filepath.EvalSymlinks(path) 225 if err != nil { 226 return err 227 } 228 229 // https://github.com/golang/go/blob/master/src/path/filepath/path.go#L392 230 info, err = os.Lstat(symlinkPath) 231 232 if err != nil { 233 return walkFn(path, info, err) 234 } 235 236 if info.IsDir() { 237 return fsWalk(symlinkPath, path, walkFn) 238 } 239 } 240 241 return walkFn(path, info, err) 242 } 243 err := filepath.Walk(fname, fsWalkFunc) 244 return err 245 } 246 247 // Tar gz the folder. 248 func TarGzDir(destFilename, srcDir string) (name string, err error) { 249 zipFile, err := os.Create(destFilename) 250 if err != nil { 251 return "", NewBuildIfError(err, "Failed to create archive", "file", destFilename) 252 } 253 254 defer func() { 255 _ = zipFile.Close() 256 }() 257 258 gzipWriter := gzip.NewWriter(zipFile) 259 defer func() { 260 _ = gzipWriter.Close() 261 }() 262 263 tarWriter := tar.NewWriter(gzipWriter) 264 defer func() { 265 _ = tarWriter.Close() 266 }() 267 268 err = fsWalk(srcDir, srcDir, func(srcPath string, info os.FileInfo, err error) error { 269 if err != nil { 270 Logger.Debugf("error in walkFn: %s", err) 271 } 272 273 if info.IsDir() { 274 return nil 275 } 276 277 srcFile, err := os.Open(srcPath) 278 if err != nil { 279 return NewBuildIfError(err, "Failed to read file", "file", srcPath) 280 } 281 282 defer func() { 283 _ = srcFile.Close() 284 }() 285 286 err = tarWriter.WriteHeader(&tar.Header{ 287 Name: strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator)), 288 Size: info.Size(), 289 Mode: int64(info.Mode()), 290 ModTime: info.ModTime(), 291 }) 292 if err != nil { 293 return NewBuildIfError(err, "Failed to write tar entry header", "file", srcPath) 294 } 295 296 _, err = io.Copy(tarWriter, srcFile) 297 if err != nil { 298 return NewBuildIfError(err, "Failed to copy file", "file", srcPath) 299 } 300 301 return nil 302 }) 303 304 return zipFile.Name(), err 305 } 306 307 // Return true if the file exists. 308 func Exists(filename string) bool { 309 _, err := os.Stat(filename) 310 return err == nil 311 } 312 313 // empty returns true if the given directory is empty. 314 // the directory must exist. 315 func Empty(dirname string) bool { 316 if !DirExists(dirname) { 317 return true 318 } 319 dir, err := os.Open(dirname) 320 if err != nil { 321 Logger.Infof("error opening directory: %s", err) 322 return false 323 } 324 defer func() { 325 _ = dir.Close() 326 }() 327 results, _ := dir.Readdir(1) 328 return len(results) == 0 329 } 330 331 // Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory. 332 func FindSrcPaths(appPath string, packageList []string, packageResolver func(pkgName string) error) (sourcePathsmap map[string]string, err error) { 333 sourcePathsmap, missingList, err := findSrcPaths(appPath, packageList) 334 if err != nil && packageResolver != nil || len(missingList) > 0 { 335 Logger.Info("Failed to find package, attempting to call resolver for missing packages", "missing packages", missingList) 336 for _, item := range missingList { 337 if err = packageResolver(item); err != nil { 338 return 339 } 340 } 341 sourcePathsmap, missingList, err = findSrcPaths(appPath, packageList) 342 } 343 if err != nil && len(missingList) > 0 { 344 for _, missing := range missingList { 345 Logger.Error("Unable to import this package", "package", missing) 346 } 347 } 348 349 return 350 } 351 352 // Error is used for constant errors. 353 type Error string 354 355 // Error implements the error interface. 356 func (e Error) Error() string { 357 return string(e) 358 } 359 360 var ( 361 ErrNoApp Error = "no app found" 362 ErrNoRevel Error = "no revel found" 363 ) 364 365 // Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory. 366 func findSrcPaths(appPath string, packagesList []string) (sourcePathsmap map[string]string, missingList []string, err error) { 367 // Use packages to fetch 368 // by not specifying env, we will use the default env 369 config := &packages.Config{ 370 Mode: packages.NeedName | packages.NeedFiles | packages.NeedDeps, 371 Dir: appPath, 372 } 373 config.Env = ReducedEnv(false) 374 sourcePathsmap = map[string]string{} 375 Logger.Infof("Environment path %s root %s config env %s", os.Getenv("GOPATH"), os.Getenv("GOROOT"), config.Env) 376 377 pkgs, err := packages.Load(config, packagesList...) 378 Logger.Infof("Environment path %s root %s config env %s", os.Getenv("GOPATH"), os.Getenv("GOROOT"), config.Env) 379 Logger.Info("Loaded packages ", "len results", len(pkgs), "error", err, "basedir", appPath) 380 for _, packageName := range packagesList { 381 found := false 382 log := Logger.New("seeking", packageName) 383 for _, pck := range pkgs { 384 log.Info("Found package", "package", pck.ID) 385 if pck.ID == packageName { 386 if pck.Errors != nil && len(pck.Errors) > 0 { 387 log.Error("Error ", "count", len(pck.Errors), "App Import Path", pck.ID, "filesystem path", pck.PkgPath, "errors", pck.Errors) 388 // continue 389 } 390 // a,_ := pck.MarshalJSON() 391 log.Info("Found ", "count", len(pck.GoFiles), "App Import Path", pck.ID, "apppath", appPath) 392 if len(pck.GoFiles) > 0 { 393 sourcePathsmap[packageName] = filepath.Dir(pck.GoFiles[0]) 394 found = true 395 } 396 } 397 } 398 if !found { 399 if packageName == "github.com/wiselike/revel" { 400 err = ErrNoRevel 401 } else { 402 err = ErrNoApp 403 } 404 missingList = append(missingList, packageName) 405 } 406 } 407 408 return 409 }