除了 goroutines 之外,我如何使用微调器以使输出不会相互覆盖?
How can I use a spinner in addition to goroutines so that the output is not overriding each other?
我想做的是使用微调器来指示几个 goroutine 中的工作进度。我遇到的问题是微调器消息和作业完成消息都记录在同一行中。我想问的是如何将微调器固定到底部,以便微调器状态不会妨碍记录的消息本身?
我得到的输出是
a
⡿ 1/26c
⣽ 2/26b
⣯ 3/26e
⢿ 4/26d
⣾ 5/26g
⣟ 6/26f
我想得到的是
a
b
c
d
e
f
⣟ 6/26
临时工作代码。 (我意识到我的go例程可能不是很优雅,但这只是一个例子)。
package main
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/theckman/yacspin"
)
func main() {
// create a spinner
spinner, err := createSpinner()
if err != nil {
fmt.Printf("failed to make spinner from config struct: %v\n", err)
os.Exit(1)
}
// start the spinner
if err := spinner.Start(); err != nil {
panic(err)
}
wg := &sync.WaitGroup{}
wg.Add(1)
var (
total int
current int
)
spinnerCh := make(chan int, 0)
data := make(chan string)
// only want one go routine at a time but this is not important
max := make(chan struct{}, 1)
go func(s *yacspin.Spinner) {
// tried moving this into the worker goroutine also, but same effect
for range spinnerCh {
current += 1
if err := s.Pause(); err != nil {
panic(err)
}
s.Message(fmt.Sprintf("%d/%d", current, total))
if err := s.Unpause(); err != nil {
panic(err)
}
}
}(spinner)
go func() {
defer wg.Done()
for d := range data {
wg.Add(1)
go func(wg *sync.WaitGroup, d string) {
max <- struct{}{}
defer func() {
<-max
}()
// function is doing work and printing the result once done.
fmt.Println(d)
// sends a value to the spinner go routine so that it can show
// the updated count
time.Sleep(500 * time.Millisecond)
spinnerCh <- 1
wg.Done()
}(wg, d)
}
}()
// simulate queing some work
ss := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
for _, s := range ss {
data <- s
}
total = len(ss)
close(data)
wg.Wait()
close(spinnerCh)
}
func createSpinner() (*yacspin.Spinner, error) {
// build the configuration, each field is documented
cfg := yacspin.Config{
Frequency: 100 * time.Millisecond,
CharSet: yacspin.CharSets[11],
Suffix: " ", // puts a least one space between the animating spinner and the Message
// Message: "collecting files",
SuffixAutoColon: true,
ColorAll: true,
Colors: []string{"fgYellow"},
StopCharacter: "✓",
StopColors: []string{"fgGreen"},
StopMessage: "done",
StopFailCharacter: "✗",
StopFailColors: []string{"fgRed"},
StopFailMessage: "failed",
}
s, err := yacspin.New(cfg)
if err != nil {
return nil, fmt.Errorf("failed to make spinner from struct: %w", err)
}
return s, nil
}
func stopOnSignal(spinner *yacspin.Spinner) {
// ensure we stop the spinner before exiting, otherwise cursor will remain
// hidden and terminal will require a `reset`
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigCh
spinner.StopFailMessage("interrupted")
// ignoring error intentionally
_ = spinner.StopFail()
os.Exit(0)
}()
}
根据您最初的问题,以下是不在行中显示微调器的解决方案:
- 引入一个变量
pauseItForAMoment := false
这将有助于忽略在您的一个 goroutine 中是否暂停。
func main() {
pauseItForAMoment := false
goroutine 看起来像这样:
// tried moving this into the worker goroutine also, but same effect
for range spinnerCh {
current += 1
if !pauseItForAMoment {
if err := s.Pause(); err != nil {
panic(err)
}
s.Message(fmt.Sprintf("%d/%d", current, total))
if err := s.Unpause(); err != nil {
panic(err)
}
}
}
}(spinner)
- 打印时停止和启动微调器
d
pauseItForAMoment = true
spinner.Prefix(d)
spinner.Stop()
// fmt.Println(d)
spinner.Start()
pauseItForAMoment = false
- 将几个微调器配置值更改为:
StopCharacter: " ",
// StopMessage: "done",
请注意,您的 goroutine 并不完全正确地按照您已经知道的顺序打印它们,希望您能解决这个问题。
输出如下所示:
这里是代码,如果有什么不清楚的话https://go.dev/play/p/0lOza3aapf2
我更喜欢像下面这样显示输出,代码是 https://go.dev/play/p/W8Y1kwNqphl(只是我的 2cents ;-) 如果你不需要它,请忽略它)
这里是图书馆的作者。根据您在 GitHub issue you opened 中分享的片段,我认为您只需将 StopCharacter
值设置为空字符串而不是 " "
。它让我这样渲染...
这是 Go Playground 上的the code,因为我太笨不知道如何在不丢失格式的情况下复制和粘贴它。
希望对您有所帮助!
我想做的是使用微调器来指示几个 goroutine 中的工作进度。我遇到的问题是微调器消息和作业完成消息都记录在同一行中。我想问的是如何将微调器固定到底部,以便微调器状态不会妨碍记录的消息本身?
我得到的输出是
a
⡿ 1/26c
⣽ 2/26b
⣯ 3/26e
⢿ 4/26d
⣾ 5/26g
⣟ 6/26f
我想得到的是
a
b
c
d
e
f
⣟ 6/26
临时工作代码。 (我意识到我的go例程可能不是很优雅,但这只是一个例子)。
package main
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/theckman/yacspin"
)
func main() {
// create a spinner
spinner, err := createSpinner()
if err != nil {
fmt.Printf("failed to make spinner from config struct: %v\n", err)
os.Exit(1)
}
// start the spinner
if err := spinner.Start(); err != nil {
panic(err)
}
wg := &sync.WaitGroup{}
wg.Add(1)
var (
total int
current int
)
spinnerCh := make(chan int, 0)
data := make(chan string)
// only want one go routine at a time but this is not important
max := make(chan struct{}, 1)
go func(s *yacspin.Spinner) {
// tried moving this into the worker goroutine also, but same effect
for range spinnerCh {
current += 1
if err := s.Pause(); err != nil {
panic(err)
}
s.Message(fmt.Sprintf("%d/%d", current, total))
if err := s.Unpause(); err != nil {
panic(err)
}
}
}(spinner)
go func() {
defer wg.Done()
for d := range data {
wg.Add(1)
go func(wg *sync.WaitGroup, d string) {
max <- struct{}{}
defer func() {
<-max
}()
// function is doing work and printing the result once done.
fmt.Println(d)
// sends a value to the spinner go routine so that it can show
// the updated count
time.Sleep(500 * time.Millisecond)
spinnerCh <- 1
wg.Done()
}(wg, d)
}
}()
// simulate queing some work
ss := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
for _, s := range ss {
data <- s
}
total = len(ss)
close(data)
wg.Wait()
close(spinnerCh)
}
func createSpinner() (*yacspin.Spinner, error) {
// build the configuration, each field is documented
cfg := yacspin.Config{
Frequency: 100 * time.Millisecond,
CharSet: yacspin.CharSets[11],
Suffix: " ", // puts a least one space between the animating spinner and the Message
// Message: "collecting files",
SuffixAutoColon: true,
ColorAll: true,
Colors: []string{"fgYellow"},
StopCharacter: "✓",
StopColors: []string{"fgGreen"},
StopMessage: "done",
StopFailCharacter: "✗",
StopFailColors: []string{"fgRed"},
StopFailMessage: "failed",
}
s, err := yacspin.New(cfg)
if err != nil {
return nil, fmt.Errorf("failed to make spinner from struct: %w", err)
}
return s, nil
}
func stopOnSignal(spinner *yacspin.Spinner) {
// ensure we stop the spinner before exiting, otherwise cursor will remain
// hidden and terminal will require a `reset`
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigCh
spinner.StopFailMessage("interrupted")
// ignoring error intentionally
_ = spinner.StopFail()
os.Exit(0)
}()
}
根据您最初的问题,以下是不在行中显示微调器的解决方案:
- 引入一个变量
pauseItForAMoment := false
这将有助于忽略在您的一个 goroutine 中是否暂停。
func main() {
pauseItForAMoment := false
goroutine 看起来像这样:
// tried moving this into the worker goroutine also, but same effect
for range spinnerCh {
current += 1
if !pauseItForAMoment {
if err := s.Pause(); err != nil {
panic(err)
}
s.Message(fmt.Sprintf("%d/%d", current, total))
if err := s.Unpause(); err != nil {
panic(err)
}
}
}
}(spinner)
- 打印时停止和启动微调器
d
pauseItForAMoment = true
spinner.Prefix(d)
spinner.Stop()
// fmt.Println(d)
spinner.Start()
pauseItForAMoment = false
- 将几个微调器配置值更改为:
StopCharacter: " ",
// StopMessage: "done",
请注意,您的 goroutine 并不完全正确地按照您已经知道的顺序打印它们,希望您能解决这个问题。
输出如下所示:
这里是代码,如果有什么不清楚的话https://go.dev/play/p/0lOza3aapf2
我更喜欢像下面这样显示输出,代码是 https://go.dev/play/p/W8Y1kwNqphl(只是我的 2cents ;-) 如果你不需要它,请忽略它)
这里是图书馆的作者。根据您在 GitHub issue you opened 中分享的片段,我认为您只需将 StopCharacter
值设置为空字符串而不是 " "
。它让我这样渲染...
这是 Go Playground 上的the code,因为我太笨不知道如何在不丢失格式的情况下复制和粘贴它。
希望对您有所帮助!