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 = ¶meters.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 }