153 lines
3.7 KiB
Go
153 lines
3.7 KiB
Go
package local
|
|
|
|
// forked from: https://github.com/moby/moby/blob/master/pkg/jsonmessage/jsonmessage.go
|
|
// so we can customize parts of the display of our progress messages
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
gotty "github.com/Nvveen/Gotty"
|
|
"github.com/pulumi/pulumi/pkg/util/contract"
|
|
)
|
|
|
|
/* Satisfied by gotty.TermInfo as well as noTermInfo from below */
|
|
type termInfo interface {
|
|
Parse(attr string, params ...interface{}) (string, error)
|
|
}
|
|
|
|
type noTermInfo struct{} // canary used when no terminfo.
|
|
|
|
func (ti *noTermInfo) Parse(attr string, params ...interface{}) (string, error) {
|
|
return "", fmt.Errorf("noTermInfo")
|
|
}
|
|
|
|
func clearLine(out io.Writer, ti termInfo) {
|
|
// el2 (clear whole line) is not exposed by terminfo.
|
|
|
|
// First clear line from beginning to cursor
|
|
if attr, err := ti.Parse("el1"); err == nil {
|
|
fmt.Fprintf(out, "%s", attr)
|
|
} else {
|
|
fmt.Fprintf(out, "\x1b[1K")
|
|
}
|
|
// Then clear line from cursor to end
|
|
if attr, err := ti.Parse("el"); err == nil {
|
|
fmt.Fprintf(out, "%s", attr)
|
|
} else {
|
|
fmt.Fprintf(out, "\x1b[K")
|
|
}
|
|
}
|
|
|
|
func cursorUp(out io.Writer, ti termInfo, l int) {
|
|
if l == 0 { // Should never be the case, but be tolerant
|
|
return
|
|
}
|
|
if attr, err := ti.Parse("cuu", l); err == nil {
|
|
fmt.Fprintf(out, "%s", attr)
|
|
} else {
|
|
fmt.Fprintf(out, "\x1b[%dA", l)
|
|
}
|
|
}
|
|
|
|
func cursorDown(out io.Writer, ti termInfo, l int) {
|
|
if l == 0 { // Should never be the case, but be tolerant
|
|
return
|
|
}
|
|
if attr, err := ti.Parse("cud", l); err == nil {
|
|
fmt.Fprintf(out, "%s", attr)
|
|
} else {
|
|
fmt.Fprintf(out, "\x1b[%dB", l)
|
|
}
|
|
}
|
|
|
|
// Display displays the Progress to `out`. `termInfo` is non-nil if `out` is a terminal.
|
|
func (jm *Progress) Display(out io.Writer, termInfo termInfo) {
|
|
var endl string
|
|
if termInfo != nil && /*jm.Stream == "" &&*/ jm.Action != "" {
|
|
clearLine(out, termInfo)
|
|
endl = "\r"
|
|
fmt.Fprintf(out, endl)
|
|
}
|
|
|
|
if jm.ID != "" && jm.ShowID {
|
|
fmt.Fprintf(out, "%s: ", jm.ID)
|
|
}
|
|
|
|
if jm.Action != "" && termInfo != nil {
|
|
fmt.Fprintf(out, "%s%s", jm.Action, endl)
|
|
} else {
|
|
var msg string
|
|
if jm.Action != "" {
|
|
msg = jm.Action
|
|
} else {
|
|
msg = jm.Message
|
|
}
|
|
|
|
fmt.Fprintf(out, "%s%s\n", msg, endl)
|
|
}
|
|
}
|
|
|
|
// DisplayProgressToStream displays a Progress stream from `in` to `out`, `isTerminal` describes if
|
|
// `out` is a terminal. If this is the case, it will print `\n` at the end of each line and move the
|
|
// cursor while displaying.
|
|
func DisplayProgressToStream(in <-chan Progress, out io.Writer, isTerminal bool) {
|
|
var (
|
|
ids = make(map[string]int)
|
|
)
|
|
|
|
var info termInfo
|
|
|
|
if isTerminal {
|
|
term := os.Getenv("TERM")
|
|
if term == "" {
|
|
term = "vt102"
|
|
}
|
|
|
|
var err error
|
|
if info, err = gotty.OpenTermInfo(term); err != nil {
|
|
info = &noTermInfo{}
|
|
}
|
|
}
|
|
|
|
for jm := range in {
|
|
diff := 0
|
|
|
|
if jm.Action != "" {
|
|
if jm.ID == "" {
|
|
contract.Failf("Must have an ID if we have an action! %s", jm.Action)
|
|
}
|
|
|
|
line, ok := ids[jm.ID]
|
|
if !ok {
|
|
// NOTE: This approach of using len(id) to
|
|
// figure out the number of lines of history
|
|
// only works as long as we clear the history
|
|
// when we output something that's not
|
|
// accounted for in the map, such as a line
|
|
// with no ID.
|
|
line = len(ids)
|
|
ids[jm.ID] = line
|
|
if info != nil {
|
|
fmt.Fprintf(out, "\n")
|
|
}
|
|
}
|
|
diff = len(ids) - line
|
|
if info != nil {
|
|
cursorUp(out, info, diff)
|
|
}
|
|
} else {
|
|
// When outputting something that isn't progress
|
|
// output, clear the history of previous lines. We
|
|
// don't want progress entries from some previous
|
|
// operation to be updated (for example, pull -a
|
|
// with multiple tags).
|
|
ids = make(map[string]int)
|
|
}
|
|
jm.Display(out, info)
|
|
if jm.Action != "" && info != nil {
|
|
cursorDown(out, info, diff)
|
|
}
|
|
}
|
|
}
|