github.com/fairyhunter13/air@v1.40.5/runner/util.go (about)

     1  package runner
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/hex"
     6  	"errors"
     7  	"log"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"runtime"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  
    16  	"github.com/fsnotify/fsnotify"
    17  )
    18  
    19  const (
    20  	sliceCmdArgSeparator = ","
    21  )
    22  
    23  func (e *Engine) mainLog(format string, v ...interface{}) {
    24  	e.logWithLock(func() {
    25  		e.logger.main()(format, v...)
    26  	})
    27  }
    28  
    29  func (e *Engine) mainDebug(format string, v ...interface{}) {
    30  	if e.debugMode {
    31  		e.mainLog(format, v...)
    32  	}
    33  }
    34  
    35  func (e *Engine) buildLog(format string, v ...interface{}) {
    36  	e.logWithLock(func() {
    37  		e.logger.build()(format, v...)
    38  	})
    39  }
    40  
    41  func (e *Engine) runnerLog(format string, v ...interface{}) {
    42  	e.logWithLock(func() {
    43  		e.logger.runner()(format, v...)
    44  	})
    45  }
    46  
    47  func (e *Engine) watcherLog(format string, v ...interface{}) {
    48  	e.logWithLock(func() {
    49  		e.logger.watcher()(format, v...)
    50  	})
    51  }
    52  
    53  func (e *Engine) watcherDebug(format string, v ...interface{}) {
    54  	if e.debugMode {
    55  		e.watcherLog(format, v...)
    56  	}
    57  }
    58  
    59  func (e *Engine) isTmpDir(path string) bool {
    60  	return path == e.config.tmpPath()
    61  }
    62  
    63  func (e *Engine) isTestDataDir(path string) bool {
    64  	return path == e.config.testDataPath()
    65  }
    66  
    67  func isHiddenDirectory(path string) bool {
    68  	return len(path) > 1 && strings.HasPrefix(filepath.Base(path), ".")
    69  }
    70  
    71  func cleanPath(path string) string {
    72  	return strings.TrimSuffix(strings.TrimSpace(path), "/")
    73  }
    74  
    75  func (e *Engine) isExcludeDir(path string) bool {
    76  	cleanName := cleanPath(e.config.rel(path))
    77  	for _, d := range e.config.Build.ExcludeDir {
    78  		if cleanName == d {
    79  			return true
    80  		}
    81  	}
    82  	return false
    83  }
    84  
    85  // return isIncludeDir, walkDir
    86  func (e *Engine) checkIncludeDir(path string) (bool, bool) {
    87  	cleanName := cleanPath(e.config.rel(path))
    88  	iDir := e.config.Build.IncludeDir
    89  	if len(iDir) == 0 { // ignore empty
    90  		return true, true
    91  	}
    92  	if cleanName == "." {
    93  		return false, true
    94  	}
    95  	walkDir := false
    96  	for _, d := range iDir {
    97  		if d == cleanName {
    98  			return true, true
    99  		}
   100  		if strings.HasPrefix(cleanName, d) { // current dir is sub-directory of `d`
   101  			return true, true
   102  		}
   103  		if strings.HasPrefix(d, cleanName) { // `d` is sub-directory of current dir
   104  			walkDir = true
   105  		}
   106  	}
   107  	return false, walkDir
   108  }
   109  
   110  func (e *Engine) isIncludeExt(path string) bool {
   111  	ext := filepath.Ext(path)
   112  	for _, v := range e.config.Build.IncludeExt {
   113  		if ext == "."+strings.TrimSpace(v) {
   114  			return true
   115  		}
   116  	}
   117  	return false
   118  }
   119  
   120  func (e *Engine) isExcludeRegex(path string) (bool, error) {
   121  	regexes, err := e.config.Build.RegexCompiled()
   122  	if err != nil {
   123  		return false, err
   124  	}
   125  	for _, re := range regexes {
   126  		if re.Match([]byte(path)) {
   127  			return true, nil
   128  		}
   129  	}
   130  	return false, nil
   131  }
   132  
   133  func (e *Engine) isExcludeFile(path string) bool {
   134  	cleanName := cleanPath(e.config.rel(path))
   135  	for _, d := range e.config.Build.ExcludeFile {
   136  		matched, err := filepath.Match(d, cleanName)
   137  		if err == nil && matched {
   138  			return true
   139  		}
   140  	}
   141  	return false
   142  }
   143  
   144  func (e *Engine) writeBuildErrorLog(msg string) error {
   145  	var err error
   146  	f, err := os.OpenFile(e.config.buildLogPath(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
   147  	if err != nil {
   148  		return err
   149  	}
   150  	if _, err = f.Write([]byte(msg)); err != nil {
   151  		return err
   152  	}
   153  	return f.Close()
   154  }
   155  
   156  func (e *Engine) withLock(f func()) {
   157  	e.mu.Lock()
   158  	f()
   159  	e.mu.Unlock()
   160  }
   161  
   162  func (e *Engine) logWithLock(f func()) {
   163  	e.ll.Lock()
   164  	f()
   165  	e.ll.Unlock()
   166  }
   167  
   168  func expandPath(path string) (string, error) {
   169  	if strings.HasPrefix(path, "~/") {
   170  		home := os.Getenv("HOME")
   171  		return home + path[1:], nil
   172  	}
   173  	var err error
   174  	wd, err := os.Getwd()
   175  	if err != nil {
   176  		return "", err
   177  	}
   178  	if path == "." {
   179  		return wd, nil
   180  	}
   181  	if strings.HasPrefix(path, "./") {
   182  		return wd + path[1:], nil
   183  	}
   184  	return path, nil
   185  }
   186  
   187  func isDir(path string) bool {
   188  	i, err := os.Stat(path)
   189  	if err != nil {
   190  		return false
   191  	}
   192  	return i.IsDir()
   193  }
   194  
   195  func validEvent(ev fsnotify.Event) bool {
   196  	return ev.Op&fsnotify.Create == fsnotify.Create ||
   197  		ev.Op&fsnotify.Write == fsnotify.Write ||
   198  		ev.Op&fsnotify.Remove == fsnotify.Remove
   199  }
   200  
   201  func removeEvent(ev fsnotify.Event) bool {
   202  	return ev.Op&fsnotify.Remove == fsnotify.Remove
   203  }
   204  
   205  func cmdPath(path string) string {
   206  	return strings.Split(path, " ")[0]
   207  }
   208  
   209  func adaptToVariousPlatforms(c *Config) {
   210  	// Fix the default configuration is not used in Windows
   211  	// Use the unix configuration on Windows
   212  	if runtime.GOOS == PlatformWindows {
   213  
   214  		runName := "start"
   215  		extName := ".exe"
   216  		originBin := c.Build.Bin
   217  
   218  		if 0 < len(c.Build.FullBin) {
   219  
   220  			if !strings.HasSuffix(c.Build.FullBin, extName) {
   221  
   222  				c.Build.FullBin += extName
   223  			}
   224  			if !strings.HasPrefix(c.Build.FullBin, runName) {
   225  				c.Build.FullBin = runName + " /wait /b " + c.Build.FullBin
   226  			}
   227  		}
   228  
   229  		// bin=/tmp/main  cmd=go build -o ./tmp/main.exe main.go
   230  		if !strings.Contains(c.Build.Cmd, c.Build.Bin) && strings.Contains(c.Build.Cmd, originBin) {
   231  			c.Build.Cmd = strings.Replace(c.Build.Cmd, originBin, c.Build.Bin, 1)
   232  		}
   233  	}
   234  }
   235  
   236  // fileChecksum returns a checksum for the given file's contents.
   237  func fileChecksum(filename string) (checksum string, err error) {
   238  	contents, err := os.ReadFile(filename)
   239  	if err != nil {
   240  		return "", err
   241  	}
   242  
   243  	// If the file is empty, an editor might've been in the process of rewriting the file when we read it.
   244  	// This can happen often if editors are configured to run format after save.
   245  	// Instead of calculating a new checksum, we'll assume the file was unchanged, but return an error to force a rebuild anyway.
   246  	if len(contents) == 0 {
   247  		return "", errors.New("empty file, forcing rebuild without updating checksum")
   248  	}
   249  
   250  	h := sha256.New()
   251  	if _, err := h.Write(contents); err != nil {
   252  		return "", err
   253  	}
   254  
   255  	return hex.EncodeToString(h.Sum(nil)), nil
   256  }
   257  
   258  // checksumMap is a thread-safe map to store file checksums.
   259  type checksumMap struct {
   260  	l sync.Mutex
   261  	m map[string]string
   262  }
   263  
   264  // update updates the filename with the given checksum if different.
   265  func (a *checksumMap) updateFileChecksum(filename, newChecksum string) (ok bool) {
   266  	a.l.Lock()
   267  	defer a.l.Unlock()
   268  	oldChecksum, ok := a.m[filename]
   269  	if !ok || oldChecksum != newChecksum {
   270  		a.m[filename] = newChecksum
   271  		return true
   272  	}
   273  	return false
   274  }
   275  
   276  // TomlInfo is a struct for toml config file
   277  type TomlInfo struct {
   278  	fieldPath string
   279  	field     reflect.StructField
   280  	Value     *string
   281  }
   282  
   283  func setValue2Struct(v reflect.Value, fieldName string, value string) {
   284  	index := strings.Index(fieldName, ".")
   285  	if index == -1 && len(fieldName) == 0 {
   286  		return
   287  	}
   288  	fields := strings.Split(fieldName, ".")
   289  	var addressableVal reflect.Value
   290  	switch v.Type().String() {
   291  	case "*runner.Config":
   292  		addressableVal = v.Elem()
   293  	default:
   294  		addressableVal = v
   295  	}
   296  	if len(fields) == 1 {
   297  		// string slice int switch case
   298  		field := addressableVal.FieldByName(fieldName)
   299  		switch field.Kind() {
   300  		case reflect.String:
   301  			field.SetString(value)
   302  		case reflect.Slice:
   303  			if len(value) == 0 {
   304  				field.Set(reflect.ValueOf([]string{}))
   305  			} else {
   306  				field.Set(reflect.ValueOf(strings.Split(value, sliceCmdArgSeparator)))
   307  			}
   308  		case reflect.Int64:
   309  			i, _ := strconv.ParseInt(value, 10, 64)
   310  			field.SetInt(i)
   311  		case reflect.Int:
   312  			i, _ := strconv.Atoi(value)
   313  			field.SetInt(int64(i))
   314  		case reflect.Bool:
   315  			b, _ := strconv.ParseBool(value)
   316  			field.SetBool(b)
   317  		case reflect.Ptr:
   318  			field.SetString(value)
   319  		default:
   320  			log.Fatalf("unsupported type %s", v.FieldByName(fields[0]).Kind())
   321  		}
   322  	} else if len(fields) == 0 {
   323  		return
   324  	} else {
   325  		field := addressableVal.FieldByName(fields[0])
   326  		s2 := fieldName[index+1:]
   327  		setValue2Struct(field, s2, value)
   328  	}
   329  }
   330  
   331  // flatConfig ...
   332  func flatConfig(stut interface{}) map[string]TomlInfo {
   333  	m := make(map[string]TomlInfo)
   334  	t := reflect.TypeOf(stut)
   335  	setTage2Map("", t, m, "")
   336  	return m
   337  }
   338  
   339  func setTage2Map(root string, t reflect.Type, m map[string]TomlInfo, fieldPath string) {
   340  	for i := 0; i < t.NumField(); i++ {
   341  		field := t.Field(i)
   342  		tomlVal := field.Tag.Get("toml")
   343  		switch field.Type.Kind() {
   344  		case reflect.Struct:
   345  			path := fieldPath + field.Name + "."
   346  			setTage2Map(root+tomlVal+".", field.Type, m, path)
   347  		default:
   348  			if tomlVal == "" {
   349  				continue
   350  			}
   351  			tomlPath := root + tomlVal
   352  			path := fieldPath + field.Name
   353  			var v *string
   354  			str := ""
   355  			v = &str
   356  			m[tomlPath] = TomlInfo{field: field, Value: v, fieldPath: path}
   357  		}
   358  	}
   359  }