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  }