github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/docs/apis/lang.IndexTemplateTable.md (about) 1 # `lang.IndexTemplateTable()` (template API) 2 3 > Returns element(s) from a table 4 5 ## Description 6 7 This is a template API you can use for your custom data types. 8 9 It should only be called from `ReadIndex()` and `ReadNotIndex()` functions. 10 11 This function ensures consistency with the index, `[`, builtin when used with 12 different Murex data types. Thus making indexing a data type agnostic 13 capability. 14 15 16 17 ## Examples 18 19 Example calling `lang.IndexTemplateTable()` function: 20 21 ```go 22 package generic 23 24 import ( 25 "bytes" 26 "strings" 27 28 "github.com/lmorg/murex/lang" 29 ) 30 31 func index(p *lang.Process, params []string) error { 32 cRecords := make(chan []string, 1) 33 34 go func() { 35 err := p.Stdin.ReadLine(func(b []byte) { 36 cRecords <- rxWhitespace.Split(string(bytes.TrimSpace(b)), -1) 37 }) 38 if err != nil { 39 p.Stderr.Writeln([]byte(err.Error())) 40 } 41 close(cRecords) 42 }() 43 44 marshaller := func(s []string) (b []byte) { 45 b = []byte(strings.Join(s, "\t")) 46 return 47 } 48 49 return lang.IndexTemplateTable(p, params, cRecords, marshaller) 50 } 51 ``` 52 53 ## Detail 54 55 ### API Source: 56 57 ```go 58 package lang 59 60 import ( 61 "errors" 62 "fmt" 63 "regexp" 64 "strconv" 65 "strings" 66 67 "github.com/lmorg/murex/utils" 68 ) 69 70 const ( 71 byRowNumber = iota + 1 72 byColumnNumber 73 byColumnName 74 75 maxReportedUnmatched = 5 76 ) 77 78 var ( 79 rxColumnPrefixOld = regexp.MustCompile(`^:[0-9]+$`) 80 rxRowSuffixOld = regexp.MustCompile(`^[0-9]+:$`) 81 rxColumnPrefixNew = regexp.MustCompile(`^\*[a-zA-Z]$`) 82 rxRowSuffixNew = regexp.MustCompile(`^\*[0-9]+$`) 83 errMixAndMatch = errors.New("you cannot mix and match matching modes") 84 ) 85 86 // IndexTemplateTable is a handy standard indexer you can use in your custom data types for tabulated / streamed data. 87 // The point of this is to minimize code rewriting and standardising the behavior of the indexer. 88 func IndexTemplateTable(p *Process, params []string, cRecords chan []string, marshaller func([]string) []byte) error { 89 if p.IsNot { 90 return ittNot(p, params, cRecords, marshaller) 91 } 92 return ittIndex(p, params, cRecords, marshaller) 93 } 94 95 func charToIndex(b byte) int { 96 if b > 96 { 97 return int(b - 97) 98 } 99 return int(b - 65) 100 } 101 102 func ittIndex(p *Process, params []string, cRecords chan []string, marshaller func([]string) []byte) (err error) { 103 var ( 104 mode int 105 matchStr []string 106 matchInt []int 107 unmatched []string 108 unmatchedCount int 109 ) 110 111 defer func() { 112 if len(unmatched) != 0 { 113 p.ExitNum = 1 114 if unmatchedCount > maxReportedUnmatched { 115 unmatched = append(unmatched, fmt.Sprintf("...plus %d more", unmatchedCount-maxReportedUnmatched)) 116 } 117 err = fmt.Errorf("some records did not contain all of the requested fields:%s%s", 118 utils.NewLineString, 119 strings.Join(unmatched, utils.NewLineString)) 120 } 121 }() 122 123 errUnmatched := func(recs []string) { 124 unmatchedCount++ 125 if unmatchedCount > maxReportedUnmatched { 126 return 127 } 128 unmatched = append(unmatched, strings.Join(recs, "\t")) 129 } 130 131 for i := range params { 132 switch { 133 case rxRowSuffixOld.MatchString(params[i]): 134 if mode != 0 && mode != byRowNumber { 135 return errMixAndMatch 136 } 137 mode = byRowNumber 138 num, _ := strconv.Atoi(params[i][:len(params[i])-1]) 139 matchInt = append(matchInt, num) 140 141 case rxRowSuffixNew.MatchString(params[i]): 142 if mode != 0 && mode != byRowNumber { 143 return errMixAndMatch 144 } 145 mode = byRowNumber 146 num, _ := strconv.Atoi(params[i][1:]) 147 matchInt = append(matchInt, num-1) // Don't count from zero 148 149 case rxColumnPrefixOld.MatchString(params[i]): 150 if mode != 0 && mode != byColumnNumber { 151 return errMixAndMatch 152 } 153 mode = byColumnNumber 154 num, _ := strconv.Atoi(params[i][1:]) 155 matchInt = append(matchInt, num) 156 157 case rxColumnPrefixNew.MatchString(params[i]): 158 if mode != 0 && mode != byColumnNumber { 159 return errMixAndMatch 160 } 161 mode = byColumnNumber 162 num := charToIndex(params[i][1]) 163 matchInt = append(matchInt, num) 164 165 default: 166 if mode != 0 && mode != byColumnName { 167 return errMixAndMatch 168 } 169 matchStr = append(matchStr, params[i]) 170 mode = byColumnName 171 172 } 173 } 174 175 switch mode { 176 case byRowNumber: 177 var ( 178 ordered = true 179 last int 180 max int 181 ) 182 // check order 183 for _, i := range matchInt { 184 if i < last { 185 ordered = false 186 } 187 if i > max { 188 max = i 189 } 190 last = i 191 } 192 193 if ordered { 194 // ordered matching - for this we can just read in the records we want sequentially. Low memory overhead 195 var i int 196 for { 197 recs, ok := <-cRecords 198 if !ok { 199 return nil 200 } 201 if i == matchInt[0] { 202 _, err = p.Stdout.Writeln(marshaller(recs)) 203 if err != nil { 204 p.Stderr.Writeln([]byte(err.Error())) 205 } 206 if len(matchInt) == 1 { 207 matchInt[0] = -1 208 return nil 209 } 210 matchInt = matchInt[1:] 211 } 212 i++ 213 } 214 215 } else { 216 // unordered matching - for this we load the entire data set into memory - up until the maximum value 217 var ( 218 i int 219 lines = make([][]string, max+1) 220 ) 221 for { 222 recs, ok := <-cRecords 223 if !ok { 224 break 225 } 226 if i <= max { 227 lines[i] = recs 228 } 229 i++ 230 } 231 232 for _, j := range matchInt { 233 _, err = p.Stdout.Writeln(marshaller(lines[j])) 234 if err != nil { 235 p.Stderr.Writeln([]byte(err.Error())) 236 } 237 } 238 239 return nil 240 } 241 242 case byColumnNumber: 243 for { 244 recs, ok := <-cRecords 245 if !ok { 246 return nil 247 } 248 249 var line []string 250 for _, i := range matchInt { 251 if i < len(recs) { 252 line = append(line, recs[i]) 253 } else { 254 if len(recs) == 0 || (len(recs) == 1 && recs[0] == "") { 255 continue 256 } 257 errUnmatched(recs) 258 } 259 } 260 if len(line) != 0 { 261 _, err = p.Stdout.Writeln(marshaller(line)) 262 if err != nil { 263 p.Stderr.Writeln([]byte(err.Error())) 264 } 265 } 266 } 267 268 case byColumnName: 269 var ( 270 lineNum int 271 headings = make(map[string]int) 272 ) 273 274 for { 275 var line []string 276 recs, ok := <-cRecords 277 if !ok { 278 return nil 279 } 280 281 if lineNum == 0 { 282 for i := range recs { 283 headings[recs[i]] = i + 1 284 } 285 for i := range matchStr { 286 if headings[matchStr[i]] != 0 { 287 line = append(line, matchStr[i]) 288 } 289 } 290 if len(line) != 0 { 291 _, err = p.Stdout.Writeln(marshaller(line)) 292 if err != nil { 293 p.Stderr.Writeln([]byte(err.Error())) 294 } 295 } 296 297 } else { 298 for i := range matchStr { 299 col := headings[matchStr[i]] 300 if col != 0 && col < len(recs)+1 { 301 line = append(line, recs[col-1]) 302 } else { 303 if len(recs) == 0 || (len(recs) == 1 && recs[0] == "") { 304 continue 305 } 306 errUnmatched(recs) 307 } 308 } 309 if len(line) != 0 { 310 _, err = p.Stdout.Writeln(marshaller(line)) 311 if err != nil { 312 p.Stderr.Writeln([]byte(err.Error())) 313 } 314 } 315 } 316 lineNum++ 317 } 318 319 default: 320 return errors.New("you haven't selected any rows / columns") 321 } 322 } 323 324 func ittNot(p *Process, params []string, cRecords chan []string, marshaller func([]string) []byte) error { 325 var ( 326 mode int 327 matchStr = make(map[string]bool) 328 matchInt = make(map[int]bool) 329 ) 330 331 for i := range params { 332 switch { 333 case rxRowSuffixOld.MatchString(params[i]): 334 if mode != 0 && mode != byRowNumber { 335 return errMixAndMatch 336 } 337 mode = byRowNumber 338 num, _ := strconv.Atoi(params[i][:len(params[i])-1]) 339 matchInt[num] = true 340 341 case rxRowSuffixNew.MatchString(params[i]): 342 if mode != 0 && mode != byRowNumber { 343 return errMixAndMatch 344 } 345 mode = byRowNumber 346 num, _ := strconv.Atoi(params[i][1:]) 347 matchInt[num+1] = true // Don't count from zero 348 349 case rxColumnPrefixOld.MatchString(params[i]): 350 if mode != 0 && mode != byColumnNumber { 351 return errMixAndMatch 352 } 353 mode = byColumnNumber 354 num, _ := strconv.Atoi(params[i][1:]) 355 matchInt[num] = true 356 357 case rxColumnPrefixNew.MatchString(params[i]): 358 if mode != 0 && mode != byColumnNumber { 359 return errMixAndMatch 360 } 361 mode = byColumnNumber 362 num := charToIndex(params[i][1]) 363 matchInt[num] = true 364 365 default: 366 if mode != 0 && mode != byColumnName { 367 return errMixAndMatch 368 } 369 matchStr[params[i]] = true 370 mode = byColumnName 371 372 } 373 } 374 375 switch mode { 376 case byRowNumber: 377 i := -1 378 for { 379 recs, ok := <-cRecords 380 if !ok { 381 return nil 382 } 383 384 if !matchInt[i] { 385 _, err := p.Stdout.Writeln(marshaller(recs)) 386 if err != nil { 387 p.Stderr.Writeln([]byte(err.Error())) 388 } 389 } 390 i++ 391 } 392 393 case byColumnNumber: 394 for { 395 recs, ok := <-cRecords 396 if !ok { 397 return nil 398 } 399 400 var line []string 401 for i := range recs { 402 if !matchInt[i] { 403 line = append(line, recs[i]) 404 } 405 } 406 if len(line) != 0 { 407 p.Stdout.Writeln(marshaller(line)) 408 } 409 } 410 411 case byColumnName: 412 var ( 413 lineNum int 414 headings = make(map[int]string) 415 ) 416 417 for { 418 var line []string 419 recs, ok := <-cRecords 420 if !ok { 421 return nil 422 } 423 424 if lineNum == 0 { 425 for i := range recs { 426 headings[i] = recs[i] 427 if !matchStr[headings[i]] { 428 line = append(line, recs[i]) 429 } 430 } 431 if len(line) != 0 { 432 p.Stdout.Writeln(marshaller(line)) 433 } 434 435 } else { 436 for i := range recs { 437 if !matchStr[headings[i]] { 438 line = append(line, recs[i]) 439 } 440 } 441 442 if len(line) != 0 { 443 p.Stdout.Writeln(marshaller(line)) 444 } 445 } 446 lineNum++ 447 } 448 449 default: 450 return errors.New("you haven't selected any rows / columns") 451 } 452 } 453 ``` 454 455 ## Parameters 456 457 1. `*lang.Process`: Process's runtime state. Typically expressed as the variable `p` 458 2. `[]string`: slice of parameters used in `[` / `![` 459 3. `chan []string`: a channel for rows (each element in the slice is a column within the row). This allows tables to be stream-able 460 4. `func(interface{}) ([]byte, error)`: data type marshaller function 461 462 ## See Also 463 464 * [apis/`ReadArray()` (type)](../apis/ReadArray.md): 465 Read from a data type one array element at a time 466 * [apis/`ReadArrayWithType()` (type)](../apis/ReadArrayWithType.md): 467 Read from a data type one array element at a time and return the elements contents and data type 468 * [apis/`ReadIndex()` (type)](../apis/ReadIndex.md): 469 Data type handler for the index, `[`, builtin 470 * [apis/`ReadMap()` (type)](../apis/ReadMap.md): 471 Treat data type as a key/value structure and read its contents 472 * [apis/`ReadNotIndex()` (type)](../apis/ReadNotIndex.md): 473 Data type handler for the bang-prefixed index, `: 475 Write a data type, one array element at a time 476 * [apis/`lang.IndexTemplateObject()` (template API)](../apis/lang.IndexTemplateObject.md): 477 Returns element(s) from a data structure 478 * [parser/index](../parser/item-index.md): 479 Outputs an element from an array, map or table 480 481 <hr/> 482 483 This document was generated from [lang/stdio/interface_doc.yaml](https://github.com/lmorg/murex/blob/master/lang/stdio/interface_doc.yaml).