-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommand.go
More file actions
120 lines (108 loc) · 3.76 KB
/
Copy pathcommand.go
File metadata and controls
120 lines (108 loc) · 3.76 KB
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
package command
import (
"context"
"io"
"github.com/destel/rill"
gloo "github.com/gloo-foo/framework"
"github.com/gloo-foo/framework/patterns"
"github.com/spf13/afero"
)
// subprocessBuilder constructs the Command that forks the external process. It
// is the seam that lets tests substitute a fake for patterns.Subprocess, so the
// argument-building and input-wiring logic is covered without a real perl
// binary on the host.
type subprocessBuilder func(name patterns.ProcessName, args ...patterns.ProcessArg) gloo.Command[[]byte, []byte]
// Perl returns a Command that forks perl with a script.
//
// Two call shapes are supported:
//
// Perl("script", opts...) // script as required positional
// Perl(PerlScript("script"), opts...) // script as a flag (preferred in examples)
//
// Options: PerlLoop (-n), PerlPrint (-p), PerlAutoSplit (-a),
// PerlScript (script body), positional io.Reader / gloo.File for input.
func Perl(opts ...any) gloo.Command[[]byte, []byte] {
return perlWith(patterns.Subprocess, afero.NewOsFs(), opts...)
}
// perlWith is Perl with an injectable subprocess builder and filesystem. The
// seams keep argument construction and file-input wiring testable without
// running perl or touching the real filesystem.
func perlWith(build subprocessBuilder, fs afero.Fs, opts ...any) gloo.Command[[]byte, []byte] {
f, rest := foldOptions(promoteScript(opts))
params := gloo.NewParameters[gloo.File, struct{}](rest...)
sub := build("perl", perlArgs(f)...)
if len(params.Positional) == 0 {
return sub
}
return inputBoundCommand(sub, params, fs)
}
// promoteScript rewrites the first bare string option into a PerlScript flag,
// supporting the Perl("script", ...) call shape. An explicit PerlScript option
// already supplies the script, so no promotion happens and a bare string is
// then treated as a positional file path.
func promoteScript(opts []any) []any {
if hasScript(opts) {
return opts
}
for i, o := range opts {
if s, ok := o.(string); ok {
opts[i] = PerlScript(s)
break
}
}
return opts
}
// hasScript reports whether opts already contains an explicit PerlScript flag.
func hasScript(opts []any) bool {
for _, o := range opts {
if _, ok := o.(PerlScript); ok {
return true
}
}
return false
}
// perlArgs renders the perl command-line arguments from the parsed flags: the
// mode switches in GNU order followed by the -e script body.
func perlArgs(f flags) []patterns.ProcessArg {
args := switchArgs(f)
return append(args, "-e", patterns.ProcessArg(f.script))
}
// switchArgs collects the boolean mode switches that are enabled.
func switchArgs(f flags) []patterns.ProcessArg {
var args []patterns.ProcessArg
for _, s := range switches(f) {
if s.isOn {
args = append(args, patterns.ProcessArg(s.flag))
}
}
return args
}
// perlSwitch pairs a mode flag with whether it is enabled.
type perlSwitch struct {
flag string
isOn bool
}
// switches lists the perl mode switches in canonical command-line order.
func switches(f flags) []perlSwitch {
return []perlSwitch{
{flag: "-n", isOn: bool(f.loopEnabled)},
{flag: "-p", isOn: bool(f.printEnabled)},
{flag: "-a", isOn: bool(f.autoSplitEnabled)},
}
}
// inputBoundCommand wraps sub so that positional file/reader inputs are opened
// and streamed to perl's stdin when the command executes.
func inputBoundCommand(
sub gloo.Command[[]byte, []byte],
params gloo.Parameters[gloo.File, struct{}],
fs afero.Fs,
) gloo.Command[[]byte, []byte] {
return gloo.FuncCommand[[]byte, []byte](func(ctx context.Context, _ gloo.Stream[[]byte]) gloo.Stream[[]byte] {
reader, err := params.ReaderFrom(fs, nil)
if err != nil {
return gloo.Wrap(rill.FromSlice([][]byte(nil), err))
}
input := gloo.ByteReaderSource([]io.Reader{reader}).Stream(ctx)
return sub.Execute(ctx, input)
})
}