github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/lang/preview.go (about)

     1  package lang
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"sync"
     7  
     8  	"github.com/lmorg/murex/builtins/pipes/streams"
     9  	"github.com/lmorg/murex/lang/expressions/functions"
    10  	"github.com/lmorg/murex/lang/stdio"
    11  	"github.com/lmorg/murex/utils/lists"
    12  	"github.com/lmorg/murex/utils/parser"
    13  )
    14  
    15  var previewCache = new(previewCacheT)
    16  
    17  type previewCacheT struct {
    18  	mutex sync.Mutex
    19  	err   error
    20  	raw   []string
    21  	cache []cacheBytesT
    22  	dt    []cacheDataTypeT
    23  }
    24  
    25  type cacheT struct {
    26  	b   *cacheBytesT
    27  	dt  *cacheDataTypeT
    28  	tee cacheStreamT
    29  	use bool
    30  }
    31  
    32  type cacheStreamT struct {
    33  	stdout stdio.Io
    34  	stderr stdio.Io
    35  }
    36  
    37  type cacheBytesT struct {
    38  	stdout []byte
    39  	stderr []byte
    40  }
    41  
    42  type cacheDataTypeT struct {
    43  	stdout string
    44  	stderr string
    45  }
    46  
    47  func PreviewInit() {
    48  	previewCache = new(previewCacheT)
    49  }
    50  
    51  const errPressF9 = "for your safety, press [f9] to confirm preview reload"
    52  
    53  // compare returns:
    54  // -1     == new / no match
    55  // len(s) == all matched
    56  func (pc *previewCacheT) compare(s []string) (int, error) {
    57  	if len(pc.raw) > 0 {
    58  
    59  		if len(s) != len(pc.raw) {
    60  			return 0, fmt.Errorf("new commands added to the command line: %s", errPressF9)
    61  		}
    62  
    63  	}
    64  
    65  	sLen, rLen := len(s), len(pc.raw)
    66  
    67  	var i int
    68  	for ; i < sLen && i < rLen; i++ {
    69  		if s[i] != pc.raw[i] {
    70  			break
    71  		}
    72  	}
    73  
    74  	return i - 1, nil
    75  }
    76  
    77  func (pc *previewCacheT) grow(s []string) {
    78  	pc.raw = s
    79  
    80  	cache := make([]cacheBytesT, len(s))
    81  	copy(cache, pc.cache)
    82  	pc.cache = cache
    83  
    84  	dt := make([]cacheDataTypeT, len(s))
    85  	copy(dt, pc.dt)
    86  	pc.dt = dt
    87  }
    88  
    89  func (pc *previewCacheT) compile(tree *[]functions.FunctionT, procs *[]Process) error {
    90  	pc.mutex.Lock()
    91  	defer pc.mutex.Unlock()
    92  
    93  	if pc.err != nil {
    94  		return pc.err
    95  	}
    96  
    97  	s := make([]string, len(*tree))
    98  	for i := range s {
    99  		s[i] = string((*tree)[i].Raw)
   100  	}
   101  
   102  	offset, err := pc.compare(s)
   103  	if err != nil {
   104  		pc.err = err
   105  		return err
   106  	}
   107  
   108  	if len(pc.raw) > 0 {
   109  
   110  		safe := parser.GetSafeCmds()
   111  		for i := offset + 1; i < len(*tree)-1; i++ {
   112  			cmd := string((*tree)[i].CommandName())
   113  		check:
   114  			switch {
   115  			case cmd == ExpressionFunctionName:
   116  				continue
   117  			case cmd == "exec" && !strings.HasPrefix(s[i], cmd):
   118  				if len((*tree)[i].Parameters) > 0 {
   119  					cmd = string((*tree)[i].Parameters[0])
   120  					goto check
   121  				}
   122  			case !strings.HasPrefix(s[i], cmd):
   123  				return fmt.Errorf("a command executable has changed name: %s", errPressF9)
   124  			case !lists.Match(safe, cmd):
   125  				return fmt.Errorf("a command line change has been made prior to potentially unsafe commands: %s", errPressF9)
   126  			}
   127  		}
   128  
   129  	}
   130  
   131  	pc.grow(s)
   132  
   133  	for i := 0; i < len(*procs)-1; i++ {
   134  		(*procs)[i].cache = new(cacheT)
   135  		(*procs)[i].cache.b = &pc.cache[i]
   136  		(*procs)[i].cache.dt = &pc.dt[i]
   137  		(*procs)[i].Stdout, (*procs)[i].cache.tee.stdout = streams.NewTee((*procs)[i].Stdout)
   138  		(*procs)[i].Stderr, (*procs)[i].cache.tee.stderr = streams.NewTee((*procs)[i].Stderr)
   139  
   140  		if i <= offset {
   141  			(*procs)[i].cache.use = true
   142  		}
   143  	}
   144  
   145  	return nil
   146  }