go/statuschart/tty.go

Summary

Maintainability
A
0 mins
Test Coverage
package statuschart

import (
    "fmt"
    "sort"
    "strings"

    "github.com/burke/ttyutils"
    slog "github.com/burke/zeus/go/shinylog"

    "os"

    "github.com/burke/zeus/go/processtree"
)

const (
    lineT = "{yellow}├── "
    lineL = "{yellow}└── "
    lineI = "{yellow}│   "
    lineX = "{yellow}    "
)

func ttyStart(tree *processtree.ProcessTree, done, quit chan bool) {
    go func() {
        scw := &StringChannelWriter{make(chan string, 10)}
        slog.SetDefaultLogger(slog.NewShinyLogger(scw, scw))

        termios, err := ttyutils.NoEcho(uintptr(os.Stdout.Fd()))
        if err != nil {
            theChart.terminalSupported = false
        }

        for {
            select {
            case <-quit:
                ttyutils.RestoreTerminalState(uintptr(os.Stdout.Fd()), termios)
                done <- true
                return
            case output := <-scw.Notif:
                theChart.L.Lock()
                if theChart.drawnInitial {
                    print(output)
                }
                theChart.extraOutput += output
                theChart.L.Unlock()
                theChart.draw()
            case <-theChart.update:
                theChart.draw()
            }
        }
    }()
}

func (s *StatusChart) draw() {
    s.L.Lock()
    defer s.L.Unlock()

    if s.drawnInitial {
        lengthOfOutput := s.lengthOfOutput()
        numberOfOutputLines := s.numberOfSlaves + len(s.Commands) + lengthOfOutput + 3
        fmt.Printf("\033[%dA", numberOfOutputLines)
    } else {
        s.drawnInitial = true
    }

    log := theChart.directLogger

    log.Colorized("\x1b[4m{green}[ready] {red}[crashed] {blue}[running] {magenta}[connecting] {yellow}[waiting]\033[K")
    s.drawSubtree(s.RootSlave, "", "")

    log.Colorized("\033[K\n\x1b[4mAvailable Commands: {yellow}[waiting] {red}[crashed] {green}[ready]\033[K")
    s.drawCommands()
    output := strings.Replace(s.extraOutput, "\n", "\033[K\n", -1)
    fmt.Printf(output)
}

func (s *StatusChart) lengthOfOutput() int {
    ts, err := ttyutils.Winsize(os.Stdout)
    if err != nil {
        // This can happen when the output is redirected to a device
        // that blows up on the ioctl Winsize uses. We don't care about fancy drawing in this case.
        return 0
    }
    width := int(ts.Columns)
    if width == 0 { // output has been redirected
        return 0
    }

    lines := strings.Split(s.extraOutput, "\n")

    numLines := 0
    for _, line := range lines {
        n := (len(line) + width - 1) / width
        if n == 0 {
            n = 1
        }
        numLines += n
    }

    return numLines - 1
}

func (s *StatusChart) drawCommands() {
    sort.Sort(processtree.Commands(s.Commands))

    for _, command := range s.Commands {
        state := command.Parent.State()

        alia := strings.Join(command.Aliases, ", ")
        var aliasPart string
        if len(alia) > 0 {
            aliasPart = " (alias: " + alia + ")"
        }
        text := "zeus " + command.Name + aliasPart
        reset := "\033[K"

        log := theChart.directLogger

        switch state {
        case processtree.SReady:
            log.Green(text + reset)
        case processtree.SCrashed:
            log.Red(text + " {yellow}[run to see backtrace]" + reset)
        default:
            log.Yellow(text + reset)
        }
    }
}

func (s *StatusChart) drawSubtree(node *processtree.SlaveNode, myIndentation, childIndentation string) {
    printStateInfo(myIndentation, node.Name, node.State(), false, true)

    for i, slave := range node.Slaves {
        if i == len(node.Slaves)-1 {
            s.drawSubtree(slave, childIndentation+lineL, childIndentation+lineX)
        } else {
            s.drawSubtree(slave, childIndentation+lineT, childIndentation+lineI)
        }
    }
}

type StringChannelWriter struct {
    Notif chan string
}

func (s *StringChannelWriter) Write(o []byte) (int, error) {
    s.Notif <- string(o)
    return len(o), nil
}