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  }