Implement progressbar for web_connectivity test
This commit is contained in:
parent
eb4e6988b3
commit
5b31403061
|
@ -7,10 +7,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
colorable "github.com/mattn/go-colorable"
|
colorable "github.com/mattn/go-colorable"
|
||||||
|
"github.com/ooni/probe-cli/internal/log/handlers/cli/progress"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default handler outputting to stderr.
|
// Default handler outputting to stderr.
|
||||||
|
@ -71,14 +73,21 @@ func logSectionTitle(w io.Writer, f log.Fields) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bar *progress.Bar
|
||||||
|
var lastBarChars int64
|
||||||
|
|
||||||
// TypedLog is used for handling special "typed" logs to the CLI
|
// TypedLog is used for handling special "typed" logs to the CLI
|
||||||
func (h *Handler) TypedLog(t string, e *log.Entry) error {
|
func (h *Handler) TypedLog(t string, e *log.Entry) error {
|
||||||
switch t {
|
switch t {
|
||||||
case "progress":
|
case "progress":
|
||||||
// XXX replace this with something more fancy like https://github.com/tj/go-progress
|
var err error
|
||||||
fmt.Fprintf(h.Writer, "%.1f%% [%s]: %s", e.Fields.Get("percentage").(float64)*100, e.Fields.Get("key"), e.Message)
|
if bar == nil {
|
||||||
fmt.Fprintln(h.Writer)
|
bar = progress.New(1.0)
|
||||||
return nil
|
}
|
||||||
|
bar.Value(e.Fields.Get("percentage").(float64))
|
||||||
|
bar.Text(e.Message)
|
||||||
|
lastBarChars, err = bar.WriteTo(h.Writer)
|
||||||
|
return err
|
||||||
case "result_item":
|
case "result_item":
|
||||||
return logResultItem(h.Writer, e.Fields)
|
return logResultItem(h.Writer, e.Fields)
|
||||||
case "result_summary":
|
case "result_summary":
|
||||||
|
@ -96,16 +105,28 @@ func (h *Handler) DefaultLog(e *log.Entry) error {
|
||||||
level := Strings[e.Level]
|
level := Strings[e.Level]
|
||||||
names := e.Fields.Names()
|
names := e.Fields.Names()
|
||||||
|
|
||||||
color.Fprintf(h.Writer, "%s %-25s", bold.Sprintf("%*s", h.Padding+1, level), e.Message)
|
s := color.Sprintf("%s %-25s", bold.Sprintf("%*s", h.Padding+1, level), e.Message)
|
||||||
|
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
if name == "source" {
|
if name == "source" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Fprintf(h.Writer, " %s=%s", color.Sprint(name), e.Fields.Get(name))
|
s += fmt.Sprintf(" %s=%s", color.Sprint(name), e.Fields.Get(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bar != nil {
|
||||||
|
// We need to move the cursor back to the begging of the line and add some
|
||||||
|
// padding to the end of the string to delete the previous line written to
|
||||||
|
// the console.
|
||||||
|
sChars := int64(utf8.RuneCountInString(s))
|
||||||
|
fmt.Fprintf(h.Writer,
|
||||||
|
fmt.Sprintf("\r%s%s", s, strings.Repeat(" ", int(lastBarChars-sChars))),
|
||||||
|
)
|
||||||
fmt.Fprintln(h.Writer)
|
fmt.Fprintln(h.Writer)
|
||||||
|
bar.WriteTo(h.Writer)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(h.Writer, s)
|
||||||
|
fmt.Fprintln(h.Writer)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
126
internal/log/handlers/cli/progress/progress.go
Normal file
126
internal/log/handlers/cli/progress/progress.go
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
// Package progress provides a simple terminal progress bar.
|
||||||
|
package progress
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bar is a progress bar.
|
||||||
|
type Bar struct {
|
||||||
|
StartDelimiter string // StartDelimiter for the bar ("|").
|
||||||
|
EndDelimiter string // EndDelimiter for the bar ("|").
|
||||||
|
Filled string // Filled section representation ("█").
|
||||||
|
Empty string // Empty section representation ("░")
|
||||||
|
Total float64 // Total value.
|
||||||
|
Width int // Width of the bar.
|
||||||
|
|
||||||
|
value float64
|
||||||
|
tmpl *template.Template
|
||||||
|
text string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new bar with the given total.
|
||||||
|
func New(total float64) *Bar {
|
||||||
|
b := &Bar{
|
||||||
|
StartDelimiter: "|",
|
||||||
|
EndDelimiter: "|",
|
||||||
|
Filled: "█",
|
||||||
|
Empty: "░",
|
||||||
|
Total: total,
|
||||||
|
Width: 60,
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Template(`{{.Percent | printf "%3.0f"}}% {{.Bar}} {{.Text}}`)
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt returns a new bar with the given total.
|
||||||
|
func NewInt(total int) *Bar {
|
||||||
|
return New(float64(total))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text sets the text value.
|
||||||
|
func (b *Bar) Text(s string) {
|
||||||
|
b.text = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value sets the value.
|
||||||
|
func (b *Bar) Value(n float64) {
|
||||||
|
if n > b.Total {
|
||||||
|
panic("Bar update value cannot be greater than the total")
|
||||||
|
}
|
||||||
|
b.value = n
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueInt sets the value.
|
||||||
|
func (b *Bar) ValueInt(n int) {
|
||||||
|
b.Value(float64(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Percent returns the percentage
|
||||||
|
func (b *Bar) percent() float64 {
|
||||||
|
return (b.value / b.Total) * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bar returns the progress bar string.
|
||||||
|
func (b *Bar) bar() string {
|
||||||
|
p := b.value / b.Total
|
||||||
|
filled := math.Ceil(float64(b.Width) * p)
|
||||||
|
empty := math.Floor(float64(b.Width) - filled)
|
||||||
|
s := b.StartDelimiter
|
||||||
|
s += strings.Repeat(b.Filled, int(filled))
|
||||||
|
s += strings.Repeat(b.Empty, int(empty))
|
||||||
|
s += b.EndDelimiter
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the progress bar.
|
||||||
|
func (b *Bar) String() string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
Value float64
|
||||||
|
Total float64
|
||||||
|
Percent float64
|
||||||
|
StartDelimiter string
|
||||||
|
EndDelimiter string
|
||||||
|
Bar string
|
||||||
|
Text string
|
||||||
|
}{
|
||||||
|
Value: b.value,
|
||||||
|
Text: b.text,
|
||||||
|
StartDelimiter: b.StartDelimiter,
|
||||||
|
EndDelimiter: b.EndDelimiter,
|
||||||
|
Percent: b.percent(),
|
||||||
|
Bar: b.bar(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.tmpl.Execute(&buf, data); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes the progress bar to w.
|
||||||
|
func (b *Bar) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
s := fmt.Sprintf("\r %s ", b.String())
|
||||||
|
_, err := io.WriteString(w, s)
|
||||||
|
return int64(len(s)), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template for rendering. This method will panic if the template fails to parse.
|
||||||
|
func (b *Bar) Template(s string) {
|
||||||
|
t, err := template.New("").Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.tmpl = t
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user