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

     1  package expressions
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/lmorg/murex/lang"
     7  	"github.com/lmorg/murex/lang/expressions/primitives"
     8  	"github.com/lmorg/murex/lang/types"
     9  	"github.com/lmorg/murex/utils"
    10  	"github.com/lmorg/murex/utils/consts"
    11  )
    12  
    13  func (tree *ParserT) parseSubShell(exec bool, sigil rune, strOrVal varFormatting) ([]rune, primitives.FunctionT, error) {
    14  	start := tree.charPos
    15  
    16  	tree.charPos += 2
    17  
    18  	_, err := tree.parseBlockQuote()
    19  	if err != nil {
    20  		return nil, nil, err
    21  	}
    22  
    23  	value := tree.expression[start : tree.charPos+1]
    24  	block := tree.expression[start+2 : tree.charPos]
    25  
    26  	if !exec {
    27  		return value, nil, nil
    28  	}
    29  
    30  	switch sigil {
    31  	case '$':
    32  		fn := func() (*primitives.Value, error) {
    33  			return execSubShellScalar(tree, block, strOrVal)
    34  		}
    35  		return value, fn, nil
    36  
    37  	case '@':
    38  		fn := func() (*primitives.Value, error) {
    39  			return execSubShellArray(tree, block, strOrVal)
    40  		}
    41  		return value, fn, nil
    42  	default:
    43  		err = fmt.Errorf("invalid prefix in expression '%s'. %s", string(sigil), consts.IssueTrackerURL)
    44  		return nil, nil, err
    45  	}
    46  }
    47  
    48  func execSubShellScalar(tree *ParserT, block []rune, strOrVal varFormatting) (*primitives.Value, error) {
    49  	if tree.p == nil {
    50  		panic("`tree.p` is undefined")
    51  	}
    52  
    53  	val := new(primitives.Value)
    54  	var err error
    55  
    56  	fork := tree.p.Fork(lang.F_NO_STDIN | lang.F_PARENT_VARTABLE | lang.F_CREATE_STDOUT)
    57  	val.ExitNum, err = fork.Execute(block)
    58  	if err != nil {
    59  		return val, fmt.Errorf("subshell failed: %s", err.Error())
    60  	}
    61  	if val.ExitNum > 0 && tree.p.RunMode.IsStrict() {
    62  		return val, fmt.Errorf("subshell exit status %d", val.ExitNum)
    63  	}
    64  	b, err := fork.Stdout.ReadAll()
    65  	if err != nil {
    66  		return val, fmt.Errorf("cannot read from subshell's STDOUT: %s", err.Error())
    67  	}
    68  
    69  	b = utils.CrLfTrim(b)
    70  	val.DataType = fork.Stdout.GetDataType()
    71  
    72  	val.Value, err = formatBytes(b, val.DataType, strOrVal)
    73  	return val, err
    74  }
    75  
    76  func execSubShellArray(tree *ParserT, block []rune, strOrVal varFormatting) (*primitives.Value, error) {
    77  	var slice []interface{}
    78  	val := new(primitives.Value)
    79  	var err error
    80  
    81  	fork := tree.p.Fork(lang.F_NO_STDIN | lang.F_CREATE_STDOUT | lang.F_PARENT_VARTABLE)
    82  	val.ExitNum, err = fork.Execute(block)
    83  	if err != nil {
    84  		return val, fmt.Errorf("subshell failed: %s", err.Error())
    85  	}
    86  	if val.ExitNum > 0 && tree.p.RunMode.IsStrict() {
    87  		return val, fmt.Errorf("subshell exit status %d", val.ExitNum)
    88  	}
    89  
    90  	switch strOrVal {
    91  	case varAsString:
    92  		err = fork.Stdout.ReadArray(tree.p.Context, func(b []byte) {
    93  			slice = append(slice, string(b))
    94  		})
    95  	case varAsValue:
    96  		err = fork.Stdout.ReadArrayWithType(tree.p.Context, func(v interface{}, _ string) {
    97  			slice = append(slice, v)
    98  		})
    99  	default:
   100  		panic("invalid value set for strOrVal")
   101  	}
   102  	if err != nil {
   103  		return val, fmt.Errorf("cannot read from subshell's STDOUT: %s", err.Error())
   104  	}
   105  
   106  	if len(slice) == 0 && tree.StrictArrays() {
   107  		return nil, fmt.Errorf(errEmptyArray, "{"+string(block)+"}")
   108  	}
   109  
   110  	val.Value = slice
   111  	val.DataType = types.Json
   112  	return val, nil
   113  }