Implement progressbar for web_connectivity test
This commit is contained in:
		
							parent
							
								
									eb4e6988b3
								
							
						
					
					
						commit
						5b31403061
					
				| @ -7,10 +7,12 @@ import ( | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 	"unicode/utf8" | ||||
| 
 | ||||
| 	"github.com/apex/log" | ||||
| 	"github.com/fatih/color" | ||||
| 	colorable "github.com/mattn/go-colorable" | ||||
| 	"github.com/ooni/probe-cli/internal/log/handlers/cli/progress" | ||||
| ) | ||||
| 
 | ||||
| // Default handler outputting to stderr. | ||||
| @ -71,14 +73,21 @@ func logSectionTitle(w io.Writer, f log.Fields) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| var bar *progress.Bar | ||||
| var lastBarChars int64 | ||||
| 
 | ||||
| // TypedLog is used for handling special "typed" logs to the CLI | ||||
| func (h *Handler) TypedLog(t string, e *log.Entry) error { | ||||
| 	switch t { | ||||
| 	case "progress": | ||||
| 		// XXX replace this with something more fancy like https://github.com/tj/go-progress | ||||
| 		fmt.Fprintf(h.Writer, "%.1f%% [%s]: %s", e.Fields.Get("percentage").(float64)*100, e.Fields.Get("key"), e.Message) | ||||
| 		fmt.Fprintln(h.Writer) | ||||
| 		return nil | ||||
| 		var err error | ||||
| 		if bar == nil { | ||||
| 			bar = progress.New(1.0) | ||||
| 		} | ||||
| 		bar.Value(e.Fields.Get("percentage").(float64)) | ||||
| 		bar.Text(e.Message) | ||||
| 		lastBarChars, err = bar.WriteTo(h.Writer) | ||||
| 		return err | ||||
| 	case "result_item": | ||||
| 		return logResultItem(h.Writer, e.Fields) | ||||
| 	case "result_summary": | ||||
| @ -96,16 +105,28 @@ func (h *Handler) DefaultLog(e *log.Entry) error { | ||||
| 	level := Strings[e.Level] | ||||
| 	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 { | ||||
| 		if name == "source" { | ||||
| 			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)) | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Fprintln(h.Writer) | ||||
| 	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) | ||||
| 		bar.WriteTo(h.Writer) | ||||
| 	} else { | ||||
| 		fmt.Fprintf(h.Writer, s) | ||||
| 		fmt.Fprintln(h.Writer) | ||||
| 	} | ||||
| 
 | ||||
| 	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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user