go/statuschart/tty.go
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
}