github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/builtins/events/onCommandCompletion/commandcompletion.go (about) 1 package oncommandcompletion 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "sync" 8 9 "github.com/lmorg/murex/builtins/events" 10 "github.com/lmorg/murex/builtins/pipes/term" 11 "github.com/lmorg/murex/lang" 12 "github.com/lmorg/murex/lang/ref" 13 ) 14 15 const eventType = "onCommandCompletion" 16 17 func init() { 18 event := newCommandCompletion() 19 events.AddEventType(eventType, event, nil) 20 lang.ShellProcess.CCEvent = event.callback 21 lang.ShellProcess.CCExists = event.exists 22 } 23 24 // Interrupt is a JSONable structure passed to the murex function 25 type Interrupt struct { 26 Command string 27 Parameters []string 28 Stdout string 29 Stderr string 30 ExitNum int 31 } 32 33 type commandCompletionEvent struct { 34 Command string 35 Block []rune 36 FileRef *ref.File 37 } 38 39 type commandCompletionEvents struct { 40 events map[string]commandCompletionEvent 41 mutex sync.Mutex 42 } 43 44 func newCommandCompletion() *commandCompletionEvents { 45 cce := new(commandCompletionEvents) 46 cce.events = make(map[string]commandCompletionEvent) 47 return cce 48 } 49 50 // Add a command to the onCommandCompleteionEvents 51 func (evt *commandCompletionEvents) Add(name, command string, block []rune, fileRef *ref.File) error { 52 if command == "exec" || command == "fexec" { 53 return errors.New("for safety reasons, you cannot assign this event against `exec` nor `fexec`") 54 } 55 56 evt.mutex.Lock() 57 58 evt.events[name] = commandCompletionEvent{ 59 Command: command, 60 Block: block, 61 FileRef: fileRef, 62 } 63 64 evt.mutex.Unlock() 65 66 return nil 67 } 68 69 func (evt *commandCompletionEvents) Remove(name string) error { 70 evt.mutex.Lock() 71 if evt.events[name].FileRef == nil { 72 evt.mutex.Unlock() 73 return fmt.Errorf("%s event %s does not exist", eventType, name) 74 } 75 76 delete(evt.events, name) 77 78 evt.mutex.Unlock() 79 return nil 80 } 81 82 func (evt *commandCompletionEvents) callback(command string, p *lang.Process) { 83 if command == "exec" || command == "fexec" { 84 return 85 } 86 87 evt.mutex.Lock() 88 89 //command := p.Name.String() 90 parameters := p.Parameters.StringArray() 91 92 if p.Name.String() == "exec" && len(parameters) > 0 { 93 command = parameters[0] 94 parameters = parameters[1:] 95 } 96 97 for name, cce := range evt.events { 98 if cce.Command == command { 99 evt.mutex.Unlock() 100 cce.execEvent(name, parameters, p) 101 evt.mutex.Lock() 102 } 103 } 104 105 evt.mutex.Unlock() 106 } 107 108 func (evt *commandCompletionEvents) exists(command string) bool { 109 evt.mutex.Lock() 110 111 for name := range evt.events { 112 if evt.events[name].Command == command { 113 evt.mutex.Unlock() 114 return true 115 } 116 } 117 118 evt.mutex.Unlock() 119 return false 120 } 121 122 func writeErr(desc string, err error, name string, cmd string) { 123 os.Stderr.WriteString( 124 fmt.Sprintf( 125 "ERROR: cannot execute event '%s' (command `%s`): %s: %s", 126 name, cmd, desc, err)) 127 } 128 129 func (cce *commandCompletionEvent) execEvent(name string, parameters []string, p *lang.Process) { 130 var err error 131 132 stdout := fmt.Sprintf("%d-out", p.Id) 133 stderr := fmt.Sprintf("%d-err", p.Id) 134 135 err = lang.GlobalPipes.ExposePipe(stdout, "std", p.CCOut) 136 if err != nil { 137 writeErr(fmt.Sprintf("cannot expose stdout pipe '%s'", stdout), err, name, cce.Command) 138 return 139 } 140 141 lang.GlobalPipes.ExposePipe(stderr, "std", p.CCErr) 142 if err != nil { 143 writeErr(fmt.Sprintf("cannot expose stderr pipe '%s'", stderr), err, name, cce.Command) 144 return 145 } 146 147 interrupt := Interrupt{ 148 Command: cce.Command, 149 Parameters: parameters, 150 Stdout: stdout, 151 Stderr: stderr, 152 ExitNum: p.ExitNum, 153 } 154 155 events.Callback(name, interrupt, cce.Block, cce.FileRef, term.NewErr(false), false) 156 157 lang.GlobalPipes.Delete(stdout) 158 lang.GlobalPipes.Delete(stderr) 159 } 160 161 func (evt *commandCompletionEvents) Dump() map[string]events.DumpT { 162 dump := make(map[string]events.DumpT) 163 164 evt.mutex.Lock() 165 166 for name, event := range evt.events { 167 dump[name] = events.DumpT{ 168 Interrupt: event.Command, 169 Block: string(event.Block), 170 FileRef: event.FileRef, 171 } 172 } 173 174 evt.mutex.Unlock() 175 return dump 176 }