forked from johnkerl/miller
-
Notifications
You must be signed in to change notification settings - Fork 0
/
context.go
166 lines (142 loc) · 4.37 KB
/
context.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package types
import (
"bytes"
"container/list"
"strconv"
"github.com/johnkerl/miller/internal/pkg/mlrval"
)
// Since Go is concurrent, the context struct (AWK-like variables such as
// FILENAME, NF, NR, FNR, etc.) needs to be duplicated and passed through the
// channels along with each record.
//
// Strings to be printed from put/filter DSL print/dump/etc statements are
// passed along to the output channel via this OutputString rather than
// fmt.Println directly in the put/filter handlers since we want all print
// statements and record-output to be in the same goroutine, for deterministic
// output ordering.
type RecordAndContext struct {
Record *mlrval.Mlrmap
Context Context
OutputString string
EndOfStream bool
}
func NewRecordAndContext(
record *mlrval.Mlrmap,
context *Context,
) *RecordAndContext {
return &RecordAndContext{
Record: record,
// Since Go is concurrent, the context struct needs to be duplicated and
// passed through the channels along with each record. Here is where
// the copy happens, via the '*' in *context.
Context: *context,
OutputString: "",
EndOfStream: false,
}
}
// For the record-readers to update their initial context as each new record is read.
func (rac *RecordAndContext) Copy() *RecordAndContext {
if rac == nil {
return nil
}
recordCopy := rac.Record.Copy()
contextCopy := rac.Context
return &RecordAndContext{
Record: recordCopy,
Context: contextCopy,
OutputString: "",
EndOfStream: false,
}
}
// For print/dump/etc to insert strings sequenced into the record-output
// stream. This avoids race conditions between different goroutines printing
// to stdout: we have a single designated goroutine printing to stdout. This
// makes output more predictable and intuitive for users; it also makes our
// regression tests run reliably the same each time.
func NewOutputString(
outputString string,
context *Context,
) *RecordAndContext {
return &RecordAndContext{
Record: nil,
Context: *context,
OutputString: outputString,
EndOfStream: false,
}
}
// For the record-readers to update their initial context as each new record is read.
func NewEndOfStreamMarker(context *Context) *RecordAndContext {
return &RecordAndContext{
Record: nil,
Context: *context,
OutputString: "",
EndOfStream: true,
}
}
// TODO: comment
// For the record-readers to update their initial context as each new record is read.
func NewEndOfStreamMarkerList(context *Context) *list.List {
ell := list.New()
ell.PushBack(NewEndOfStreamMarker(context))
return ell
}
// ----------------------------------------------------------------
type Context struct {
FILENAME string
FILENUM int64
// This is computed dynammically from the current record's field-count
// NF int
NR int64
FNR int64
}
// TODO: comment: Remember command-line values to pass along to CST evaluators.
// The options struct-pointer can be nil when invoked by non-DSL verbs such as
// join or seqgen.
func NewContext() *Context {
context := &Context{
FILENAME: "(stdin)",
FILENUM: 0,
NR: 0,
FNR: 0,
}
return context
}
// TODO: comment: Remember command-line values to pass along to CST evaluators.
// The options struct-pointer can be nil when invoked by non-DSL verbs such as
// join or seqgen.
func NewNilContext() *Context { // TODO: rename
context := &Context{
FILENAME: "(stdin)",
FILENUM: 0,
NR: 0,
FNR: 0,
}
return context
}
// For the record-readers to update their initial context as each new file is opened.
func (context *Context) UpdateForStartOfFile(filename string) {
context.FILENAME = filename
context.FILENUM++
context.FNR = 0
}
// For the record-readers to update their initial context as each new record is read.
func (context *Context) UpdateForInputRecord() {
context.NR++
context.FNR++
}
func (context *Context) Copy() *Context {
other := *context
return &other
}
func (context *Context) GetStatusString() string {
var buffer bytes.Buffer // stdio is non-buffered in Go, so buffer for speed increase
buffer.WriteString("FILENAME=\"")
buffer.WriteString(context.FILENAME)
buffer.WriteString("\",FILENUM=")
buffer.WriteString(strconv.FormatInt(context.FILENUM, 10))
buffer.WriteString(",NR=")
buffer.WriteString(strconv.FormatInt(context.NR, 10))
buffer.WriteString(",FNR=")
buffer.WriteString(strconv.FormatInt(context.FNR, 10))
return buffer.String()
}