英文:
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
临时工作代码(我意识到我的 goroutine 可能不太优雅,但这只是一个示例)。
package main
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/theckman/yacspin"
)
func main() {
// 创建一个旋转器
spinner, err := createSpinner()
if err != nil {
fmt.Printf("failed to make spinner from config struct: %v\n", err)
os.Exit(1)
}
// 启动旋转器
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)
// 只想要一个 goroutine 在运行,但这不重要
max := make(chan struct{}, 1)
go func(s *yacspin.Spinner) {
// 尝试将此代码移到 worker goroutine 中,但效果相同
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
}()
// 执行工作并在完成后打印结果。
fmt.Println(d)
// 向旋转器的 goroutine 发送一个值,以便它可以显示更新后的计数
time.Sleep(500 * time.Millisecond)
spinnerCh <- 1
wg.Done()
}(wg, d)
}
}()
// 模拟排队一些工作
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) {
// 构建配置,每个字段都有文档说明
cfg := yacspin.Config{
Frequency: 100 * time.Millisecond,
CharSet: yacspin.CharSets[11],
Suffix: " ", // 在动画旋转器和消息之间至少放一个空格
// 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) {
// 确保在退出之前停止旋转器,否则光标将保持隐藏,终端将需要“reset”
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigCh
spinner.StopFailMessage("interrupted")
// 故意忽略错误
_ = spinner.StopFail()
os.Exit(0)
}()
}
英文:
What I am trying to do is use a spinner to indicate work progress in a few goroutines. The issue I am having is that both the spinner message and job completion message is being logged in the same line. What I am asking is how can I have the spinner pinned to the bottom of so that the spinner status is not getting in the way of the logged messages itself?
The output I am getting is
a
⡿ 1/26c
⣽ 2/26b
⣯ 3/26e
⢿ 4/26d
⣾ 5/26g
⣟ 6/26f
What I am trying to get is
a
b
c
d
e
f
⣟ 6/26
Temp working code. (I realize my go routines may not be very elegant, but this is just an example).
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)
}()
}
答案1
得分: 1
根据你的初始问题,以下是不显示行中旋转器的解决方案:
1)引入一个变量 pauseItForAMoment := false
,它将帮助在一个goroutine中忽略是否暂停。
func main() {
pauseItForAMoment := false
goroutine看起来像这样:
go func(s *yacspin.Spinner) {
// 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)
2)在打印d
时停止和启动旋转器:
pauseItForAMoment = true
spinner.Prefix(d)
spinner.Stop()
// fmt.Println(d)
spinner.Start()
pauseItForAMoment = false
3)将旋转器的一些配置值更改为:
StopCharacter: " ",
// StopMessage: "done",
请注意,你的goroutines并不完全正确地按顺序打印它们,你已经知道了,希望你能修复它。
输出如下所示:
如果有什么不清楚的地方,这是代码 https://go.dev/play/p/0lOza3aapf2
我更喜欢以下面这种方式显示输出,代码在这里 https://go.dev/play/p/W8Y1kwNqphl (只是我的个人意见;-) 如果你不需要可以忽略它)
英文:
Based on your initial question, here is the solution for not showing the spinner in the lines:
- Introduce a variable
pauseItForAMoment := false
which will help to ignore to do Pause or not in one of your goroutine.
func main() {
pauseItForAMoment := false
And the goroutine looks like this:
// 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)
- Stop and Start the spinner while printing
d
pauseItForAMoment = true
spinner.Prefix(d)
spinner.Stop()
// fmt.Println(d)
spinner.Start()
pauseItForAMoment = false
- Change few of the spinner config values to:
StopCharacter: " ",
// StopMessage: "done",
Note it, your goroutines are not exactly correct to print them in the order, which you know already, hope you will fix that.
Output looks like this:
Here is the code if something is not clear https://go.dev/play/p/0lOza3aapf2
I prefer to show the output like this below & the code for that is https://go.dev/play/p/W8Y1kwNqphl (just my 2cents ignore it if you don't need it)
答案2
得分: 0
这是你要翻译的内容:
这是库的作者。根据你在你打开的GitHub问题中分享的代码片段,我认为你只需要将StopCharacter
的值设置为空字符串,而不是" "
。这样对我来说就可以渲染成这样...
这是代码在Go Playground上的链接,因为我太笨了,不知道如何在这里复制和粘贴而不丢失格式。
希望这可以帮到你!
英文:
Author of the library here. Based on the snippet you shared in the GitHub issue you opened, I think you just need to set the StopCharacter
value to an empty string instead of " "
. It made it render like this for me...
Here is the code on the Go Playground, since I am too dumb to figure out how to copy and paste it here without losing formatting.
I hope this helps!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论