progressbar.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098
  1. package progressbar
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "math"
  7. "os"
  8. "regexp"
  9. "strings"
  10. "sync"
  11. "time"
  12. "github.com/mattn/go-runewidth"
  13. "github.com/mitchellh/colorstring"
  14. "golang.org/x/term"
  15. )
  16. // ProgressBar is a thread-safe, simple
  17. // progress bar
  18. type ProgressBar struct {
  19. state state
  20. config config
  21. lock sync.Mutex
  22. }
  23. // State is the basic properties of the bar
  24. type State struct {
  25. CurrentPercent float64
  26. CurrentBytes float64
  27. SecondsSince float64
  28. SecondsLeft float64
  29. KBsPerSecond float64
  30. }
  31. type state struct {
  32. currentNum int64
  33. currentPercent int
  34. lastPercent int
  35. currentSaucerSize int
  36. isAltSaucerHead bool
  37. lastShown time.Time
  38. startTime time.Time
  39. counterTime time.Time
  40. counterNumSinceLast int64
  41. counterLastTenRates []float64
  42. maxLineWidth int
  43. currentBytes float64
  44. finished bool
  45. exit bool // Progress bar exit halfway
  46. rendered string
  47. }
  48. type config struct {
  49. max int64 // max number of the counter
  50. maxHumanized string
  51. maxHumanizedSuffix string
  52. width int
  53. writer io.Writer
  54. theme Theme
  55. renderWithBlankState bool
  56. description string
  57. iterationString string
  58. ignoreLength bool // ignoreLength if max bytes not known
  59. // whether the output is expected to contain color codes
  60. colorCodes bool
  61. // show rate of change in kB/sec or MB/sec
  62. showBytes bool
  63. // show the iterations per second
  64. showIterationsPerSecond bool
  65. showIterationsCount bool
  66. // whether the progress bar should show elapsed time.
  67. // always enabled if predictTime is true.
  68. elapsedTime bool
  69. showElapsedTimeOnFinish bool
  70. // whether the progress bar should attempt to predict the finishing
  71. // time of the progress based on the start time and the average
  72. // number of seconds between increments.
  73. predictTime bool
  74. // minimum time to wait in between updates
  75. throttleDuration time.Duration
  76. // clear bar once finished
  77. clearOnFinish bool
  78. // spinnerType should be a number between 0-75
  79. spinnerType int
  80. // spinnerTypeOptionUsed remembers if the spinnerType was changed manually
  81. spinnerTypeOptionUsed bool
  82. // spinner represents the spinner as a slice of string
  83. spinner []string
  84. // fullWidth specifies whether to measure and set the bar to a specific width
  85. fullWidth bool
  86. // invisible doesn't render the bar at all, useful for debugging
  87. invisible bool
  88. onCompletion func()
  89. // whether the render function should make use of ANSI codes to reduce console I/O
  90. useANSICodes bool
  91. // showDescriptionAtLineEnd specifies whether description should be written at line end instead of line start
  92. showDescriptionAtLineEnd bool
  93. }
  94. // Theme defines the elements of the bar
  95. type Theme struct {
  96. Saucer string
  97. AltSaucerHead string
  98. SaucerHead string
  99. SaucerPadding string
  100. BarStart string
  101. BarEnd string
  102. }
  103. // Option is the type all options need to adhere to
  104. type Option func(p *ProgressBar)
  105. // OptionSetWidth sets the width of the bar
  106. func OptionSetWidth(s int) Option {
  107. return func(p *ProgressBar) {
  108. p.config.width = s
  109. }
  110. }
  111. // OptionSpinnerType sets the type of spinner used for indeterminate bars
  112. func OptionSpinnerType(spinnerType int) Option {
  113. return func(p *ProgressBar) {
  114. p.config.spinnerTypeOptionUsed = true
  115. p.config.spinnerType = spinnerType
  116. }
  117. }
  118. // OptionSpinnerCustom sets the spinner used for indeterminate bars to the passed
  119. // slice of string
  120. func OptionSpinnerCustom(spinner []string) Option {
  121. return func(p *ProgressBar) {
  122. p.config.spinner = spinner
  123. }
  124. }
  125. // OptionSetTheme sets the elements the bar is constructed of
  126. func OptionSetTheme(t Theme) Option {
  127. return func(p *ProgressBar) {
  128. p.config.theme = t
  129. }
  130. }
  131. // OptionSetVisibility sets the visibility
  132. func OptionSetVisibility(visibility bool) Option {
  133. return func(p *ProgressBar) {
  134. p.config.invisible = !visibility
  135. }
  136. }
  137. // OptionFullWidth sets the bar to be full width
  138. func OptionFullWidth() Option {
  139. return func(p *ProgressBar) {
  140. p.config.fullWidth = true
  141. }
  142. }
  143. // OptionSetWriter sets the output writer (defaults to os.StdOut)
  144. func OptionSetWriter(w io.Writer) Option {
  145. return func(p *ProgressBar) {
  146. p.config.writer = w
  147. }
  148. }
  149. // OptionSetRenderBlankState sets whether or not to render a 0% bar on construction
  150. func OptionSetRenderBlankState(r bool) Option {
  151. return func(p *ProgressBar) {
  152. p.config.renderWithBlankState = r
  153. }
  154. }
  155. // OptionSetDescription sets the description of the bar to render in front of it
  156. func OptionSetDescription(description string) Option {
  157. return func(p *ProgressBar) {
  158. p.config.description = description
  159. }
  160. }
  161. // OptionEnableColorCodes enables or disables support for color codes
  162. // using mitchellh/colorstring
  163. func OptionEnableColorCodes(colorCodes bool) Option {
  164. return func(p *ProgressBar) {
  165. p.config.colorCodes = colorCodes
  166. }
  167. }
  168. // OptionSetElapsedTime will enable elapsed time. Always enabled if OptionSetPredictTime is true.
  169. func OptionSetElapsedTime(elapsedTime bool) Option {
  170. return func(p *ProgressBar) {
  171. p.config.elapsedTime = elapsedTime
  172. }
  173. }
  174. // OptionSetPredictTime will also attempt to predict the time remaining.
  175. func OptionSetPredictTime(predictTime bool) Option {
  176. return func(p *ProgressBar) {
  177. p.config.predictTime = predictTime
  178. }
  179. }
  180. // OptionShowCount will also print current count out of total
  181. func OptionShowCount() Option {
  182. return func(p *ProgressBar) {
  183. p.config.showIterationsCount = true
  184. }
  185. }
  186. // OptionShowIts will also print the iterations/second
  187. func OptionShowIts() Option {
  188. return func(p *ProgressBar) {
  189. p.config.showIterationsPerSecond = true
  190. }
  191. }
  192. // OptionShowElapsedOnFinish will keep the display of elapsed time on finish
  193. func OptionShowElapsedTimeOnFinish() Option {
  194. return func(p *ProgressBar) {
  195. p.config.showElapsedTimeOnFinish = true
  196. }
  197. }
  198. // OptionSetItsString sets what's displayed for iterations a second. The default is "it" which would display: "it/s"
  199. func OptionSetItsString(iterationString string) Option {
  200. return func(p *ProgressBar) {
  201. p.config.iterationString = iterationString
  202. }
  203. }
  204. // OptionThrottle will wait the specified duration before updating again. The default
  205. // duration is 0 seconds.
  206. func OptionThrottle(duration time.Duration) Option {
  207. return func(p *ProgressBar) {
  208. p.config.throttleDuration = duration
  209. }
  210. }
  211. // OptionClearOnFinish will clear the bar once its finished
  212. func OptionClearOnFinish() Option {
  213. return func(p *ProgressBar) {
  214. p.config.clearOnFinish = true
  215. }
  216. }
  217. // OptionOnCompletion will invoke cmpl function once its finished
  218. func OptionOnCompletion(cmpl func()) Option {
  219. return func(p *ProgressBar) {
  220. p.config.onCompletion = cmpl
  221. }
  222. }
  223. // OptionShowBytes will update the progress bar
  224. // configuration settings to display/hide kBytes/Sec
  225. func OptionShowBytes(val bool) Option {
  226. return func(p *ProgressBar) {
  227. p.config.showBytes = val
  228. }
  229. }
  230. // OptionUseANSICodes will use more optimized terminal i/o.
  231. //
  232. // Only useful in environments with support for ANSI escape sequences.
  233. func OptionUseANSICodes(val bool) Option {
  234. return func(p *ProgressBar) {
  235. p.config.useANSICodes = val
  236. }
  237. }
  238. // OptionShowDescriptionAtLineEnd defines whether description should be written at line end instead of line start
  239. func OptionShowDescriptionAtLineEnd() Option {
  240. return func(p *ProgressBar) {
  241. p.config.showDescriptionAtLineEnd = true
  242. }
  243. }
  244. var defaultTheme = Theme{Saucer: "█", SaucerPadding: " ", BarStart: "|", BarEnd: "|"}
  245. // NewOptions constructs a new instance of ProgressBar, with any options you specify
  246. func NewOptions(max int, options ...Option) *ProgressBar {
  247. return NewOptions64(int64(max), options...)
  248. }
  249. // NewOptions64 constructs a new instance of ProgressBar, with any options you specify
  250. func NewOptions64(max int64, options ...Option) *ProgressBar {
  251. b := ProgressBar{
  252. state: getBasicState(),
  253. config: config{
  254. writer: os.Stdout,
  255. theme: defaultTheme,
  256. iterationString: "it",
  257. width: 40,
  258. max: max,
  259. throttleDuration: 0 * time.Nanosecond,
  260. elapsedTime: true,
  261. predictTime: true,
  262. spinnerType: 9,
  263. invisible: false,
  264. },
  265. }
  266. for _, o := range options {
  267. o(&b)
  268. }
  269. if b.config.spinnerType < 0 || b.config.spinnerType > 75 {
  270. panic("invalid spinner type, must be between 0 and 75")
  271. }
  272. // ignoreLength if max bytes not known
  273. if b.config.max == -1 {
  274. b.config.ignoreLength = true
  275. b.config.max = int64(b.config.width)
  276. b.config.predictTime = false
  277. }
  278. b.config.maxHumanized, b.config.maxHumanizedSuffix = humanizeBytes(float64(b.config.max))
  279. if b.config.renderWithBlankState {
  280. b.RenderBlank()
  281. }
  282. return &b
  283. }
  284. func getBasicState() state {
  285. now := time.Now()
  286. return state{
  287. startTime: now,
  288. lastShown: now,
  289. counterTime: now,
  290. }
  291. }
  292. // New returns a new ProgressBar
  293. // with the specified maximum
  294. func New(max int) *ProgressBar {
  295. return NewOptions(max)
  296. }
  297. // DefaultBytes provides a progressbar to measure byte
  298. // throughput with recommended defaults.
  299. // Set maxBytes to -1 to use as a spinner.
  300. func DefaultBytes(maxBytes int64, description ...string) *ProgressBar {
  301. desc := ""
  302. if len(description) > 0 {
  303. desc = description[0]
  304. }
  305. return NewOptions64(
  306. maxBytes,
  307. OptionSetDescription(desc),
  308. OptionSetWriter(os.Stderr),
  309. OptionShowBytes(true),
  310. OptionSetWidth(10),
  311. OptionThrottle(65*time.Millisecond),
  312. OptionShowCount(),
  313. OptionOnCompletion(func() {
  314. fmt.Fprint(os.Stderr, "\n")
  315. }),
  316. OptionSpinnerType(14),
  317. OptionFullWidth(),
  318. OptionSetRenderBlankState(true),
  319. )
  320. }
  321. // DefaultBytesSilent is the same as DefaultBytes, but does not output anywhere.
  322. // String() can be used to get the output instead.
  323. func DefaultBytesSilent(maxBytes int64, description ...string) *ProgressBar {
  324. // Mostly the same bar as DefaultBytes
  325. desc := ""
  326. if len(description) > 0 {
  327. desc = description[0]
  328. }
  329. return NewOptions64(
  330. maxBytes,
  331. OptionSetDescription(desc),
  332. OptionSetWriter(io.Discard),
  333. OptionShowBytes(true),
  334. OptionSetWidth(10),
  335. OptionThrottle(65*time.Millisecond),
  336. OptionShowCount(),
  337. OptionSpinnerType(14),
  338. OptionFullWidth(),
  339. )
  340. }
  341. // Default provides a progressbar with recommended defaults.
  342. // Set max to -1 to use as a spinner.
  343. func Default(max int64, description ...string) *ProgressBar {
  344. desc := ""
  345. if len(description) > 0 {
  346. desc = description[0]
  347. }
  348. return NewOptions64(
  349. max,
  350. OptionSetDescription(desc),
  351. OptionSetWriter(os.Stderr),
  352. OptionSetWidth(10),
  353. OptionThrottle(65*time.Millisecond),
  354. OptionShowCount(),
  355. OptionShowIts(),
  356. OptionOnCompletion(func() {
  357. fmt.Fprint(os.Stderr, "\n")
  358. }),
  359. OptionSpinnerType(14),
  360. OptionFullWidth(),
  361. OptionSetRenderBlankState(true),
  362. )
  363. }
  364. // DefaultSilent is the same as Default, but does not output anywhere.
  365. // String() can be used to get the output instead.
  366. func DefaultSilent(max int64, description ...string) *ProgressBar {
  367. // Mostly the same bar as Default
  368. desc := ""
  369. if len(description) > 0 {
  370. desc = description[0]
  371. }
  372. return NewOptions64(
  373. max,
  374. OptionSetDescription(desc),
  375. OptionSetWriter(io.Discard),
  376. OptionSetWidth(10),
  377. OptionThrottle(65*time.Millisecond),
  378. OptionShowCount(),
  379. OptionShowIts(),
  380. OptionSpinnerType(14),
  381. OptionFullWidth(),
  382. )
  383. }
  384. // String returns the current rendered version of the progress bar.
  385. // It will never return an empty string while the progress bar is running.
  386. func (p *ProgressBar) String() string {
  387. return p.state.rendered
  388. }
  389. // RenderBlank renders the current bar state, you can use this to render a 0% state
  390. func (p *ProgressBar) RenderBlank() error {
  391. if p.config.invisible {
  392. return nil
  393. }
  394. if p.state.currentNum == 0 {
  395. p.state.lastShown = time.Time{}
  396. }
  397. return p.render()
  398. }
  399. // Reset will reset the clock that is used
  400. // to calculate current time and the time left.
  401. func (p *ProgressBar) Reset() {
  402. p.lock.Lock()
  403. defer p.lock.Unlock()
  404. p.state = getBasicState()
  405. }
  406. // Finish will fill the bar to full
  407. func (p *ProgressBar) Finish() error {
  408. p.lock.Lock()
  409. p.state.currentNum = p.config.max
  410. p.lock.Unlock()
  411. return p.Add(0)
  412. }
  413. // Exit will exit the bar to keep current state
  414. func (p *ProgressBar) Exit() error {
  415. p.lock.Lock()
  416. defer p.lock.Unlock()
  417. p.state.exit = true
  418. if p.config.onCompletion != nil {
  419. p.config.onCompletion()
  420. }
  421. return nil
  422. }
  423. // Add will add the specified amount to the progressbar
  424. func (p *ProgressBar) Add(num int) error {
  425. return p.Add64(int64(num))
  426. }
  427. // Set will set the bar to a current number
  428. func (p *ProgressBar) Set(num int) error {
  429. return p.Set64(int64(num))
  430. }
  431. // Set64 will set the bar to a current number
  432. func (p *ProgressBar) Set64(num int64) error {
  433. p.lock.Lock()
  434. toAdd := num - int64(p.state.currentBytes)
  435. p.lock.Unlock()
  436. return p.Add64(toAdd)
  437. }
  438. // Add64 will add the specified amount to the progressbar
  439. func (p *ProgressBar) Add64(num int64) error {
  440. if p.config.invisible {
  441. return nil
  442. }
  443. p.lock.Lock()
  444. defer p.lock.Unlock()
  445. if p.state.exit {
  446. return nil
  447. }
  448. // error out since OptionSpinnerCustom will always override a manually set spinnerType
  449. if p.config.spinnerTypeOptionUsed && len(p.config.spinner) > 0 {
  450. return errors.New("OptionSpinnerType and OptionSpinnerCustom cannot be used together")
  451. }
  452. if p.config.max == 0 {
  453. return errors.New("max must be greater than 0")
  454. }
  455. if p.state.currentNum < p.config.max {
  456. if p.config.ignoreLength {
  457. p.state.currentNum = (p.state.currentNum + num) % p.config.max
  458. } else {
  459. p.state.currentNum += num
  460. }
  461. }
  462. p.state.currentBytes += float64(num)
  463. // reset the countdown timer every second to take rolling average
  464. p.state.counterNumSinceLast += num
  465. if time.Since(p.state.counterTime).Seconds() > 0.5 {
  466. p.state.counterLastTenRates = append(p.state.counterLastTenRates, float64(p.state.counterNumSinceLast)/time.Since(p.state.counterTime).Seconds())
  467. if len(p.state.counterLastTenRates) > 10 {
  468. p.state.counterLastTenRates = p.state.counterLastTenRates[1:]
  469. }
  470. p.state.counterTime = time.Now()
  471. p.state.counterNumSinceLast = 0
  472. }
  473. percent := float64(p.state.currentNum) / float64(p.config.max)
  474. p.state.currentSaucerSize = int(percent * float64(p.config.width))
  475. p.state.currentPercent = int(percent * 100)
  476. updateBar := p.state.currentPercent != p.state.lastPercent && p.state.currentPercent > 0
  477. p.state.lastPercent = p.state.currentPercent
  478. if p.state.currentNum > p.config.max {
  479. return errors.New("current number exceeds max")
  480. }
  481. // always update if show bytes/second or its/second
  482. if updateBar || p.config.showIterationsPerSecond || p.config.showIterationsCount {
  483. return p.render()
  484. }
  485. return nil
  486. }
  487. // Clear erases the progress bar from the current line
  488. func (p *ProgressBar) Clear() error {
  489. return clearProgressBar(p.config, p.state)
  490. }
  491. // Describe will change the description shown before the progress, which
  492. // can be changed on the fly (as for a slow running process).
  493. func (p *ProgressBar) Describe(description string) {
  494. p.lock.Lock()
  495. defer p.lock.Unlock()
  496. p.config.description = description
  497. if p.config.invisible {
  498. return
  499. }
  500. p.render()
  501. }
  502. // New64 returns a new ProgressBar
  503. // with the specified maximum
  504. func New64(max int64) *ProgressBar {
  505. return NewOptions64(max)
  506. }
  507. // GetMax returns the max of a bar
  508. func (p *ProgressBar) GetMax() int {
  509. return int(p.config.max)
  510. }
  511. // GetMax64 returns the current max
  512. func (p *ProgressBar) GetMax64() int64 {
  513. return p.config.max
  514. }
  515. // ChangeMax takes in a int
  516. // and changes the max value
  517. // of the progress bar
  518. func (p *ProgressBar) ChangeMax(newMax int) {
  519. p.ChangeMax64(int64(newMax))
  520. }
  521. // ChangeMax64 is basically
  522. // the same as ChangeMax,
  523. // but takes in a int64
  524. // to avoid casting
  525. func (p *ProgressBar) ChangeMax64(newMax int64) {
  526. p.config.max = newMax
  527. if p.config.showBytes {
  528. p.config.maxHumanized, p.config.maxHumanizedSuffix = humanizeBytes(float64(p.config.max))
  529. }
  530. p.Add(0) // re-render
  531. }
  532. // IsFinished returns true if progress bar is completed
  533. func (p *ProgressBar) IsFinished() bool {
  534. return p.state.finished
  535. }
  536. // render renders the progress bar, updating the maximum
  537. // rendered line width. this function is not thread-safe,
  538. // so it must be called with an acquired lock.
  539. func (p *ProgressBar) render() error {
  540. // make sure that the rendering is not happening too quickly
  541. // but always show if the currentNum reaches the max
  542. if time.Since(p.state.lastShown).Nanoseconds() < p.config.throttleDuration.Nanoseconds() &&
  543. p.state.currentNum < p.config.max {
  544. return nil
  545. }
  546. if !p.config.useANSICodes {
  547. // first, clear the existing progress bar
  548. err := clearProgressBar(p.config, p.state)
  549. if err != nil {
  550. return err
  551. }
  552. }
  553. // check if the progress bar is finished
  554. if !p.state.finished && p.state.currentNum >= p.config.max {
  555. p.state.finished = true
  556. if !p.config.clearOnFinish {
  557. renderProgressBar(p.config, &p.state)
  558. }
  559. if p.config.onCompletion != nil {
  560. p.config.onCompletion()
  561. }
  562. }
  563. if p.state.finished {
  564. // when using ANSI codes we don't pre-clean the current line
  565. if p.config.useANSICodes && p.config.clearOnFinish {
  566. err := clearProgressBar(p.config, p.state)
  567. if err != nil {
  568. return err
  569. }
  570. }
  571. return nil
  572. }
  573. // then, re-render the current progress bar
  574. w, err := renderProgressBar(p.config, &p.state)
  575. if err != nil {
  576. return err
  577. }
  578. if w > p.state.maxLineWidth {
  579. p.state.maxLineWidth = w
  580. }
  581. p.state.lastShown = time.Now()
  582. return nil
  583. }
  584. // State returns the current state
  585. func (p *ProgressBar) State() State {
  586. p.lock.Lock()
  587. defer p.lock.Unlock()
  588. s := State{}
  589. s.CurrentPercent = float64(p.state.currentNum) / float64(p.config.max)
  590. s.CurrentBytes = p.state.currentBytes
  591. s.SecondsSince = time.Since(p.state.startTime).Seconds()
  592. if p.state.currentNum > 0 {
  593. s.SecondsLeft = s.SecondsSince / float64(p.state.currentNum) * (float64(p.config.max) - float64(p.state.currentNum))
  594. }
  595. s.KBsPerSecond = float64(p.state.currentBytes) / 1000.0 / s.SecondsSince
  596. return s
  597. }
  598. // regex matching ansi escape codes
  599. var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
  600. func getStringWidth(c config, str string, colorize bool) int {
  601. if c.colorCodes {
  602. // convert any color codes in the progress bar into the respective ANSI codes
  603. str = colorstring.Color(str)
  604. }
  605. // the width of the string, if printed to the console
  606. // does not include the carriage return character
  607. cleanString := strings.Replace(str, "\r", "", -1)
  608. if c.colorCodes {
  609. // the ANSI codes for the colors do not take up space in the console output,
  610. // so they do not count towards the output string width
  611. cleanString = ansiRegex.ReplaceAllString(cleanString, "")
  612. }
  613. // get the amount of runes in the string instead of the
  614. // character count of the string, as some runes span multiple characters.
  615. // see https://stackoverflow.com/a/12668840/2733724
  616. stringWidth := runewidth.StringWidth(cleanString)
  617. return stringWidth
  618. }
  619. func renderProgressBar(c config, s *state) (int, error) {
  620. var sb strings.Builder
  621. averageRate := average(s.counterLastTenRates)
  622. if len(s.counterLastTenRates) == 0 || s.finished {
  623. // if no average samples, or if finished,
  624. // then average rate should be the total rate
  625. if t := time.Since(s.startTime).Seconds(); t > 0 {
  626. averageRate = s.currentBytes / t
  627. } else {
  628. averageRate = 0
  629. }
  630. }
  631. // show iteration count in "current/total" iterations format
  632. if c.showIterationsCount {
  633. if sb.Len() == 0 {
  634. sb.WriteString("(")
  635. } else {
  636. sb.WriteString(", ")
  637. }
  638. if !c.ignoreLength {
  639. if c.showBytes {
  640. currentHumanize, currentSuffix := humanizeBytes(s.currentBytes)
  641. if currentSuffix == c.maxHumanizedSuffix {
  642. sb.WriteString(fmt.Sprintf("%s/%s%s",
  643. currentHumanize, c.maxHumanized, c.maxHumanizedSuffix))
  644. } else {
  645. sb.WriteString(fmt.Sprintf("%s%s/%s%s",
  646. currentHumanize, currentSuffix, c.maxHumanized, c.maxHumanizedSuffix))
  647. }
  648. } else {
  649. sb.WriteString(fmt.Sprintf("%.0f/%d", s.currentBytes, c.max))
  650. }
  651. } else {
  652. if c.showBytes {
  653. currentHumanize, currentSuffix := humanizeBytes(s.currentBytes)
  654. sb.WriteString(fmt.Sprintf("%s%s", currentHumanize, currentSuffix))
  655. } else {
  656. sb.WriteString(fmt.Sprintf("%.0f/%s", s.currentBytes, "-"))
  657. }
  658. }
  659. }
  660. // show rolling average rate
  661. if c.showBytes && averageRate > 0 && !math.IsInf(averageRate, 1) {
  662. if sb.Len() == 0 {
  663. sb.WriteString("(")
  664. } else {
  665. sb.WriteString(", ")
  666. }
  667. currentHumanize, currentSuffix := humanizeBytes(averageRate)
  668. sb.WriteString(fmt.Sprintf("%s%s/s", currentHumanize, currentSuffix))
  669. }
  670. // show iterations rate
  671. if c.showIterationsPerSecond {
  672. if sb.Len() == 0 {
  673. sb.WriteString("(")
  674. } else {
  675. sb.WriteString(", ")
  676. }
  677. if averageRate > 1 {
  678. sb.WriteString(fmt.Sprintf("%0.0f %s/s", averageRate, c.iterationString))
  679. } else if averageRate*60 > 1 {
  680. sb.WriteString(fmt.Sprintf("%0.0f %s/min", 60*averageRate, c.iterationString))
  681. } else {
  682. sb.WriteString(fmt.Sprintf("%0.0f %s/hr", 3600*averageRate, c.iterationString))
  683. }
  684. }
  685. if sb.Len() > 0 {
  686. sb.WriteString(")")
  687. }
  688. leftBrac, rightBrac, saucer, saucerHead := "", "", "", ""
  689. // show time prediction in "current/total" seconds format
  690. switch {
  691. case c.predictTime:
  692. rightBracNum := (time.Duration((1/averageRate)*(float64(c.max)-float64(s.currentNum))) * time.Second)
  693. if rightBracNum.Seconds() < 0 {
  694. rightBracNum = 0 * time.Second
  695. }
  696. rightBrac = rightBracNum.String()
  697. fallthrough
  698. case c.elapsedTime:
  699. leftBrac = (time.Duration(time.Since(s.startTime).Seconds()) * time.Second).String()
  700. }
  701. if c.fullWidth && !c.ignoreLength {
  702. width, err := termWidth()
  703. if err != nil {
  704. width = 80
  705. }
  706. amend := 1 // an extra space at eol
  707. switch {
  708. case leftBrac != "" && rightBrac != "":
  709. amend = 4 // space, square brackets and colon
  710. case leftBrac != "" && rightBrac == "":
  711. amend = 4 // space and square brackets and another space
  712. case leftBrac == "" && rightBrac != "":
  713. amend = 3 // space and square brackets
  714. }
  715. if c.showDescriptionAtLineEnd {
  716. amend += 1 // another space
  717. }
  718. c.width = width - getStringWidth(c, c.description, true) - 10 - amend - sb.Len() - len(leftBrac) - len(rightBrac)
  719. s.currentSaucerSize = int(float64(s.currentPercent) / 100.0 * float64(c.width))
  720. }
  721. if s.currentSaucerSize > 0 {
  722. if c.ignoreLength {
  723. saucer = strings.Repeat(c.theme.SaucerPadding, s.currentSaucerSize-1)
  724. } else {
  725. saucer = strings.Repeat(c.theme.Saucer, s.currentSaucerSize-1)
  726. }
  727. // Check if an alternate saucer head is set for animation
  728. if c.theme.AltSaucerHead != "" && s.isAltSaucerHead {
  729. saucerHead = c.theme.AltSaucerHead
  730. s.isAltSaucerHead = false
  731. } else if c.theme.SaucerHead == "" || s.currentSaucerSize == c.width {
  732. // use the saucer for the saucer head if it hasn't been set
  733. // to preserve backwards compatibility
  734. saucerHead = c.theme.Saucer
  735. } else {
  736. saucerHead = c.theme.SaucerHead
  737. s.isAltSaucerHead = true
  738. }
  739. }
  740. /*
  741. Progress Bar format
  742. Description % |------ | (kb/s) (iteration count) (iteration rate) (predict time)
  743. or if showDescriptionAtLineEnd is enabled
  744. % |------ | (kb/s) (iteration count) (iteration rate) (predict time) Description
  745. */
  746. repeatAmount := c.width - s.currentSaucerSize
  747. if repeatAmount < 0 {
  748. repeatAmount = 0
  749. }
  750. str := ""
  751. if c.ignoreLength {
  752. selectedSpinner := spinners[c.spinnerType]
  753. if len(c.spinner) > 0 {
  754. selectedSpinner = c.spinner
  755. }
  756. spinner := selectedSpinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(selectedSpinner)))))]
  757. if c.elapsedTime {
  758. if c.showDescriptionAtLineEnd {
  759. str = fmt.Sprintf("\r%s %s [%s] %s ",
  760. spinner,
  761. sb.String(),
  762. leftBrac,
  763. c.description)
  764. } else {
  765. str = fmt.Sprintf("\r%s %s %s [%s] ",
  766. spinner,
  767. c.description,
  768. sb.String(),
  769. leftBrac)
  770. }
  771. } else {
  772. if c.showDescriptionAtLineEnd {
  773. str = fmt.Sprintf("\r%s %s %s ",
  774. spinner,
  775. sb.String(),
  776. c.description)
  777. } else {
  778. str = fmt.Sprintf("\r%s %s %s ",
  779. spinner,
  780. c.description,
  781. sb.String())
  782. }
  783. }
  784. } else if rightBrac == "" {
  785. str = fmt.Sprintf("%4d%% %s%s%s%s%s %s",
  786. s.currentPercent,
  787. c.theme.BarStart,
  788. saucer,
  789. saucerHead,
  790. strings.Repeat(c.theme.SaucerPadding, repeatAmount),
  791. c.theme.BarEnd,
  792. sb.String())
  793. if s.currentPercent == 100 && c.showElapsedTimeOnFinish {
  794. str = fmt.Sprintf("%s [%s]", str, leftBrac)
  795. }
  796. if c.showDescriptionAtLineEnd {
  797. str = fmt.Sprintf("\r%s %s ", str, c.description)
  798. } else {
  799. str = fmt.Sprintf("\r%s%s ", c.description, str)
  800. }
  801. } else {
  802. if s.currentPercent == 100 {
  803. str = fmt.Sprintf("%4d%% %s%s%s%s%s %s",
  804. s.currentPercent,
  805. c.theme.BarStart,
  806. saucer,
  807. saucerHead,
  808. strings.Repeat(c.theme.SaucerPadding, repeatAmount),
  809. c.theme.BarEnd,
  810. sb.String())
  811. if c.showElapsedTimeOnFinish {
  812. str = fmt.Sprintf("%s [%s]", str, leftBrac)
  813. }
  814. if c.showDescriptionAtLineEnd {
  815. str = fmt.Sprintf("\r%s %s", str, c.description)
  816. } else {
  817. str = fmt.Sprintf("\r%s%s", c.description, str)
  818. }
  819. } else {
  820. str = fmt.Sprintf("%4d%% %s%s%s%s%s %s [%s:%s]",
  821. s.currentPercent,
  822. c.theme.BarStart,
  823. saucer,
  824. saucerHead,
  825. strings.Repeat(c.theme.SaucerPadding, repeatAmount),
  826. c.theme.BarEnd,
  827. sb.String(),
  828. leftBrac,
  829. rightBrac)
  830. if c.showDescriptionAtLineEnd {
  831. str = fmt.Sprintf("\r%s %s", str, c.description)
  832. } else {
  833. str = fmt.Sprintf("\r%s%s", c.description, str)
  834. }
  835. }
  836. }
  837. if c.colorCodes {
  838. // convert any color codes in the progress bar into the respective ANSI codes
  839. str = colorstring.Color(str)
  840. }
  841. s.rendered = str
  842. return getStringWidth(c, str, false), writeString(c, str)
  843. }
  844. func clearProgressBar(c config, s state) error {
  845. if s.maxLineWidth == 0 {
  846. return nil
  847. }
  848. if c.useANSICodes {
  849. // write the "clear current line" ANSI escape sequence
  850. return writeString(c, "\033[2K\r")
  851. }
  852. // fill the empty content
  853. // to overwrite the progress bar and jump
  854. // back to the beginning of the line
  855. str := fmt.Sprintf("\r%s\r", strings.Repeat(" ", s.maxLineWidth))
  856. return writeString(c, str)
  857. // the following does not show correctly if the previous line is longer than subsequent line
  858. // return writeString(c, "\r")
  859. }
  860. func writeString(c config, str string) error {
  861. if _, err := io.WriteString(c.writer, str); err != nil {
  862. return err
  863. }
  864. if f, ok := c.writer.(*os.File); ok {
  865. // ignore any errors in Sync(), as stdout
  866. // can't be synced on some operating systems
  867. // like Debian 9 (Stretch)
  868. f.Sync()
  869. }
  870. return nil
  871. }
  872. // Reader is the progressbar io.Reader struct
  873. type Reader struct {
  874. io.Reader
  875. bar *ProgressBar
  876. }
  877. // NewReader return a new Reader with a given progress bar.
  878. func NewReader(r io.Reader, bar *ProgressBar) Reader {
  879. return Reader{
  880. Reader: r,
  881. bar: bar,
  882. }
  883. }
  884. // Read will read the data and add the number of bytes to the progressbar
  885. func (r *Reader) Read(p []byte) (n int, err error) {
  886. n, err = r.Reader.Read(p)
  887. r.bar.Add(n)
  888. return
  889. }
  890. // Close the reader when it implements io.Closer
  891. func (r *Reader) Close() (err error) {
  892. if closer, ok := r.Reader.(io.Closer); ok {
  893. return closer.Close()
  894. }
  895. r.bar.Finish()
  896. return
  897. }
  898. // Write implement io.Writer
  899. func (p *ProgressBar) Write(b []byte) (n int, err error) {
  900. n = len(b)
  901. p.Add(n)
  902. return
  903. }
  904. // Read implement io.Reader
  905. func (p *ProgressBar) Read(b []byte) (n int, err error) {
  906. n = len(b)
  907. p.Add(n)
  908. return
  909. }
  910. func (p *ProgressBar) Close() (err error) {
  911. p.Finish()
  912. return
  913. }
  914. func average(xs []float64) float64 {
  915. total := 0.0
  916. for _, v := range xs {
  917. total += v
  918. }
  919. return total / float64(len(xs))
  920. }
  921. func humanizeBytes(s float64) (string, string) {
  922. sizes := []string{" B", " kB", " MB", " GB", " TB", " PB", " EB"}
  923. base := 1000.0
  924. if s < 10 {
  925. return fmt.Sprintf("%2.0f", s), sizes[0]
  926. }
  927. e := math.Floor(logn(float64(s), base))
  928. suffix := sizes[int(e)]
  929. val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
  930. f := "%.0f"
  931. if val < 10 {
  932. f = "%.1f"
  933. }
  934. return fmt.Sprintf(f, val), suffix
  935. }
  936. func logn(n, b float64) float64 {
  937. return math.Log(n) / math.Log(b)
  938. }
  939. // termWidth function returns the visible width of the current terminal
  940. // and can be redefined for testing
  941. var termWidth = func() (width int, err error) {
  942. width, _, err = term.GetSize(int(os.Stdout.Fd()))
  943. if err == nil {
  944. return width, nil
  945. }
  946. return 0, err
  947. }