progress works with **multiline** input | stabilised

This commit is contained in:
xdrm-brackets 2019-01-30 20:15:39 +01:00
parent 9a73ed5c3c
commit 96d096eabb
1 changed files with 38 additions and 43 deletions

View File

@ -2,80 +2,75 @@ package clifmt
import ( import (
"fmt" "fmt"
"math"
"reflect" "reflect"
"strings" "strings"
) )
var ErrNoNewline = fmt.Errorf("no newline allowed in progress mode") // Printpf prints with possible updatable values.
// Arguments can be interface{} (standard fmt.Printf)
// Printpf prints a progress (dynamic) line that rewrites itself // or can be channels (chan interface{}) that will make the
// on arguments' update // output update on channel reception.
//
// You SHOULD launch it in a goroutine i.e. go clifmt.Printpf()
// in order for the select{} to work
//
// It can work with multiple lines thanks to ANSI escape sequences
// that allows to rewrite previously written lines
func Printpf(format string, args ...interface{}) error { func Printpf(format string, args ...interface{}) error {
// 1. check format // 1. init
if strings.ContainsAny(format, "\n\r") { values := make([]interface{}, len(args), len(args)) // actual values
return ErrNoNewline channels := make([]chan interface{}, 0, len(args)) // channels that update values
} indexes := make([]int, 0, len(args)) // association [channel order -> index in @fixed]
// 2. init // 2. manage values vs. channels
fixed := make([]interface{}, len(args), len(args)) // actual values
update := make([]chan interface{}, 0, len(args)) // channels that update values
updateIndex := make([]int, 0, len(args)) // association [order -> index in @fixed]
// 3. manage fixed values vs. updatable values (channels)
for i, arg := range args { for i, arg := range args {
// channel -> keep Zero value + store channel // channel -> keep Zero value + store channel
if reflect.TypeOf(arg).Kind() == reflect.Chan { if reflect.TypeOf(arg).Kind() == reflect.Chan {
updateIndex = append(updateIndex, i) indexes = append(indexes, i)
update = append(update, arg.(chan interface{})) channels = append(channels, arg.(chan interface{}))
continue continue
} }
// raw -> set value // raw -> set value
fixed[i] = arg values[i] = arg
} }
// 4. launch dynamic select for each channel // 3. launch dynamic select for each channel
maxlen := 0 var rows int = -1
nselect(update, func(i int, value interface{}, ok bool) { nselect(channels, func(i int, value interface{}, ok bool) {
// channel is closed -> do nothing // (1) channel is closed -> do nothing
if !ok { if !ok {
return return
} }
// extract real index // (2) update value
index := updateIndex[i] index := indexes[i]
values[index] = value
// update value // (3) don't print on error (values still NIL)
fixed[index] = value str, err := Sprintf(format, values...)
// ignore on errors (updatable values still NIL)
str, err := Sprintf(format, fixed...)
if err != nil { if err != nil {
return return
} }
reallen := displaySize(str)
// print string // (4) rewind N lines (written previous time)
fmt.Printf("\r%s", str) if rows >= 0 {
fmt.Printf("\x1b[%dF\x1b[K", rows)
// pad right to end of max size
if reallen < maxlen {
pad := make([]byte, 0, maxlen-reallen)
for i := reallen; i < maxlen; i++ {
pad = append(pad, ' ')
}
fmt.Printf("%s", pad)
} else {
maxlen = reallen
} }
rows = int(math.Max(float64(strings.Count(str, "\n")), 1))
// (5) make each line rewrite previous line
str = strings.Replace(str, "\n", "\n\x1b[K", 11)
// (6) print string
fmt.Printf("%s", str)
}) })
fmt.Printf("\n")
return nil return nil
} }