github.com/yang-ricky/air@v1.30.0/runner/util.go (about)

     1  package runner
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/hex"
     6  	"errors"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/fsnotify/fsnotify"
    14  )
    15  
    16  func (e *Engine) mainLog(format string, v ...interface{}) {
    17  	e.logWithLock(func() {
    18  		e.logger.main()(format, v...)
    19  	})
    20  }
    21  
    22  func (e *Engine) mainDebug(format string, v ...interface{}) {
    23  	if e.debugMode {
    24  		e.mainLog(format, v...)
    25  	}
    26  }
    27  
    28  func (e *Engine) buildLog(format string, v ...interface{}) {
    29  	e.logWithLock(func() {
    30  		e.logger.build()(format, v...)
    31  	})
    32  }
    33  
    34  func (e *Engine) runnerLog(format string, v ...interface{}) {
    35  	e.logWithLock(func() {
    36  		e.logger.runner()(format, v...)
    37  	})
    38  }
    39  
    40  func (e *Engine) watcherLog(format string, v ...interface{}) {
    41  	e.logWithLock(func() {
    42  		e.logger.watcher()(format, v...)
    43  	})
    44  }
    45  
    46  func (e *Engine) watcherDebug(format string, v ...interface{}) {
    47  	if e.debugMode {
    48  		e.watcherLog(format, v...)
    49  	}
    50  }
    51  
    52  func (e *Engine) isTmpDir(path string) bool {
    53  	return path == e.config.tmpPath()
    54  }
    55  
    56  func (e *Engine) isTestDataDir(path string) bool {
    57  	return path == e.config.TestDataPath()
    58  }
    59  
    60  func isHiddenDirectory(path string) bool {
    61  	return len(path) > 1 && strings.HasPrefix(filepath.Base(path), ".")
    62  }
    63  
    64  func cleanPath(path string) string {
    65  	return strings.TrimSuffix(strings.TrimSpace(path), "/")
    66  }
    67  
    68  func (e *Engine) isExcludeDir(path string) bool {
    69  	cleanName := cleanPath(e.config.rel(path))
    70  	for _, d := range e.config.Build.ExcludeDir {
    71  		if cleanName == d {
    72  			return true
    73  		}
    74  	}
    75  	return false
    76  }
    77  
    78  // return isIncludeDir, walkDir
    79  func (e *Engine) checkIncludeDir(path string) (bool, bool) {
    80  	cleanName := cleanPath(e.config.rel(path))
    81  	iDir := e.config.Build.IncludeDir
    82  	if len(iDir) == 0 { // ignore empty
    83  		return true, true
    84  	}
    85  	if cleanName == "." {
    86  		return false, true
    87  	}
    88  	walkDir := false
    89  	for _, d := range iDir {
    90  		if d == cleanName {
    91  			return true, true
    92  		}
    93  		if strings.HasPrefix(cleanName, d) { // current dir is sub-directory of `d`
    94  			return true, true
    95  		}
    96  		if strings.HasPrefix(d, cleanName) { // `d` is sub-directory of current dir
    97  			walkDir = true
    98  		}
    99  	}
   100  	return false, walkDir
   101  }
   102  
   103  func (e *Engine) isIncludeExt(path string) bool {
   104  	ext := filepath.Ext(path)
   105  	for _, v := range e.config.Build.IncludeExt {
   106  		if ext == "."+strings.TrimSpace(v) {
   107  			return true
   108  		}
   109  	}
   110  	return false
   111  }
   112  
   113  func (e *Engine) isExcludeRegex(path string) (bool, error) {
   114  	regexes, err := e.config.Build.RegexCompiled()
   115  	if err != nil {
   116  		return false, err
   117  	}
   118  	for _, re := range regexes {
   119  		if re.Match([]byte(path)) {
   120  			return true, nil
   121  		}
   122  	}
   123  	return false, nil
   124  }
   125  
   126  func (e *Engine) isExcludeFile(path string) bool {
   127  	cleanName := cleanPath(e.config.rel(path))
   128  	for _, d := range e.config.Build.ExcludeFile {
   129  		matched, err := filepath.Match(d, cleanName)
   130  		if err == nil && matched {
   131  			return true
   132  		}
   133  	}
   134  	return false
   135  }
   136  
   137  func (e *Engine) writeBuildErrorLog(msg string) error {
   138  	var err error
   139  	f, err := os.OpenFile(e.config.buildLogPath(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	if _, err = f.Write([]byte(msg)); err != nil {
   144  		return err
   145  	}
   146  	return f.Close()
   147  }
   148  
   149  func (e *Engine) withLock(f func()) {
   150  	e.mu.Lock()
   151  	f()
   152  	e.mu.Unlock()
   153  }
   154  
   155  func (e *Engine) logWithLock(f func()) {
   156  	e.ll.Lock()
   157  	f()
   158  	e.ll.Unlock()
   159  }
   160  
   161  func expandPath(path string) (string, error) {
   162  	if strings.HasPrefix(path, "~/") {
   163  		home := os.Getenv("HOME")
   164  		return home + path[1:], nil
   165  	}
   166  	var err error
   167  	wd, err := os.Getwd()
   168  	if err != nil {
   169  		return "", err
   170  	}
   171  	if path == "." {
   172  		return wd, nil
   173  	}
   174  	if strings.HasPrefix(path, "./") {
   175  		return wd + path[1:], nil
   176  	}
   177  	return path, nil
   178  }
   179  
   180  func isDir(path string) bool {
   181  	i, err := os.Stat(path)
   182  	if err != nil {
   183  		return false
   184  	}
   185  	return i.IsDir()
   186  }
   187  
   188  func validEvent(ev fsnotify.Event) bool {
   189  	return ev.Op&fsnotify.Create == fsnotify.Create ||
   190  		ev.Op&fsnotify.Write == fsnotify.Write ||
   191  		ev.Op&fsnotify.Remove == fsnotify.Remove
   192  }
   193  
   194  func removeEvent(ev fsnotify.Event) bool {
   195  	return ev.Op&fsnotify.Remove == fsnotify.Remove
   196  }
   197  
   198  func cmdPath(path string) string {
   199  	return strings.Split(path, " ")[0]
   200  }
   201  
   202  func adaptToVariousPlatforms(c *config) {
   203  	// Fix the default configuration is not used in Windows
   204  	// Use the unix configuration on Windows
   205  	if runtime.GOOS == PlatformWindows {
   206  
   207  		runName := "start"
   208  		extName := ".exe"
   209  		originBin := c.Build.Bin
   210  		
   211  		if 0 < len(c.Build.FullBin) {
   212  
   213  			if !strings.HasSuffix(c.Build.FullBin, extName) {
   214  
   215  				c.Build.FullBin += extName
   216  			}
   217  			if !strings.HasPrefix(c.Build.FullBin, runName) {
   218  				c.Build.FullBin = runName + " /wait /b " + c.Build.FullBin
   219  			}
   220  		}
   221  
   222  		// bin=/tmp/main  cmd=go build -o ./tmp/main.exe main.go
   223  		if !strings.Contains(c.Build.Cmd, c.Build.Bin) && strings.Contains(c.Build.Cmd, originBin) {
   224  			c.Build.Cmd = strings.Replace(c.Build.Cmd, originBin, c.Build.Bin, 1)
   225  		}
   226  	}
   227  }
   228  
   229  // fileChecksum returns a checksum for the given file's contents.
   230  func fileChecksum(filename string) (checksum string, err error) {
   231  	contents, err := os.ReadFile(filename)
   232  	if err != nil {
   233  		return "", err
   234  	}
   235  
   236  	// If the file is empty, an editor might've been in the process of rewriting the file when we read it.
   237  	// This can happen often if editors are configured to run format after save.
   238  	// Instead of calculating a new checksum, we'll assume the file was unchanged, but return an error to force a rebuild anyway.
   239  	if len(contents) == 0 {
   240  		return "", errors.New("empty file, forcing rebuild without updating checksum")
   241  	}
   242  
   243  	h := sha256.New()
   244  	if _, err := h.Write(contents); err != nil {
   245  		return "", err
   246  	}
   247  
   248  	return hex.EncodeToString(h.Sum(nil)), nil
   249  }
   250  
   251  // checksumMap is a thread-safe map to store file checksums.
   252  type checksumMap struct {
   253  	l sync.Mutex
   254  	m map[string]string
   255  }
   256  
   257  // update updates the filename with the given checksum if different.
   258  func (a *checksumMap) updateFileChecksum(filename, newChecksum string) (ok bool) {
   259  	a.l.Lock()
   260  	defer a.l.Unlock()
   261  	oldChecksum, ok := a.m[filename]
   262  	if !ok || oldChecksum != newChecksum {
   263  		a.m[filename] = newChecksum
   264  		return true
   265  	}
   266  	return false
   267  }