Original post

Sometimes when handling a long running process, there would be need to track the progress so that people know something is still running instead of doubting something goes wrong. In this case, a progress bar which indicates the current status and progress would be desired.

This post will show an example on how to implement progress bar using GoLang. Let’s take a look at the final outcome first before jumping into the implementation detail.

The key in the implementation is actually just the r control flag for fmt.Printf(). r is actually the carriage return control which tells the cursor to move back to the beginning of the line and starts to print which basically simulates a replace and write function in this progress bar case.

Alright here comes the implementation. First let’s define the struct for the progress bar.

type Bar struct {
 percent int64 // progress percentage
 cur int64 // current progress
 total int64 // total value for progress
 rate string // the actual progress bar to be printed
 graph string // the fill value for progress bar
}

Here cur and total basically are used to control the progress bar status. percent can be calculated with these two values. Also graph can be any character like #, @, = etc to indicate what character to be used to fill the progress bar.

Next comes with the initialization. Two functions are provided: NewOption() and NewOptionWithGraph(). NewOption() is to initialize some of the values such as graph and start position. 

func (bar *Bar) NewOption(start, total int64) {
 bar.cur = start
 bar.total = total
 if bar.graph == "" {
   bar.graph = "█"
 }
 bar.percent = bar.getPercent()
 for i := 0; i < int(bar.percent); i += 2 {
   bar.rate += bar.graph // initial progress position
 }
}

This function takes two parameters: start and total. total is the total maximum task progress. start tells where the initial progress position is, it may not be 0. In cases like consecutive upload post interruption, the initial progress can be set to the last progress value before the interruption. 

One more note to take here is the step length is 2 instead of 1. This is because the progress value is from 0 to 100 while the max number of characters to display the progress bar is 50 in this example. If you use 100 characters to display the whole progress bar, the step length will be 1.

getPercent() is simple function to calculate the percentage based on cur and total.

func (bar *Bar) getPercent() int64 {
 return int64(float32(bar.cur) / float32(bar.total) * 100)
}

NewOptionWithGraph() is similar to NewOption() with only one additional graph parameter which allows you to set the fill sign.

func (bar *Bar) NewOptionWithGraph(start, total int64, graph string) {
 bar.graph = graph
 bar.NewOption(start, total)
}

Next is the most important function of the whole implementation. After having the progress bar struct created, there needs a way to display the dynamic changing progress on the console.

func (bar *Bar) Play(cur int64) {
 bar.cur = cur
 last := bar.percent
 bar.percent = bar.getPercent()
 if bar.percent != last && bar.percent%2 == 0 {
   bar.rate += bar.graph
 }
 fmt.Printf("r[%-50s]%3d%% %8d/%d", bar.rate, bar.percent, bar.cur, bar.total)
}

In above code snippet, the most important statement is the last one. It is to print the updated progress at the beginning of the line with the newly updated rate, percentage.

After displaying the progress bar in above function, there is no newline printed. Hence a final function Finish() is needed.

func (bar *Bar) Finish(){
 fmt.Println()
}

Below is the code to run the example.

func main(){
 var bar progressbar.Bar
 bar.NewOption(0, 100)
 for i:= 0; i<=100; i++{
   time.Sleep(100*time.Millisecond)
   bar.Play(int64(i))
 }
 bar.Finish()
}

Reference: https://segmentfault.com/a/1190000023375330