mirror of
https://github.com/hanzoai/container.git
synced 2026-10-30 15:06:01 +00:00
154 lines
3.0 KiB
Go
154 lines
3.0 KiB
Go
package terminal
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/fatih/color"
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
var _ io.WriteCloser = (*verboseWriter)(nil)
|
|
|
|
type verboseWriter struct {
|
|
buf bytes.Buffer
|
|
lines []string
|
|
|
|
lineHeight int
|
|
termWidth int
|
|
overflow int
|
|
|
|
lastUpdate time.Time
|
|
}
|
|
|
|
// NewVerboseWriter creates a new verbose writer.
|
|
// A verbose writer pipes the input received to the stdout while tailing the specified lines.
|
|
// Calling `Close` when done is recommended to clear the last uncleared output.
|
|
func NewVerboseWriter(lineHeight int) io.WriteCloser {
|
|
return &verboseWriter{lineHeight: lineHeight}
|
|
}
|
|
|
|
func (v *verboseWriter) Write(p []byte) (n int, err error) {
|
|
// if it's not a terminal, simply write to stdout
|
|
if !isTerminal {
|
|
return os.Stdout.Write(p)
|
|
}
|
|
|
|
for i, c := range p {
|
|
if c != '\n' {
|
|
v.buf.WriteByte(c)
|
|
continue
|
|
}
|
|
|
|
if err := v.refresh(); err != nil {
|
|
return i + 1, err
|
|
}
|
|
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
func (v *verboseWriter) printLineVerbose() {
|
|
line := v.sanitizeLine(v.buf.String())
|
|
line = color.HiBlackString(line)
|
|
_, _ = fmt.Fprintln(os.Stderr, line)
|
|
}
|
|
|
|
func (v *verboseWriter) refresh() error {
|
|
v.clearScreen()
|
|
v.addLine()
|
|
return v.printScreen()
|
|
}
|
|
|
|
func (v *verboseWriter) addLine() {
|
|
defer v.buf.Reset()
|
|
|
|
// if height <=0, do not scroll
|
|
if v.lineHeight <= 0 {
|
|
v.printLineVerbose()
|
|
return
|
|
}
|
|
|
|
if len(v.lines) >= v.lineHeight {
|
|
v.lines = v.lines[1:]
|
|
}
|
|
v.lines = append(v.lines, v.buf.String())
|
|
}
|
|
|
|
func (v *verboseWriter) Close() error {
|
|
if v.buf.Len() > 0 {
|
|
if err := v.refresh(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
v.clearScreen()
|
|
return nil
|
|
}
|
|
|
|
func (v verboseWriter) sanitizeLine(line string) string {
|
|
// remove logrus noises
|
|
if strings.HasPrefix(line, "time=") && strings.Contains(line, "msg=") {
|
|
line = line[strings.Index(line, "msg=")+4:]
|
|
if l, err := strconv.Unquote(line); err == nil {
|
|
line = l
|
|
}
|
|
}
|
|
|
|
return "> " + line
|
|
}
|
|
|
|
func (v *verboseWriter) printScreen() error {
|
|
if err := v.updateTerm(); err != nil {
|
|
return err
|
|
}
|
|
|
|
v.overflow = 0
|
|
for _, line := range v.lines {
|
|
line = v.sanitizeLine(line)
|
|
if len(line) > v.termWidth {
|
|
v.overflow += len(line) / v.termWidth
|
|
if len(line)%v.termWidth == 0 {
|
|
v.overflow -= 1
|
|
}
|
|
}
|
|
line = color.HiBlackString(line)
|
|
fmt.Println(line)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v *verboseWriter) clearScreen() {
|
|
for i := 0; i < len(v.lines)+v.overflow; i++ {
|
|
ClearLine()
|
|
}
|
|
}
|
|
|
|
func (v *verboseWriter) updateTerm() error {
|
|
// no need to refresh so quickly
|
|
if time.Since(v.lastUpdate) < time.Second*2 {
|
|
return nil
|
|
}
|
|
v.lastUpdate = time.Now().UTC()
|
|
|
|
w, _, err := term.GetSize(int(os.Stdout.Fd()))
|
|
if err != nil {
|
|
return fmt.Errorf("error getting terminal size: %w", err)
|
|
}
|
|
// A width of zero would result in a division by zero panic when computing overflow
|
|
// in printScreen. Therefore, set it to a safe - even though probably wrong - value.
|
|
// We use <= 0 here because negative values are guaranteed to lead to unexpected
|
|
// results, even if they don't cause panics.
|
|
if w <= 0 {
|
|
w = 80
|
|
}
|
|
v.termWidth = w
|
|
|
|
return nil
|
|
}
|