Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Multiple progressbars and spinners support #544

Merged
merged 6 commits into from
Aug 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 53 additions & 0 deletions _examples/multiple-live-printers/demo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package main

import (
"github.com/pterm/pterm"
"time"
)

func main() {
multi := pterm.DefaultMultiPrinter

spinner1, _ := pterm.DefaultSpinner.WithWriter(multi.NewWriter()).Start("Spinner 1")
spinner2, _ := pterm.DefaultSpinner.WithWriter(multi.NewWriter()).Start("Spinner 2")
pb1, _ := pterm.DefaultProgressbar.WithTotal(100).WithWriter(multi.NewWriter()).Start("Progressbar 1")
pb2, _ := pterm.DefaultProgressbar.WithTotal(100).WithWriter(multi.NewWriter()).Start("Progressbar 2")
pb3, _ := pterm.DefaultProgressbar.WithTotal(100).WithWriter(multi.NewWriter()).Start("Progressbar 3")
pb4, _ := pterm.DefaultProgressbar.WithTotal(100).WithWriter(multi.NewWriter()).Start("Progressbar 4")
pb5, _ := pterm.DefaultProgressbar.WithTotal(100).WithWriter(multi.NewWriter()).Start("Progressbar 5")

multi.Start()

// Randomly increment progress bars for demo purposes.
for i := 1; i <= 100; i++ {
pb1.Increment()

if i%2 == 0 {
pb2.Add(3)
}

if i%5 == 0 {
pb3.Increment()
}

if i%10 == 0 {
pb4.Increment()
}

if i%3 == 0 {
pb5.Increment()
}

if i%50 == 0 {
spinner1.Success("Spinner 1 is done!")
}

if i%60 == 0 {
spinner2.Fail("Spinner 2 failed!")
}

time.Sleep(time.Millisecond * 50)
}

multi.Stop()
}
43 changes: 43 additions & 0 deletions _examples/progressbar/multiple/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"github.com/pterm/pterm"
"time"
)

func main() {
multi := pterm.DefaultMultiPrinter

pb1, _ := pterm.DefaultProgressbar.WithTotal(100).WithWriter(multi.NewWriter()).Start("Progressbar 1")
pb2, _ := pterm.DefaultProgressbar.WithTotal(100).WithWriter(multi.NewWriter()).Start("Progressbar 2")
pb3, _ := pterm.DefaultProgressbar.WithTotal(100).WithWriter(multi.NewWriter()).Start("Progressbar 3")
pb4, _ := pterm.DefaultProgressbar.WithTotal(100).WithWriter(multi.NewWriter()).Start("Progressbar 4")
pb5, _ := pterm.DefaultProgressbar.WithTotal(100).WithWriter(multi.NewWriter()).Start("Progressbar 5")

multi.Start()

// Randomly increment progress bars for demo purposes.
for i := 1; i <= 100; i++ {
pb1.Increment()

if i%2 == 0 {
pb2.Add(3)
}

if i%5 == 0 {
pb3.Increment()
}

if i%10 == 0 {
pb4.Increment()
}

if i%3 == 0 {
pb5.Increment()
}

time.Sleep(time.Millisecond * 50)
}

multi.Stop()
}
25 changes: 25 additions & 0 deletions _examples/spinner/multiple/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"github.com/pterm/pterm"
"time"
)

func main() {
multi := pterm.DefaultMultiPrinter

spinner1, _ := pterm.DefaultSpinner.WithWriter(multi.NewWriter()).Start("Spinner 1")
spinner2, _ := pterm.DefaultSpinner.WithWriter(multi.NewWriter()).Start("Spinner 2")
spinner3, _ := pterm.DefaultSpinner.WithWriter(multi.NewWriter()).Start("Spinner 3")

multi.Start()

time.Sleep(time.Millisecond * 1000)
spinner1.Success("Spinner 1 is done!")
time.Sleep(time.Millisecond * 750)
spinner2.Fail("Spinner 2 failed!")
time.Sleep(time.Millisecond * 500)
spinner3.Warning("Spinner 3 has a warning!")

multi.Stop()
}
6 changes: 6 additions & 0 deletions area_printer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package pterm

import (
"io"
"strings"

"atomicgo.dev/cursor"
Expand Down Expand Up @@ -47,6 +48,11 @@ func (p AreaPrinter) WithCenter(b ...bool) *AreaPrinter {
return &p
}

// SetWriter sets the writer for the AreaPrinter.
func (p *AreaPrinter) SetWriter(writer io.Writer) {

}

// Update overwrites the content of the AreaPrinter.
// Can be used live.
func (p *AreaPrinter) Update(text ...interface{}) {
Expand Down
2 changes: 1 addition & 1 deletion ci/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func main() {
os.WriteFile("./docs/docs/putils.md", []byte(putilsReadme), 0600)
})

do("Geneating Printers Table", currentLevel, func(currentLevel int) {
do("Geneating printers Table", currentLevel, func(currentLevel int) {
// get features located in "_examples/*"
files, _ := os.ReadDir("./_examples/")

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.18
require (
atomicgo.dev/cursor v0.2.0
atomicgo.dev/keyboard v0.2.9
atomicgo.dev/schedule v0.0.2
atomicgo.dev/schedule v0.1.0
github.com/MarvinJWendt/testza v0.5.2
github.com/gookit/color v1.5.4
github.com/lithammer/fuzzysearch v1.1.8
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
atomicgo.dev/schedule v0.0.2 h1:2e/4KY6t3wokja01Cyty6qgkQM8MotJzjtqCH70oX2Q=
atomicgo.dev/schedule v0.0.2/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=
atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
Expand Down
4 changes: 4 additions & 0 deletions interface_live_printer.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package pterm

import "io"

// LivePrinter is a printer which can update it's output live.
type LivePrinter interface {
// GenericStart runs Start, but returns a LivePrinter.
Expand All @@ -11,4 +13,6 @@ type LivePrinter interface {
// This is used for the interface LivePrinter.
// You most likely want to use Stop instead of this in your program.
GenericStop() (*LivePrinter, error)

SetWriter(writer io.Writer)
}
124 changes: 124 additions & 0 deletions multi_live_printer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package pterm

import (
"atomicgo.dev/schedule"
"bytes"
"io"
"os"
"strings"
"time"
)

var DefaultMultiPrinter = MultiPrinter{
printers: []LivePrinter{},
Writer: os.Stdout,
UpdateDelay: time.Millisecond * 200,

buffers: []*bytes.Buffer{},
area: DefaultArea,
}

type MultiPrinter struct {
IsActive bool
Writer io.Writer
UpdateDelay time.Duration

printers []LivePrinter
buffers []*bytes.Buffer
area AreaPrinter
}

// SetWriter sets the writer for the AreaPrinter.
func (p *MultiPrinter) SetWriter(writer io.Writer) {
p.Writer = writer
}

// WithWriter returns a fork of the MultiPrinter with a new writer.
func (p MultiPrinter) WithWriter(writer io.Writer) *MultiPrinter {
p.Writer = writer
return &p
}

// WithUpdateDelay returns a fork of the MultiPrinter with a new update delay.
func (p MultiPrinter) WithUpdateDelay(delay time.Duration) *MultiPrinter {
p.UpdateDelay = delay
return &p
}

func (p *MultiPrinter) NewWriter() io.Writer {
buf := bytes.NewBufferString("")
p.buffers = append(p.buffers, buf)
return buf
}

// getString returns all buffers appended and separated by a newline.
func (p *MultiPrinter) getString() string {
var buffer bytes.Buffer
for _, b := range p.buffers {
s := b.String()
s = strings.Trim(s, "\n")

parts := strings.Split(s, "\r") // only get the last override
s = parts[len(parts)-1]

// check if s is empty, if so get one part before, repeat until not empty
for s == "" {
parts = parts[:len(parts)-1]
s = parts[len(parts)-1]
}

s = strings.Trim(s, "\n\r")
buffer.WriteString(s)
buffer.WriteString("\n")
}
return buffer.String()
}

func (p *MultiPrinter) Start() (*MultiPrinter, error) {
p.IsActive = true
for _, printer := range p.printers {
printer.GenericStart()
}

schedule.Every(p.UpdateDelay, func() bool {
if !p.IsActive {
return false
}

p.area.Update(p.getString())

return true
})

return p, nil
}

func (p *MultiPrinter) Stop() (*MultiPrinter, error) {
p.IsActive = false
for _, printer := range p.printers {
printer.GenericStop()
}
time.Sleep(time.Millisecond * 20)
p.area.Update(p.getString())
p.area.Stop()

return p, nil
}

// GenericStart runs Start, but returns a LivePrinter.
// This is used for the interface LivePrinter.
// You most likely want to use Start instead of this in your program.
func (p MultiPrinter) GenericStart() (*LivePrinter, error) {
p2, _ := p.Start()
lp := LivePrinter(p2)
return &lp, nil
}

// GenericStop runs Stop, but returns a LivePrinter.
// This is used for the interface LivePrinter.
// You most likely want to use Stop instead of this in your program.
func (p MultiPrinter) GenericStop() (*LivePrinter, error) {
p2, _ := p.Stop()
lp := LivePrinter(p2)
return &lp, nil
}