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 }