187 lines
4.4 KiB
Go
187 lines
4.4 KiB
Go
package commandkit
|
|
|
|
import (
|
|
"io"
|
|
"sync"
|
|
)
|
|
|
|
type reportingMode uint8
|
|
|
|
const (
|
|
modeOrdinary reportingMode = iota
|
|
modeQuiet
|
|
modeVerbose
|
|
modeDebug
|
|
)
|
|
|
|
type reportingDestination uint8
|
|
|
|
const (
|
|
destinationOutput reportingDestination = iota
|
|
destinationDiagnostics
|
|
)
|
|
|
|
type reportingState struct {
|
|
mu sync.Mutex
|
|
output destinationState
|
|
diagnostics destinationState
|
|
closed bool
|
|
mode reportingMode
|
|
progress bool
|
|
progressSet bool
|
|
}
|
|
|
|
type destinationState struct {
|
|
writer io.Writer
|
|
submitted bool
|
|
delivered bool
|
|
suppressed bool
|
|
renderError error
|
|
writeError error
|
|
}
|
|
|
|
// Reporter coordinates presentation and delivery for one invocation.
|
|
//
|
|
// The invocation supplies the reporter. Consumers use Output for substantive
|
|
// command results and the reporting methods for categorized messages.
|
|
type Reporter struct {
|
|
state *reportingState
|
|
}
|
|
|
|
type managedOutputWriter struct {
|
|
state *reportingState
|
|
destination reportingDestination
|
|
}
|
|
|
|
func newReporter(streams Streams) *Reporter {
|
|
streams = normalizeStreams(streams)
|
|
return &Reporter{state: &reportingState{
|
|
output: destinationState{writer: streams.Out},
|
|
diagnostics: destinationState{writer: streams.Err},
|
|
mode: modeOrdinary,
|
|
}}
|
|
}
|
|
|
|
// Output returns a managed writer for substantive command results.
|
|
func (r *Reporter) Output() io.Writer {
|
|
if r == nil || r.state == nil {
|
|
return managedOutputWriter{}
|
|
}
|
|
return managedOutputWriter{state: r.state, destination: destinationOutput}
|
|
}
|
|
|
|
func (w managedOutputWriter) Write(p []byte) (int, error) {
|
|
if w.state == nil {
|
|
return 0, ErrReporterClosed
|
|
}
|
|
return w.state.write(w.destination, p)
|
|
}
|
|
|
|
func (state *reportingState) write(destination reportingDestination, p []byte) (int, error) {
|
|
state.mu.Lock()
|
|
defer state.mu.Unlock()
|
|
return state.writeLocked(destination, p)
|
|
}
|
|
|
|
// writeLocked is used by reporter operations that already hold state.mu.
|
|
func (state *reportingState) writeLocked(destination reportingDestination, p []byte) (int, error) {
|
|
if state.closed {
|
|
return 0, ErrReporterClosed
|
|
}
|
|
|
|
current := state.destination(destination)
|
|
current.submitted = true
|
|
if state.suppressed(destination) {
|
|
current.suppressed = true
|
|
return len(p), nil
|
|
}
|
|
if current.writeError != nil {
|
|
return 0, current.writeError
|
|
}
|
|
|
|
n, err := current.writer.Write(p)
|
|
if err == nil && n < len(p) {
|
|
err = io.ErrShortWrite
|
|
}
|
|
if err != nil && current.writeError == nil {
|
|
current.writeError = err
|
|
}
|
|
if err == nil {
|
|
current.delivered = true
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func (state *reportingState) destination(destination reportingDestination) *destinationState {
|
|
if destination == destinationDiagnostics {
|
|
return &state.diagnostics
|
|
}
|
|
return &state.output
|
|
}
|
|
|
|
func (state *reportingState) suppressed(destination reportingDestination) bool {
|
|
if destination == destinationOutput {
|
|
return state.mode == modeQuiet
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (state *reportingState) recordRenderErrorLocked(destination reportingDestination, err error) {
|
|
if err == nil {
|
|
return
|
|
}
|
|
current := state.destination(destination)
|
|
if current.renderError == nil {
|
|
current.renderError = err
|
|
}
|
|
}
|
|
|
|
func (state *reportingState) presentationLocked() PresentationResult {
|
|
return PresentationResult{
|
|
Output: state.deliveryResultLocked(destinationOutput),
|
|
Diagnostics: state.deliveryResultLocked(destinationDiagnostics),
|
|
}
|
|
}
|
|
|
|
func (state *reportingState) presentation() PresentationResult {
|
|
state.mu.Lock()
|
|
defer state.mu.Unlock()
|
|
return state.presentationLocked()
|
|
}
|
|
|
|
func (state *reportingState) deliveryResultLocked(destination reportingDestination) DeliveryResult {
|
|
current := state.destination(destination)
|
|
result := DeliveryResult{RenderError: current.renderError, WriteError: current.writeError}
|
|
switch {
|
|
case current.renderError != nil || current.writeError != nil:
|
|
result.State = DeliveryIncomplete
|
|
case !current.submitted:
|
|
result.State = DeliveryNone
|
|
case current.delivered:
|
|
result.State = DeliveryComplete
|
|
case current.suppressed:
|
|
result.State = DeliverySuppressed
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (r *Reporter) setVisibility(mode reportingMode, progress bool, progressSet bool) {
|
|
if r == nil || r.state == nil {
|
|
return
|
|
}
|
|
r.state.mu.Lock()
|
|
r.state.mode = mode
|
|
r.state.progress = progress
|
|
r.state.progressSet = progressSet
|
|
r.state.mu.Unlock()
|
|
}
|
|
|
|
func (r *Reporter) close() {
|
|
if r == nil || r.state == nil {
|
|
return
|
|
}
|
|
r.state.mu.Lock()
|
|
r.state.closed = true
|
|
r.state.mu.Unlock()
|
|
}
|