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, `![`, builtin
   474  * [apis/`WriteArray()` (type)](../apis/WriteArray.md):
   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).