-
Notifications
You must be signed in to change notification settings - Fork 215
/
tee.go
162 lines (142 loc) · 5.6 KB
/
tee.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
// ================================================================
// This handles tee statements.
// ================================================================
package cst
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/output"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/miller/v6/pkg/types"
)
// ----------------------------------------------------------------
// Examples:
// tee > "foo.dat", $*
// tee > stderr, $*
// tee > stdout, $*
// tee | "jq .", $*
//
// The item being teed can only be $*. This is a special case of emit. (This
// doesn't do anything emit can't do.)
//
// $ mlr -n put -v 'tee > stdout, $*'
// DSL EXPRESSION:
// tee > stdout, $*
// AST:
// * statement block
// * tee statement "tee"
// * full record "$*"
// * redirect write ">"
// * stdout redirect target "stdout"
//
// $ mlr -n put -v 'tee > "foo.dat", $*'
// DSL EXPRESSION:
// tee > "foo.dat", $*
// AST:
// * statement block
// * tee statement "tee"
// * full record "$*"
// * redirect write ">"
// * string literal "foo.dat"
//
// $ mlr -n put -v 'tee | "jq .", $*'
// DSL EXPRESSION:
// tee | "jq .", $*
// AST:
// * statement block
// * tee statement "tee"
// * full record "$*"
// * redirect pipe "|"
// * string literal "jq ."
// ----------------------------------------------------------------
// ================================================================
type tTeeToRedirectFunc func(
outrec *mlrval.Mlrmap,
state *runtime.State,
) error
type TeeStatementNode struct {
expressionEvaluable IEvaluable // always the $* evaluable
teeToRedirectFunc tTeeToRedirectFunc
redirectorTargetEvaluable IEvaluable // for file/pipe targets
outputHandlerManager output.OutputHandlerManager // for file/pipe targets
}
// ----------------------------------------------------------------
func (root *RootNode) BuildTeeStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeTeeStatement)
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
expressionNode := astNode.Children[0]
redirectorNode := astNode.Children[1]
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Expression to be teed, which is $*.
lib.InternalCodingErrorIf(expressionNode.Type != dsl.NodeTypeFullSrec)
expressionEvaluable := root.BuildFullSrecRvalueNode()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Redirection targets (the thing after > >> |, if any).
retval := &TeeStatementNode{
expressionEvaluable: expressionEvaluable,
teeToRedirectFunc: nil,
redirectorTargetEvaluable: nil,
outputHandlerManager: nil,
}
// There is > >> or | provided.
lib.InternalCodingErrorIf(redirectorNode.Children == nil)
lib.InternalCodingErrorIf(len(redirectorNode.Children) != 1)
redirectorTargetNode := redirectorNode.Children[0]
var err error = nil
if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStdout {
retval.teeToRedirectFunc = retval.teeToFileOrPipe
retval.outputHandlerManager = output.NewStdoutWriteHandlerManager(root.recordWriterOptions)
retval.redirectorTargetEvaluable = root.BuildStringLiteralNode("(stdout)")
} else if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStderr {
retval.teeToRedirectFunc = retval.teeToFileOrPipe
retval.outputHandlerManager = output.NewStderrWriteHandlerManager(root.recordWriterOptions)
retval.redirectorTargetEvaluable = root.BuildStringLiteralNode("(stderr)")
} else {
retval.teeToRedirectFunc = retval.teeToFileOrPipe
retval.redirectorTargetEvaluable, err = root.BuildEvaluableNode(redirectorTargetNode)
if err != nil {
return nil, err
}
if redirectorNode.Type == dsl.NodeTypeRedirectWrite {
retval.outputHandlerManager = output.NewFileWritetHandlerManager(root.recordWriterOptions)
} else if redirectorNode.Type == dsl.NodeTypeRedirectAppend {
retval.outputHandlerManager = output.NewFileAppendHandlerManager(root.recordWriterOptions)
} else if redirectorNode.Type == dsl.NodeTypeRedirectPipe {
retval.outputHandlerManager = output.NewPipeWriteHandlerManager(root.recordWriterOptions)
} else {
return nil, fmt.Errorf("mlr: unhandled redirector node type %s.", string(redirectorNode.Type))
}
}
// Register this with the CST root node so that open file descriptrs can be
// closed, etc at end of stream.
if retval.outputHandlerManager != nil {
root.RegisterOutputHandlerManager(retval.outputHandlerManager)
}
return retval, nil
}
// ----------------------------------------------------------------
func (node *TeeStatementNode) Execute(state *runtime.State) (*BlockExitPayload, error) {
expression := node.expressionEvaluable.Evaluate(state)
if !expression.IsMap() {
return nil, fmt.Errorf("mlr: tee-evaluaiton yielded %s, not map.", expression.GetTypeName())
}
err := node.teeToRedirectFunc(expression.GetMap(), state)
return nil, err
}
// ----------------------------------------------------------------
func (node *TeeStatementNode) teeToFileOrPipe(
outrec *mlrval.Mlrmap,
state *runtime.State,
) error {
redirectorTarget := node.redirectorTargetEvaluable.Evaluate(state)
if !redirectorTarget.IsString() {
return fmt.Errorf("mlr: output redirection yielded %s, not string.", redirectorTarget.GetTypeName())
}
outputFileName := redirectorTarget.String()
return node.outputHandlerManager.WriteRecordAndContext(
types.NewRecordAndContext(outrec, state.Context),
outputFileName,
)
}