基于Golang实现的跨平台、并发安全的多进度条打印程序
1203 浏览2023年10月27日作者:Cellent

Golang对并发操作有着不错的支持,所以平时在设计一些io密集型操作时我经常会使用go来处理。程序运行过程中,难免要打印一些处理进度信息,通常为了图省事会使用回车符\r来简单实现进度条功能,但是随着我对进度条使用场景的增加,逐渐发现回车符实现的进度条存在很多兼容性问题或不能满足我的某些需求:

  • 很难做到同时打印多个进度条,也很难让它们在并发操作中保持良好的同步性
  • 由于ANSI控制字符在Windows powershell中并不能正常工作,而导致不同平台表现不一致
  • 随着需求改变可能要频繁改变格式处理和边界处理相关的逻辑代码,例如在某些虚拟终端中回车符不会覆盖完整的一行的问题,需要判断先前打印的字符数,而后如果打印字符变少需要用空格补足字符数量;而这是往往又会伴随着中英文及Unicode字符宽度不一致的问题,心理负担较大...
  • 有一些特定需求,例如在打印过程中要禁止输入,要隐藏光标等


介绍


Progress是一个用Golang写的跨平台终端进度条打印程序包,它是并发安全的,并且支持高效同时输出多个进度条信息且拥有一下特点:

  • 支持中文及其他汉字文化圈字体,支持Unicode字符
  • 提供简单且丰富的API开自定义进度条风格,并可以将多种风格的进度条按照任意格式结合
  • 实现了ANSI控制字符的跨平台使用,可以禁止打印过程中的终端输入,隐藏光标,甚至可以在动态改变终端窗口的时候保持稳定的打印(此功能继承自single-line-print
  • 良好的运行性能:与其他进度条打印程序不同的是,progress不是基于定时器来以很高的频率(如1ms)一直刷新终端输出,而是基于一个打印channel信号,在需要重新渲染时才会刷新终端输出,这对多个进度条的稳定输出有着非常重要的作用


安装


像其他的package一样,只需要一条命令就可以快速在项目中引用progress:

# go get github.com/lianggaoqiang/progress


基本使用


import "github.com/lianggaoqiang/progress"

func main(){
  p := progress.Start()
  bar := progress.NewBar()
  p.AddBar(bar)
  
  for i := 0; i <= 100; i++ {
    bar.Inc()
    time.Sleep(time.Millisecond * 10)
  }
}


自定义样式


如果上方的实例不能满足你的需求,那么可以尝试使用Custom方法来自定义进度条的样式:

import "github.com/lianggaoqiang/progress"

func main() {
  p := progress.Start()
  b := progress.NewBar().Custom(
    progress.BarSetting{
      StartText:       "[",
      EndText:         "]",
      PassedText:      "-",
      FirstPassedText: ">",
      NotPassedText:   "=",
    },
  )
  p.AddBar(b)

  for i := 0; i <= 100; i++ {
    b.Percent(float64(i))
    time.Sleep(time.Millisecond * 30)
  }
}

BarSetting结构体包含了丰富的与自定义样式相关的属性:

  • LeftSpace, RightSpace (string): 进度条左右两侧的间距
  • Total (uint16): 进度条内字符数量
  • Hidden (bool): 如果设置为true,进度条街为将不会插入换行符(即行内进度条)
  • HidePercent (bool): 如果设置为true,将会隐藏进度条街为的百分比值
  • UseFloat (bool): 如果设置为true,进度条的打印即百分比值的精度都会精确到0.01%
  • StartText, EndText (string): 在进度条开头和结尾处的文本
  • PassedText, NotPassedText (string): 已经走过的文本和未走过的文本
  • FirstPassedText (string): 通过的文本中开头的文本
  • PercentColor (uint8): 百分比值的颜色,可选值参见改变颜色


修改颜色


如果想要修改进度条的颜色,可以使用此包提供的颜色相关的方法生成对应ANSI字符:

import "github.com/lianggaoqiang/progress"

// progress.ColorText(str, progress.Xxx) 和 progress.XxxText(str) 两种用法都是允许的
// Xxx的可选值:Black, Red, Green, Yellow, Blue, Purple, Cyan, White
func main() {
  p := progress.Start()
  b := progress.NewBar().Custom(progress.BarSetting{
    NotPassedText: progress.WhiteText("▇"),
    PassedText:    progress.ColorText("▇", progress.Green),
  })
  p.AddBar(b)

  for i := 0; i <= 100; i++ {
    b.Inc()
    time.Sleep(time.Millisecond * 50)
  }
}


LoadingBar 和 TextBar


上面的例子展示了如何使用默认的progress bar,此外,这个包还包含加载普通文本两种UI展现形式:LoadingBar和TextBar,它们的用法就像下面这样

import "github.com/lianggaoqiang/progress"

func main(){
  p := progress.Start()

  // TextBar with green text
  tb := progress.NewTextBar(progress.GreenText("start successfully!"))
  p.AddBar(tb)

  // LoadingBar with RedColor
  lb := progress.NewLoadingBar(300, "Loading", "Loading.", "Loading..", "Loading...")
  p.AddBar(lb.SetColor(progress.Red))

  <-time.After(time.Second * 3)
  lb.Hide()

  p.AddBar(progress.NewTextBar("Done, exiting!"))
  p.Stop()
}

这里在使用NewLoadingBar的时候我们第一个参数的含义是加载条每次渲染的时间间隔,单位是毫秒,接下来的参数是每次加载状态切换时展示的文本。这种方式在加载文本比较短的时候还好,但是如果文本较长,我们就要重复写好几遍长文本。所以这里我们可以通过连字符-来饮用前一个文本,所以上述例子中的写法与下面写法是等效的:

lb := progress.NewLoadingBar(300, "Loading", "-.", "-.", "-.")

此外需要注意的是TextBarSettingLoadingBarSetting的属性要比BarSetting少一些:

TextBarSetting的属性:

  • Inline, Hidden (bool): 作用与BarSetting相同

LoadingBarSetting的属性:

  • Hide, Inline (bool), StartText, EndText (string): 作用与SettingBar相同
  • FixedWidth (bool): 如果设置为true, LoadingBar的宽度将会固定(与加载时最长的文本一致)


组合不同风格的进度条


正如开头所说,我们可以将不同风格的进度条按照任意格式进行组合,下面就是组合多个进度条的例子:

import "github.com/lianggaoqiang/progress"

func main() {
  p := progress.Start()

  // create a custom bar
  b1 := progress.NewBar().Custom(progress.BarSetting{
    Total:           50,
    StartText:       "[",
    EndText:         "]",
    PassedText:      "-",
    FirstPassedText: ">",
    NotPassedText:   "=",
  })

  // create a custom inline bar
  b2 := progress.NewBar().Custom(progress.BarSetting{
    UseFloat:        true,
    Inline:          true,
    StartText:       "|",
    EndText:         "|",
    FirstPassedText: ">",
    PassedText:      "=",
    NotPassedText:   " ",
  })

  // create a custom bar with emoji character
  b3 := progress.NewBar().Custom(progress.BarSetting{
    LeftSpace:     10,
    Total:         10,
    StartText:     "|",
    EndText:       "|",
    PassedText:    "⚡",
    NotPassedText: "  ",
  })

  // add bars in progress
  p.AddBar(b2)
  p.AddBar(b3)
  p.AddBar(b1)

  for i := 0; i <= 100; i++ {
    b1.Inc()
    b2.Add(1.4)
    b3.Percent(float64(i))
    time.Sleep(time.Millisecond * 40)
  }
}


常见问题


  1. 在NewLoadingBar中怎么使用真实的连字符"-"? 使用连个连字符"--"替代即可。需要注意的是"---"会被解析为"-[前一个文本引用]",如果你的意图是用最后两个连字符代表真实的连字符,那么就可以使用括号来改变解析的优先级,例如:"-(--)"将会被解析为"[前一个文本引用]-"
  2. 怎么关闭自动停止功能? 为了防止忘记关闭progress资源,在所有进度条都加载至100%并且加载条处于关闭状态时,progress.Stop方法会被自动调用。要修改这一默认行为,可以使用progress.StartWithFlag来初始化progress(其中前三个flag继承自single-line-print:
  • HideCursor: 如果设置此标志位,打印过程中光标将被隐藏
  • DisableInput: 如果设置此标志位,打印过程中将会禁止输入
  • ResizeReactively: 如果设置此标志位,每次打印前都会重新获取终端窗口的大小,这可以在动态修改终端窗口时候保持稳定的输出
  • PercentOverflow: 如果设置此标志位,百分比值将可以突破100%,同样会导致AutoStop功能失效
  • AutoStop: 如果设置此标志位,将在所有进度条达到100%切加载条处于关闭状态时注销progress以释放资源,标志位的传入方式如下:
p := progress.StartWithFlag(progress.HideCursor | progress.DisAbleInput | progress.AutoStop)


相关链接


原文链接:https://blog.lgqhealer.cn/38.html

GitHub项目地址: https://github.com/lianggaoqiang/progress

single-line-print: https://github.com/lianggaoqiang/single-line-print

GoReference: https://pkg.go.dev/github.com/lianggaoqiang/progress

最后一次编辑于 2023 年 11 月 09 日
1 条评论

相关文章

专题推荐

热门文章

热门问答