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