1package input
2
3import (
4	"bufio"
5	"io"
6	"os"
7	"strconv"
8	"strings"
9
10	"miller/src/cliutil"
11	"miller/src/lib"
12	"miller/src/types"
13)
14
15type RecordReaderDKVP struct {
16	ifs string
17	ips string
18	// TODO: parameterize IRS
19}
20
21func NewRecordReaderDKVP(readerOptions *cliutil.TReaderOptions) *RecordReaderDKVP {
22	return &RecordReaderDKVP{
23		ifs: readerOptions.IFS,
24		ips: readerOptions.IPS,
25	}
26}
27
28func (this *RecordReaderDKVP) Read(
29	filenames []string,
30	context types.Context,
31	inputChannel chan<- *types.RecordAndContext,
32	errorChannel chan error,
33) {
34	if filenames != nil { // nil for mlr -n
35		if len(filenames) == 0 { // read from stdin
36			handle := os.Stdin
37			this.processHandle(handle, "(stdin)", &context, inputChannel, errorChannel)
38		} else {
39			for _, filename := range filenames {
40				handle, err := os.Open(filename)
41				if err != nil {
42					errorChannel <- err
43				} else {
44					this.processHandle(handle, filename, &context, inputChannel, errorChannel)
45					handle.Close()
46				}
47			}
48		}
49	}
50	inputChannel <- types.NewEndOfStreamMarker(&context)
51}
52
53func (this *RecordReaderDKVP) processHandle(
54	handle *os.File,
55	filename string,
56	context *types.Context,
57	inputChannel chan<- *types.RecordAndContext,
58	errorChannel chan error,
59) {
60	context.UpdateForStartOfFile(filename)
61
62	lineReader := bufio.NewReader(handle)
63	eof := false
64	for !eof {
65		line, err := lineReader.ReadString('\n') // TODO: auto-detect
66		if err == io.EOF {
67			err = nil
68			eof = true
69		} else if err != nil {
70			errorChannel <- err
71		} else {
72			// This is how to do a chomp:
73			line = strings.TrimRight(line, "\n")
74			record := recordFromDKVPLine(&line, &this.ifs, &this.ips)
75			context.UpdateForInputRecord()
76			inputChannel <- types.NewRecordAndContext(
77				record,
78				context,
79			)
80		}
81	}
82}
83
84// ----------------------------------------------------------------
85func recordFromDKVPLine(
86	line *string,
87	ifs *string,
88	ips *string,
89) *types.Mlrmap {
90	record := types.NewMlrmap()
91	pairs := lib.SplitString(*line, *ifs)
92	for i, pair := range pairs {
93		kv := strings.SplitN(pair, *ips, 2)
94		// TODO check length 0. also, check input is empty since "".split() -> [""] not []
95		if len(kv) == 1 {
96			// E.g the pair has no equals sign: "a" rather than "a=1" or
97			// "a=".  Here we use the positional index as the key. This way
98			// DKVP is a generalization of NIDX.
99			key := strconv.Itoa(i + 1) // Miller userspace indices are 1-up
100			value := types.MlrvalPointerFromInferredType(kv[0])
101			record.PutReference(key, value)
102		} else {
103			key := kv[0]
104			value := types.MlrvalPointerFromInferredType(kv[1])
105			record.PutReference(key, value)
106		}
107	}
108	return record
109}
110