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() }