-
Notifications
You must be signed in to change notification settings - Fork 0
/
io.go
110 lines (98 loc) · 3.5 KB
/
io.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
// Package cmdio provides portable interfaces for commands and command runners.
//
// A command is an [io.ReadWriter]. Writing to a command writes to its standard
// input. Reading from a command reads from its standard output. Commands may
// optionally implement [Logger] to capture standard error and [Coder] to
// represent exit codes.
//
// Commands are instantiated by a [Runner]. This package contains several
// Runner implementations: [lesiw.io/cmdio/sys], which runs commands on the
// local system; [lesiw.io/cmdio/ctr], which runs commands in containers; and
// [lesiw.io/cmdio/sub], which runs commands as subcommands.
//
// While most of this package is written to support traditional Go error
// handling, Must-type functions, such as [Runner.MustRun] and [MustPipe], are
// provided to support a script-like programming style, where failures result
// in panics.
package cmdio
import (
"context"
"fmt"
"io"
"os"
"lesiw.io/prefix"
)
// A Commander instantiates commands.
//
// The Command function accepts a [context.Context], a map of environment
// variables, and a variable number of arguments representing the command
// itself. It returns a [Command].
type Commander interface {
Command(
ctx context.Context,
env map[string]string,
arg ...string,
) (cmd Command)
}
// An Enver has environment variables.
//
// A [Commander] that also implements this interface will call Env to retrieve
// environment variables.
type Enver interface {
Env(name string) (value string)
}
// A Logger accepts an [io.Writer] for logging diagnostic information.
//
// Implementing this interface is the idiomatic way for commands to represent
// standard error.
type Logger interface {
Log(io.Writer)
}
// A Coder has an exit code.
//
// Implementing this interface is the idiomatic way for commands to represent
// exit codes.
type Coder interface {
Code() int
}
// An Attacher can be connected directly to the controlling terminal.
// An attached command cannot be written to.
// It must be readable exactly once. The read must block for the duration of
// command execution, after which it must exit with 0 bytes read.
type Attacher interface {
Attach() error
}
// A [Command] is the broadest possible command interface.
//
// Commands must not begin execution until the first time they are read from or
// written to. They must return [io.EOF] once execution has completed and all
// output has been consumed.
//
// In general, the Write method will correspond to standard in, the Read
// method will correspond to standard out, and an [io.Writer] may be passed
// to Log for handling standard error.
type Command interface {
io.ReadWriteCloser
fmt.Stringer
Attacher
Coder
Logger
}
// A [NopCommand] is an empty [Command] implementation. It is useful for
// embedding in command implementations that may not choose to implement
// optional interfaces.
type NopCommand struct{}
func (NopCommand) Read([]byte) (int, error) { return 0, nil }
func (NopCommand) Write([]byte) (int, error) { return 0, nil }
func (NopCommand) Close() error { return nil }
func (NopCommand) String() string { return "<nop>" }
func (NopCommand) Attach() error { return nil }
func (NopCommand) Code() int { return 0 }
func (NopCommand) Log(io.Writer) {}
// Trace is an [io.Writer] to which command tracing information is written.
// To disable tracing, set this variable to [io.Discard].
var Trace io.Writer = prefix.NewWriter("+ ", stderr)
var (
stdout io.Writer = os.Stdout
stderr io.Writer = os.Stderr
)