Go每日一库之164:uiprogress(终端进度条)

发布时间 2023-10-01 08:46:05作者: 阿瑞娜

今天给大家推荐的是在终端(terminal)下能够显示进度条的工具:uiprogress。先看下使用该包的效果图:

相信大家在linux或mac终端上都下载过东西,然后会出现下载的进度条。今天我们就给大家分析下实现原理并演示其效果。

安装

$ go get -v github.com/gosuri/uiprogress

实现原理分析

实现原理其实也很简单,本质上是通过计算机的换码符来实现的。这里使用的就是“清除当前行,将鼠标移动到行首,再重新输出进度”的原理。“清除当前行,将鼠标移动到行首”的换码符是:ESC[1AESC[2K。ESC[1A是代表将鼠标移动到第1行;ESC[2K是代表清除终端上的整行内容。二者组合就能实现上述效果。如下是一个简单的输出百分比的代码:

const ESC = 27

var clear = fmt.Sprintf("%c[%dA%c[2K", ESC, 1, ESC)

var Out = io.Writer(os.Stdout)

func main() {
    for i:= 0; i <=100; i++ {
        _, _ = fmt.Fprint(io.Writer(os.Stdout), clear)
        fmt.Fprintf(Out, "Downloading.. (%d/%d)GB\n", i, 100)
        time.Sleep(time.Millisecond * 500)
    }
}

在该包中,为了计算进度条还获取了当前终端的宽度(即一行能显示多列)的如下代码:


type windowSize struct {
    rows    uint16
    cols    uint16
}

var sz windowSize

func getTermSize() (int, int) {
    if runtime.GOOS == "openbsd" {
        out, err = os.OpenFile("/dev/tty", os.O_RDWR, 0)
        if err != nil {
            return 0, 0
        }
        
    } else {
        out, err = os.OpenFile("/dev/tty", os.O_WRONLY, 0)
        if err != nil {
            return 0, 0
        }
    }
    _, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
                              out.Fd(), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
    return int(sz.cols), int(sz.rows)
}

该包的特点

  • 同时渲染多个进度条:uiprogress能够同时并行处理多个进度条
  • 动态添加进度条:可以随时添加进度条
  • 前置函数和后置函数处理:通过前置和后置函数,可以在进度条的前、后添加已消耗的时间和当前的进度占比。
  • 自定义进度条样式函数:通过Bar的辅助函数还可以自定义进度条样式。

包的基本使用

通过uiprogress.Start()函数就能开启对进度条的监听,然后使用uiprogress.AddBar(total int)函数可以添加一个进度条。通过bar.Incr() 或 bar.Set(n int)可以设置进度条的进度值。如下:


uiprogress.Start()            // start rendering
bar := uiprogress.AddBar(100) // Add a new bar

// optionally, append and prepend completion and elapsed time
bar.AppendCompleted()
bar.PrependElapsed()

for bar.Incr() {
  time.Sleep(time.Millisecond * 20)
}

代码效果:

自定义进度条样式

除了使用bar.AppendCompleted()函数(在进度条最后增加百分比进度)和bar.PrependElapsed()函数(在进度条最前面添加耗时)可以装饰进度条外,还可以通过自定义的装饰函数给进度条。下面代码就是演示了一个自定义的部署进度的样式,如下:


uiprogress.Start()
var steps = []string{"downloading source", "installing deps", "compiling", "packaging", "seeding database", "deploying", "staring servers"}
bar := uiprogress.AddBar(len(steps))

// prepend the current step to the bar
bar.PrependFunc(func(b *uiprogress.Bar) string {
    return "app: " + steps[b.Current()-1]
})

for bar.Incr() {
    time.Sleep(time.Millisecond * 1000)
}

多个进度条

通过调用uiprogress.AddBar(n)函数多次来添加多个进度条。下面代码演示了并发更新多个进度条以及在程序运行中新添加一个进度条(符合随时添加进度条的特点)的示例。

waitTime := time.Millisecond * 100
uiprogress.Start()

// start the progress bars in go routines
var wg sync.WaitGroup

bar1 := uiprogress.AddBar(20).AppendCompleted().PrependElapsed()
wg.Add(1)
go func() {
    defer wg.Done()
    for bar1.Incr() {
        time.Sleep(waitTime)
    }
}()

bar2 := uiprogress.AddBar(40).AppendCompleted().PrependElapsed()
wg.Add(1)
go func() {
    defer wg.Done()
    for bar2.Incr() {
        time.Sleep(waitTime)
    }
}()

time.Sleep(time.Second)
bar3 := uiprogress.AddBar(20).PrependElapsed().AppendCompleted()
wg.Add(1)
go func() {
    defer wg.Done()
    for i := 1; i <= bar3.Total; i++ {
        bar3.Set(i)
        time.Sleep(waitTime)
    }
}()

// wait for all the go routines to finish
wg.Wait()

效果图如下:

更多项目详情请查看如下链接。

开源项目地址:https://github.com/gosuri/uiprogress

开源项目作者:Greg Osuri