github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/builtins/core/structs/foreach.go (about)

     1  package structs
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/lmorg/murex/lang"
     8  	"github.com/lmorg/murex/lang/parameters"
     9  	"github.com/lmorg/murex/lang/types"
    10  	"github.com/lmorg/murex/utils"
    11  	"github.com/lmorg/murex/utils/json"
    12  	"github.com/mattn/go-runewidth"
    13  )
    14  
    15  func init() {
    16  	lang.DefineMethod("foreach", cmdForEach, types.ReadArrayWithType, types.Any)
    17  }
    18  
    19  const (
    20  	foreachJmap = "--jmap"
    21  	foreachStep = "--step"
    22  )
    23  
    24  var argsForEach = &parameters.Arguments{
    25  	AllowAdditional: true,
    26  	Flags: map[string]string{
    27  		foreachJmap: types.Boolean,
    28  		foreachStep: types.Integer,
    29  	},
    30  }
    31  
    32  func cmdForEach(p *lang.Process) error {
    33  	flags, additional, err := p.Parameters.ParseFlags(argsForEach)
    34  	//flags := map[string]string{}
    35  	//additional := p.Parameters.StringArray()
    36  	//var err error
    37  	if err != nil {
    38  		p.Stdout.SetDataType(types.Null)
    39  		return err
    40  	}
    41  
    42  	switch {
    43  	case flags[foreachJmap] == types.TrueString:
    44  		return cmdForEachJmap(p)
    45  
    46  	default:
    47  		return cmdForEachDefault(p, flags, additional)
    48  	}
    49  }
    50  
    51  func convertToByte(v interface{}) ([]byte, error) {
    52  	s, err := types.ConvertGoType(v, types.String)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	return []byte(s.(string)), nil
    58  }
    59  
    60  func getSteps(flags map[string]string) (int, []interface{}, error) {
    61  	steps, err := types.ConvertGoType(flags[foreachStep], types.Integer)
    62  	if err != nil {
    63  		return 0, nil, fmt.Errorf(`expecting integer for %s, instead got "%s": %s`, foreachStep, flags[foreachStep], err.Error())
    64  	}
    65  
    66  	return steps.(int), make([]any, steps.(int)), nil
    67  }
    68  
    69  func cmdForEachDefault(p *lang.Process, flags map[string]string, additional []string) error {
    70  	dataType := p.Stdin.GetDataType()
    71  	if dataType == types.Json {
    72  		p.Stdout.SetDataType(types.JsonLines)
    73  	} else {
    74  		p.Stdout.SetDataType(dataType)
    75  	}
    76  
    77  	var (
    78  		block   []rune
    79  		varName string
    80  	)
    81  
    82  	switch len(additional) {
    83  	case 1:
    84  		varName = "!"
    85  		block = []rune(additional[0])
    86  
    87  	case 2:
    88  		varName = additional[0]
    89  		block = []rune(additional[1])
    90  
    91  	default:
    92  		return errors.New("invalid number of parameters")
    93  	}
    94  	if !types.IsBlockRune(block) {
    95  		return fmt.Errorf("invalid code block: `%s`", runewidth.Truncate(string(block), 70, "…"))
    96  	}
    97  
    98  	steps, slice, err := getSteps(flags)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	var (
   104  		step      int
   105  		iteration int
   106  	)
   107  
   108  	err = p.Stdin.ReadArrayWithType(p.Context, func(varValue interface{}, dataType string) {
   109  		if steps > 0 {
   110  			varValue, _ = marshal(p, varValue, dataType)
   111  			slice[step] = varValue
   112  			step++
   113  			if step == steps {
   114  				varValue = slice
   115  				dataType = types.Json
   116  				step = 0
   117  			} else {
   118  				return
   119  			}
   120  		}
   121  
   122  		iteration++
   123  		forEachInnerLoop(p, block, varName, varValue, dataType, iteration)
   124  	})
   125  
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	if steps > 0 && step > 0 {
   131  		forEachInnerLoop(p, block, varName, slice[:step], types.Json, iteration+1)
   132  	}
   133  
   134  	return nil
   135  }
   136  
   137  func marshal(p *lang.Process, v any, dataType string) (any, error) {
   138  	switch v.(type) {
   139  	case []byte:
   140  		if dataType != types.String && dataType != types.Generic {
   141  			return lang.UnmarshalDataBuffered(p, v.([]byte), dataType)
   142  		}
   143  	case string:
   144  		if dataType != types.String && dataType != types.Generic {
   145  			return lang.UnmarshalDataBuffered(p, []byte(v.(string)), dataType)
   146  		}
   147  	}
   148  	return v, nil
   149  }
   150  
   151  func setMetaValues(p *lang.Process, iteration int) bool {
   152  	meta := map[string]any{
   153  		"i": iteration,
   154  	}
   155  	err := p.Variables.Set(p, "", meta, types.Json)
   156  	if err != nil {
   157  		p.Stderr.Writeln([]byte("unable to set meta variable: " + err.Error()))
   158  		p.Done()
   159  		return false
   160  	}
   161  	return true
   162  }
   163  
   164  func forEachInnerLoop(p *lang.Process, block []rune, varName string, varValue interface{}, dataType string, iteration int) {
   165  	var b []byte
   166  	b, err := convertToByte(varValue)
   167  	if err != nil {
   168  		p.Done()
   169  		return
   170  	}
   171  
   172  	if len(b) == 0 || p.HasCancelled() {
   173  		return
   174  	}
   175  
   176  	if varName != "!" {
   177  		err = p.Variables.Set(p, varName, varValue, dataType)
   178  		if err != nil {
   179  			p.Stderr.Writeln([]byte("error: " + err.Error()))
   180  			p.Done()
   181  			return
   182  		}
   183  	}
   184  
   185  	if !setMetaValues(p, iteration) {
   186  		return
   187  	}
   188  
   189  	fork := p.Fork(lang.F_PARENT_VARTABLE | lang.F_CREATE_STDIN)
   190  	fork.Stdin.SetDataType(dataType)
   191  	_, err = fork.Stdin.Writeln(b)
   192  	if err != nil {
   193  		p.Stderr.Writeln([]byte("error: " + err.Error()))
   194  		p.Done()
   195  		return
   196  	}
   197  	_, err = fork.Execute(block)
   198  	if err != nil {
   199  		p.Stderr.Writeln([]byte("error: " + err.Error()))
   200  		p.Done()
   201  		return
   202  	}
   203  }
   204  
   205  func cmdForEachJmap(p *lang.Process) error {
   206  	p.Stdout.SetDataType(types.Json)
   207  
   208  	varName, err := p.Parameters.String(1)
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	blockKey, err := p.Parameters.Block(2)
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	blockVal, err := p.Parameters.Block(3)
   219  	if err != nil {
   220  		return err
   221  	}
   222  
   223  	var (
   224  		m         = make(map[string]string)
   225  		iteration int
   226  	)
   227  
   228  	err = p.Stdin.ReadArrayWithType(p.Context, func(v interface{}, dt string) {
   229  		var b []byte
   230  		b, err = convertToByte(v)
   231  		if err != nil {
   232  			p.Done()
   233  			return
   234  		}
   235  
   236  		if len(b) == 0 || p.HasCancelled() {
   237  			return
   238  		}
   239  
   240  		if varName != "!" {
   241  			p.Variables.Set(p, varName, v, dt)
   242  		}
   243  
   244  		iteration++
   245  		if !setMetaValues(p, iteration) {
   246  			return
   247  		}
   248  
   249  		forkKey := p.Fork(lang.F_PARENT_VARTABLE | lang.F_NO_STDIN | lang.F_CREATE_STDOUT)
   250  		forkKey.Execute(blockKey)
   251  		bKey, err := forkKey.Stdout.ReadAll()
   252  		if err != nil {
   253  			p.Stderr.Writeln([]byte(err.Error()))
   254  			p.Kill()
   255  		}
   256  
   257  		forkVal := p.Fork(lang.F_PARENT_VARTABLE | lang.F_NO_STDIN | lang.F_CREATE_STDOUT)
   258  		forkVal.Execute(blockVal)
   259  		bVal, err := forkVal.Stdout.ReadAll()
   260  		if err != nil {
   261  			p.Stderr.Writeln([]byte(err.Error()))
   262  			p.Kill()
   263  		}
   264  
   265  		m[string(utils.CrLfTrim(bKey))] = string(utils.CrLfTrim(bVal))
   266  	})
   267  
   268  	if err != nil {
   269  		return err
   270  	}
   271  
   272  	b, err := json.Marshal(m, p.Stdout.IsTTY())
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	_, err = p.Stdout.Write(b)
   278  	return err
   279  }