Add managed reporter output state
This commit is contained in:
186
reporter.go
Normal file
186
reporter.go
Normal file
@@ -0,0 +1,186 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user