progress works with **multiline** input | stabilised
This commit is contained in:
parent
9a73ed5c3c
commit
96d096eabb
81
progress.go
81
progress.go
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue