github.com/jmigpin/editor@v1.6.0/core/externalcmd.go (about)

     1  package core
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/jmigpin/editor/core/toolbarparser"
    12  	"github.com/jmigpin/editor/util/iout/iorw"
    13  	"github.com/jmigpin/editor/util/osutil"
    14  	"github.com/jmigpin/editor/util/parseutil"
    15  )
    16  
    17  func ExternalCmd(erow *ERow, part *toolbarparser.Part) {
    18  	env := []string{}
    19  	env = append(env, toolbarVarsEnv(part)...)
    20  	cargs := cmdPartArgs(part)
    21  
    22  	ExternalCmdFromArgs(erow, cargs, nil, env)
    23  }
    24  
    25  func ExternalCmdFromArgs(erow *ERow, cargs []string, fend func(error), env []string) {
    26  	env = append(env, populateEdEnvVars(erow, cargs)...)
    27  
    28  	switch {
    29  	case erow.Info.IsDir():
    30  		externalCmdFromDir(erow, cargs, fend, env)
    31  	case erow.Info.IsFileButNotDir():
    32  		// create a row with the file dir and run the cmd
    33  		dir := filepath.Dir(erow.Info.Name())
    34  		info := erow.Ed.ReadERowInfo(dir)
    35  		rowPos := erow.Row.PosBelow()
    36  		erow2 := NewBasicERow(info, rowPos)
    37  		externalCmdFromDir(erow2, cargs, fend, env)
    38  	default:
    39  		erow.Ed.Errorf("unable to run external cmd for erow: %v", erow.Info.Name())
    40  	}
    41  }
    42  
    43  //----------
    44  
    45  func externalCmdFromDir(erow *ERow, cargs []string, fend func(error), env []string) {
    46  	if !erow.Info.IsDir() {
    47  		panic("not a directory")
    48  	}
    49  	erow.Exec.RunAsync(func(ctx context.Context, rw io.ReadWriter) error {
    50  		err := externalCmdDir2(ctx, erow, cargs, env, rw)
    51  		if fend != nil {
    52  			fend(err)
    53  		}
    54  		return err
    55  	})
    56  }
    57  
    58  func externalCmdDir2(ctx context.Context, erow *ERow, cargs []string, env []string, rw io.ReadWriter) error {
    59  	cmd := osutil.NewCmd(ctx, cargs...)
    60  	cmd.Dir = erow.Info.Name()
    61  	cmd.Env = env
    62  
    63  	if err := cmd.SetupStdio(rw, rw, rw); err != nil {
    64  		return err
    65  	}
    66  
    67  	// output pid before any output
    68  	cmd.PreOutputCallback = func() {
    69  		cargsStr := strings.Join(cargs, " ")
    70  		fmt.Fprintf(rw, "# pid %d: %s\n", cmd.Process.Pid, cargsStr)
    71  	}
    72  
    73  	if err := cmd.Start(); err != nil {
    74  		return err
    75  	}
    76  	return cmd.Wait()
    77  }
    78  
    79  //----------
    80  //----------
    81  //----------
    82  
    83  func cmdPartArgs(part *toolbarparser.Part) []string {
    84  	var u []string
    85  	for _, a := range part.Args {
    86  		s := a.String()
    87  		if !parseutil.IsQuoted(s) {
    88  			s = parseutil.RemoveEscapesEscapable(s, osutil.EscapeRune, "|")
    89  		}
    90  		u = append(u, s)
    91  	}
    92  	return osutil.ShellRunArgs(u...)
    93  }
    94  
    95  //----------
    96  //----------
    97  //----------
    98  
    99  func populateEdEnvVars(erow *ERow, cargs []string) []string {
   100  	// Can't use os.Expand() to replace (and show the values in cargs) since the idea is for the variable to be available in scripting if wanted.
   101  
   102  	// supported env vars
   103  	m := map[string]func() string{
   104  		"edName": erow.Info.Name, // filename
   105  		"edDir":  erow.Info.Dir,  // directory
   106  		"edFileOffset": func() string { // filename + offset "filename:#123"
   107  			return cmdVar_edFileOffset(erow)
   108  		},
   109  		"edFileLine": func() string { // cursor line
   110  			return cmdVar_edFileLine(erow)
   111  		},
   112  		"edFileWord": func() string {
   113  			return cmdVar_edFileWord(erow)
   114  		},
   115  	}
   116  
   117  	// Deprecated: allow continued usage
   118  	m["edPosOffset"] = m["edFileOffset"]
   119  	m["edLine"] = m["edFileLine"]
   120  
   121  	// populate env vars only if detected
   122  	env := os.Environ()
   123  	for k, v := range m {
   124  		for _, s := range cargs {
   125  			if parseutil.DetectEnvVar(s, k) {
   126  				env = append(env, k+"="+v())
   127  				break
   128  			}
   129  		}
   130  	}
   131  
   132  	return env
   133  }
   134  
   135  func cmdVar_edFileOffset(erow *ERow) string {
   136  	offset := erow.Row.TextArea.CursorIndex()
   137  	posOffset := fmt.Sprintf("%v:#%v", erow.Info.Name(), offset)
   138  	return posOffset
   139  }
   140  
   141  func cmdVar_edFileLine(erow *ERow) string {
   142  	ta := erow.Row.TextArea
   143  	l, _, err := parseutil.IndexLineColumn(ta.RW(), ta.CursorIndex())
   144  	if err != nil {
   145  		return ""
   146  	}
   147  	return fmt.Sprintf("%v", l)
   148  }
   149  
   150  func cmdVar_edFileWord(erow *ERow) string {
   151  	ta := erow.Row.TextArea
   152  	b, _, err := iorw.WordAtIndex(ta.RW(), ta.CursorIndex())
   153  	if err != nil {
   154  		return ""
   155  	}
   156  	return string(b)
   157  }
   158  
   159  //----------
   160  
   161  func toolbarVarsEnv(part *toolbarparser.Part) []string {
   162  	data2 := *part.Data // copy
   163  
   164  	// use data only up to the selected part
   165  	for k, part2 := range data2.Parts {
   166  		if part2 == part {
   167  			data2.Parts = data2.Parts[:k+1]
   168  			break
   169  		}
   170  	}
   171  
   172  	env := []string{}
   173  	vmap := toolbarparser.ParseVars(&data2)
   174  	for k, v := range vmap {
   175  		if strings.HasPrefix(k, "$") {
   176  			u := k[1:]
   177  			env = append(env, u+"="+v)
   178  		}
   179  	}
   180  	return env
   181  }