Last active
April 4, 2018 00:20
-
-
Save marklap/7d04ddba0276b8959418df57eb9fdb13 to your computer and use it in GitHub Desktop.
Toy CLI progress bar (thread safe) - experiment in using bits as placeholders for the indicator so we can do bitwise operations to shift the spinner left and right instead of mucking with string magic.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//////////////////////////////////////////////////////////////////////////////// | |
// The MIT License (MIT) | |
// | |
// Copyright (c) 2018 Mark LaPerriere | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
//////////////////////////////////////////////////////////////////////////////// | |
// Package main is a thread-safe toy CLI progress bar experiment in using bits to store the state | |
// of the spinner so we can do bitwise operations to move it left and right instead of a glut of | |
// string operations. | |
// BONUS: it only depends on the `sync` package (other dependencies are for the example only) | |
package main | |
import ( | |
"fmt" | |
"sync" | |
"time" | |
) | |
const ( | |
indicatorLeft = 0x8000 // 1000 0000 0000 0000 | |
indicatorRight = 0x1 // 0000 0000 0000 0001 | |
directionRight = 0 | |
directionLeft = 1 | |
defaultWidth = 72 | |
indicatorRune = '*' | |
spaceRune = ' ' | |
) | |
// progressBar is a simple progress bar that uses bits and bitwise operators to track and move | |
// an indicator back and forth across a "progress bar". | |
type progressBar struct { | |
sync.RWMutex | |
barState uint16 | |
bar []byte | |
op int | |
stat string | |
width int | |
drawBuf []byte | |
} | |
// NewProgressBar creates a new progress bar | |
func NewProgressBar() *progressBar { | |
p := &progressBar{ | |
barState: indicatorLeft, | |
width: defaultWidth, | |
bar: make([]byte, 16), | |
drawBuf: make([]byte, defaultWidth), | |
} | |
p.drawBuf[0] = byte('[') | |
p.drawBuf[18] = byte(']') | |
p.drawBuf[19] = byte(' ') | |
return p | |
} | |
// Update updates the progress bar representation | |
func (p *progressBar) Update(stat string) { | |
// write lock | |
p.Lock() | |
defer p.Unlock() | |
// move the indicator in the appropriate direction | |
switch p.op { | |
case directionRight: | |
p.barState >>= 1 | |
case directionLeft: | |
p.barState <<= 1 | |
} | |
switch p.barState { | |
case indicatorLeft: | |
// indicator at far left, change direction to right | |
p.op = directionRight | |
case indicatorRight: | |
// indicator at far right, change direction to left | |
p.op = directionLeft | |
} | |
// convert bits to runes | |
c := uint16(p.barState) | |
for i := 15; i >= 0; i-- { | |
if c&1 == 1 { | |
p.bar[i] = indicatorRune | |
} else { | |
p.bar[i] = spaceRune | |
} | |
c >>= 1 | |
} | |
// update the stat message | |
p.stat = stat | |
} | |
// String returns a string representation of the progress bar | |
func (p *progressBar) String() string { | |
// read lock | |
p.RLock() | |
defer p.RUnlock() | |
// copy the progress bar into the buffer | |
copy(p.drawBuf[1:17], p.bar) | |
// copy the stat message into the buffer | |
copy(p.drawBuf[20:20+len(p.stat)], []byte(p.stat)) | |
// add the padding from right to left | |
for i := (20 + len(p.stat)); i < p.width; i++ { | |
p.drawBuf[i] = byte(' ') | |
} | |
// spit out the results | |
return string(p.drawBuf) | |
} | |
// main is just an example use | |
func main() { | |
// create a new progress bar | |
progress := NewProgressBar() | |
// update a couple times to see it in action | |
for i := 100; i >= 0; i-- { | |
var stat string | |
if i == 0 { | |
stat = fmt.Sprint("WE'RE DONE!") | |
} else { | |
stat = fmt.Sprintf("%d more to go", i) | |
} | |
progress.Update(stat) | |
// make sure to reset the line first with \r | |
fmt.Printf("\r%s", progress.String()) | |
// artificial pause so we can actually see it | |
time.Sleep(time.Millisecond * 50) | |
} | |
fmt.Println("\ndone") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment