lang.IndexTemplateTable()
(template API)Returns element(s) from a table
This is a template API you can use for your custom data types.
It should only be called from ReadIndex()
and
ReadNotIndex()
functions.
This function ensures consistency with the index, [
,
builtin when used with different Murex data types. Thus making indexing
a data type agnostic capability.
Example calling lang.IndexTemplateTable()
function:
package generic
import (
"bytes"
"strings"
"github.com/lmorg/murex/lang"
)
func index(p *lang.Process, params []string) error {
:= make(chan []string, 1)
cRecords
go func() {
:= p.Stdin.ReadLine(func(b []byte) {
err <- rxWhitespace.Split(string(bytes.TrimSpace(b)), -1)
cRecords })
if err != nil {
.Stderr.Writeln([]byte(err.Error()))
p}
close(cRecords)
}()
:= func(s []string) (b []byte) {
marshaller = []byte(strings.Join(s, "\t"))
b return
}
return lang.IndexTemplateTable(p, params, cRecords, marshaller)
}
package lang
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/lmorg/murex/utils"
)
const (
= iota + 1
byRowNumber
byColumnNumber
byColumnName
= 5
maxReportedUnmatched )
var (
= regexp.MustCompile(`^:[0-9]+$`)
rxColumnPrefixOld = regexp.MustCompile(`^[0-9]+:$`)
rxRowSuffixOld = regexp.MustCompile(`^\*[a-zA-Z]$`)
rxColumnPrefixNew = regexp.MustCompile(`^\*[0-9]+$`)
rxRowSuffixNew = errors.New("you cannot mix and match matching modes")
errMixAndMatch )
// IndexTemplateTable is a handy standard indexer you can use in your custom data types for tabulated / streamed data.
// The point of this is to minimize code rewriting and standardising the behavior of the indexer.
func IndexTemplateTable(p *Process, params []string, cRecords chan []string, marshaller func([]string) []byte) error {
if p.IsNot {
return ittNot(p, params, cRecords, marshaller)
}
return ittIndex(p, params, cRecords, marshaller)
}
func charToIndex(b byte) int {
if b > 96 {
return int(b - 97)
}
return int(b - 65)
}
func ittIndex(p *Process, params []string, cRecords chan []string, marshaller func([]string) []byte) (err error) {
var (
int
mode []string
matchStr []int
matchInt []string
unmatched int
unmatchedCount )
defer func() {
if len(unmatched) != 0 {
.ExitNum = 1
pif unmatchedCount > maxReportedUnmatched {
= append(unmatched, fmt.Sprintf("...plus %d more", unmatchedCount-maxReportedUnmatched))
unmatched }
= fmt.Errorf("some records did not contain all of the requested fields:%s%s",
err .NewLineString,
utils.Join(unmatched, utils.NewLineString))
strings}
}()
:= func(recs []string) {
errUnmatched ++
unmatchedCountif unmatchedCount > maxReportedUnmatched {
return
}
= append(unmatched, strings.Join(recs, "\t"))
unmatched }
for i := range params {
switch {
case rxRowSuffixOld.MatchString(params[i]):
if mode != 0 && mode != byRowNumber {
return errMixAndMatch
}
= byRowNumber
mode , _ := strconv.Atoi(params[i][:len(params[i])-1])
num= append(matchInt, num)
matchInt
case rxRowSuffixNew.MatchString(params[i]):
if mode != 0 && mode != byRowNumber {
return errMixAndMatch
}
= byRowNumber
mode , _ := strconv.Atoi(params[i][1:])
num= append(matchInt, num-1) // Don't count from zero
matchInt
case rxColumnPrefixOld.MatchString(params[i]):
if mode != 0 && mode != byColumnNumber {
return errMixAndMatch
}
= byColumnNumber
mode , _ := strconv.Atoi(params[i][1:])
num= append(matchInt, num)
matchInt
case rxColumnPrefixNew.MatchString(params[i]):
if mode != 0 && mode != byColumnNumber {
return errMixAndMatch
}
= byColumnNumber
mode := charToIndex(params[i][1])
num = append(matchInt, num)
matchInt
default:
if mode != 0 && mode != byColumnName {
return errMixAndMatch
}
= append(matchStr, params[i])
matchStr = byColumnName
mode
}
}
switch mode {
case byRowNumber:
var (
= true
ordered int
last int
max )
// check order
for _, i := range matchInt {
if i < last {
= false
ordered }
if i > max {
= i
max }
= i
last }
if ordered {
// ordered matching - for this we can just read in the records we want sequentially. Low memory overhead
var i int
for {
, ok := <-cRecords
recsif !ok {
return nil
}
if i == matchInt[0] {
, err = p.Stdout.Writeln(marshaller(recs))
_if err != nil {
.Stderr.Writeln([]byte(err.Error()))
p}
if len(matchInt) == 1 {
[0] = -1
matchIntreturn nil
}
= matchInt[1:]
matchInt }
++
i}
} else {
// unordered matching - for this we load the entire data set into memory - up until the maximum value
var (
int
i = make([][]string, max+1)
lines )
for {
, ok := <-cRecords
recsif !ok {
break
}
if i <= max {
[i] = recs
lines}
++
i}
for _, j := range matchInt {
, err = p.Stdout.Writeln(marshaller(lines[j]))
_if err != nil {
.Stderr.Writeln([]byte(err.Error()))
p}
}
return nil
}
case byColumnNumber:
for {
, ok := <-cRecords
recsif !ok {
return nil
}
var line []string
for _, i := range matchInt {
if i < len(recs) {
= append(line, recs[i])
line } else {
if len(recs) == 0 || (len(recs) == 1 && recs[0] == "") {
continue
}
(recs)
errUnmatched}
}
if len(line) != 0 {
, err = p.Stdout.Writeln(marshaller(line))
_if err != nil {
.Stderr.Writeln([]byte(err.Error()))
p}
}
}
case byColumnName:
var (
int
lineNum = make(map[string]int)
headings )
for {
var line []string
, ok := <-cRecords
recsif !ok {
return nil
}
if lineNum == 0 {
for i := range recs {
[recs[i]] = i + 1
headings}
for i := range matchStr {
if headings[matchStr[i]] != 0 {
= append(line, matchStr[i])
line }
}
if len(line) != 0 {
, err = p.Stdout.Writeln(marshaller(line))
_if err != nil {
.Stderr.Writeln([]byte(err.Error()))
p}
}
} else {
for i := range matchStr {
:= headings[matchStr[i]]
col if col != 0 && col < len(recs)+1 {
= append(line, recs[col-1])
line } else {
if len(recs) == 0 || (len(recs) == 1 && recs[0] == "") {
continue
}
(recs)
errUnmatched}
}
if len(line) != 0 {
, err = p.Stdout.Writeln(marshaller(line))
_if err != nil {
.Stderr.Writeln([]byte(err.Error()))
p}
}
}
++
lineNum}
default:
return errors.New("you haven't selected any rows / columns")
}
}
func ittNot(p *Process, params []string, cRecords chan []string, marshaller func([]string) []byte) error {
var (
int
mode = make(map[string]bool)
matchStr = make(map[int]bool)
matchInt )
for i := range params {
switch {
case rxRowSuffixOld.MatchString(params[i]):
if mode != 0 && mode != byRowNumber {
return errMixAndMatch
}
= byRowNumber
mode , _ := strconv.Atoi(params[i][:len(params[i])-1])
num[num] = true
matchInt
case rxRowSuffixNew.MatchString(params[i]):
if mode != 0 && mode != byRowNumber {
return errMixAndMatch
}
= byRowNumber
mode , _ := strconv.Atoi(params[i][1:])
num[num+1] = true // Don't count from zero
matchInt
case rxColumnPrefixOld.MatchString(params[i]):
if mode != 0 && mode != byColumnNumber {
return errMixAndMatch
}
= byColumnNumber
mode , _ := strconv.Atoi(params[i][1:])
num[num] = true
matchInt
case rxColumnPrefixNew.MatchString(params[i]):
if mode != 0 && mode != byColumnNumber {
return errMixAndMatch
}
= byColumnNumber
mode := charToIndex(params[i][1])
num [num] = true
matchInt
default:
if mode != 0 && mode != byColumnName {
return errMixAndMatch
}
[params[i]] = true
matchStr= byColumnName
mode
}
}
switch mode {
case byRowNumber:
:= -1
i for {
, ok := <-cRecords
recsif !ok {
return nil
}
if !matchInt[i] {
, err := p.Stdout.Writeln(marshaller(recs))
_if err != nil {
.Stderr.Writeln([]byte(err.Error()))
p}
}
++
i}
case byColumnNumber:
for {
, ok := <-cRecords
recsif !ok {
return nil
}
var line []string
for i := range recs {
if !matchInt[i] {
= append(line, recs[i])
line }
}
if len(line) != 0 {
.Stdout.Writeln(marshaller(line))
p}
}
case byColumnName:
var (
int
lineNum = make(map[int]string)
headings )
for {
var line []string
, ok := <-cRecords
recsif !ok {
return nil
}
if lineNum == 0 {
for i := range recs {
[i] = recs[i]
headingsif !matchStr[headings[i]] {
= append(line, recs[i])
line }
}
if len(line) != 0 {
.Stdout.Writeln(marshaller(line))
p}
} else {
for i := range recs {
if !matchStr[headings[i]] {
= append(line, recs[i])
line }
}
if len(line) != 0 {
.Stdout.Writeln(marshaller(line))
p}
}
++
lineNum}
default:
return errors.New("you haven't selected any rows / columns")
}
}
*lang.Process
: Process’s runtime state. Typically
expressed as the variable p
[]string
: slice of parameters used in [
/
![
chan []string
: a channel for rows (each element in the
slice is a column within the row). This allows tables to be
stream-ablefunc(interface{}) ([]byte, error)
: data type marshaller
functionReadArray()
(type): Read from a data type one array element at a timeReadArrayWithType()
(type): Read from a data type one array element at a time and return
the elements contents and data typeReadIndex()
(type): Data type handler for the index, [
,
builtinReadMap()
(type):
Treat data type as a key/value structure and read its contentsReadNotIndex()
(type): Data type handler for the bang-prefixed index,
![
, builtinWriteArray()
(type): Write a data type, one array element at a timelang.IndexTemplateObject()
(template API): Returns element(s) from a data structureThis document was generated from lang/stdio/interface_doc.yaml.
This site's content is rebuilt automatically from murex's source code after each merge to the master
branch. Downloadable murex binaries are also built with the website.
Last built on Wed Jan 15 23:07:50 UTC 2025 against commit b4c4296b4c429617fd41527ea0efef33c52c15ef2b64972.
Current version is 6.4.2063 (develop) which has been verified against tests cases.