...

Source file src/github.com/jedib0t/go-pretty/v6/progress/progress.go

Documentation: github.com/jedib0t/go-pretty/v6/progress

     1  package progress
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"sync"
     8  	"time"
     9  	"unicode/utf8"
    10  
    11  	"github.com/jedib0t/go-pretty/v6/text"
    12  )
    13  
    14  var (
    15  	// DefaultLengthTracker defines a sane value for a Tracker's length.
    16  	DefaultLengthTracker = 20
    17  
    18  	// DefaultUpdateFrequency defines a sane value for the frequency with which
    19  	// all the Tracker's get updated on the screen.
    20  	DefaultUpdateFrequency = time.Millisecond * 250
    21  )
    22  
    23  // Progress helps track progress for one or more tasks.
    24  type Progress struct {
    25  	autoStop              bool
    26  	done                  chan bool
    27  	lengthTracker         int
    28  	lengthProgress        int
    29  	lengthProgressOverall int
    30  	outputWriter          io.Writer
    31  	logsToRender          []string
    32  	logsToRenderMutex     sync.RWMutex
    33  	messageWidth          int
    34  	numTrackersExpected   int64
    35  	overallTracker        *Tracker
    36  	overallTrackerMutex   sync.RWMutex
    37  	renderInProgress      bool
    38  	renderInProgressMutex sync.RWMutex
    39  	sortBy                SortBy
    40  	style                 *Style
    41  	trackerPosition       Position
    42  	trackersActive        []*Tracker
    43  	trackersActiveMutex   sync.RWMutex
    44  	trackersDone          []*Tracker
    45  	trackersDoneMutex     sync.RWMutex
    46  	trackersInQueue       []*Tracker
    47  	trackersInQueueMutex  sync.RWMutex
    48  	updateFrequency       time.Duration
    49  }
    50  
    51  // Position defines the position of the Tracker with respect to the Tracker's
    52  // Message.
    53  type Position int
    54  
    55  const (
    56  	// PositionLeft will make the Tracker be displayed first before the Message.
    57  	PositionLeft Position = iota
    58  
    59  	// PositionRight will make the Tracker be displayed after the Message.
    60  	PositionRight
    61  )
    62  
    63  // AppendTracker appends a single Tracker for tracking. The Tracker gets added
    64  // to a queue, which gets picked up by the Render logic in the next rendering
    65  // cycle.
    66  func (p *Progress) AppendTracker(t *Tracker) {
    67  	t.start()
    68  
    69  	p.overallTrackerMutex.Lock()
    70  	if p.overallTracker == nil {
    71  		p.overallTracker = &Tracker{Total: 1}
    72  		if p.numTrackersExpected > 0 {
    73  			p.overallTracker.Total = p.numTrackersExpected * 100
    74  		}
    75  		p.overallTracker.start()
    76  	}
    77  	p.trackersInQueueMutex.Lock()
    78  	p.trackersInQueue = append(p.trackersInQueue, t)
    79  	p.trackersInQueueMutex.Unlock()
    80  	p.overallTracker.mutex.Lock()
    81  	if p.overallTracker.Total < int64(p.Length())*100 {
    82  		p.overallTracker.Total = int64(p.Length()) * 100
    83  	}
    84  	p.overallTracker.mutex.Unlock()
    85  	p.overallTrackerMutex.Unlock()
    86  }
    87  
    88  // AppendTrackers appends one or more Trackers for tracking.
    89  func (p *Progress) AppendTrackers(trackers []*Tracker) {
    90  	for _, tracker := range trackers {
    91  		p.AppendTracker(tracker)
    92  	}
    93  }
    94  
    95  // IsRenderInProgress returns true if a call to Render() was made, and is still
    96  // in progress and has not ended yet.
    97  func (p *Progress) IsRenderInProgress() bool {
    98  	p.renderInProgressMutex.RLock()
    99  	defer p.renderInProgressMutex.RUnlock()
   100  
   101  	return p.renderInProgress
   102  }
   103  
   104  // Length returns the number of Trackers tracked overall.
   105  func (p *Progress) Length() int {
   106  	p.trackersActiveMutex.RLock()
   107  	p.trackersDoneMutex.RLock()
   108  	p.trackersInQueueMutex.RLock()
   109  	out := len(p.trackersInQueue) + len(p.trackersActive) + len(p.trackersDone)
   110  	p.trackersInQueueMutex.RUnlock()
   111  	p.trackersDoneMutex.RUnlock()
   112  	p.trackersActiveMutex.RUnlock()
   113  
   114  	return out
   115  }
   116  
   117  // LengthActive returns the number of Trackers actively tracked (not done yet).
   118  func (p *Progress) LengthActive() int {
   119  	p.trackersActiveMutex.RLock()
   120  	p.trackersInQueueMutex.RLock()
   121  	out := len(p.trackersInQueue) + len(p.trackersActive)
   122  	p.trackersInQueueMutex.RUnlock()
   123  	p.trackersActiveMutex.RUnlock()
   124  
   125  	return out
   126  }
   127  
   128  // LengthDone returns the number of Trackers that are done tracking.
   129  func (p *Progress) LengthDone() int {
   130  	p.trackersDoneMutex.RLock()
   131  	out := len(p.trackersDone)
   132  	p.trackersDoneMutex.RUnlock()
   133  
   134  	return out
   135  }
   136  
   137  // LengthInQueue returns the number of Trackers in queue to be actively tracked
   138  // (not tracking yet).
   139  func (p *Progress) LengthInQueue() int {
   140  	p.trackersInQueueMutex.RLock()
   141  	out := len(p.trackersInQueue)
   142  	p.trackersInQueueMutex.RUnlock()
   143  
   144  	return out
   145  }
   146  
   147  // Log appends a log to display above the active progress bars during the next
   148  // refresh.
   149  func (p *Progress) Log(msg string, a ...interface{}) {
   150  	if len(a) > 0 {
   151  		msg = fmt.Sprintf(msg, a...)
   152  	}
   153  	p.logsToRenderMutex.Lock()
   154  	p.logsToRender = append(p.logsToRender, msg)
   155  	p.logsToRenderMutex.Unlock()
   156  }
   157  
   158  // SetAutoStop toggles the auto-stop functionality. Auto-stop set to true would
   159  // mean that the Render() function will automatically stop once all currently
   160  // active Trackers reach their final states. When set to false, the client code
   161  // will have to call Progress.Stop() to stop the Render() logic. Default: false.
   162  func (p *Progress) SetAutoStop(autoStop bool) {
   163  	p.autoStop = autoStop
   164  }
   165  
   166  // SetMessageWidth sets the (printed) length of the tracker message. Any message
   167  // longer the specified width will be snipped abruptly. Any message shorter than
   168  // the specified width will be padded with spaces.
   169  func (p *Progress) SetMessageWidth(width int) {
   170  	p.messageWidth = width
   171  }
   172  
   173  // SetNumTrackersExpected sets the expected number of trackers to be tracked.
   174  // This helps calculate the overall progress with better accuracy.
   175  func (p *Progress) SetNumTrackersExpected(numTrackers int) {
   176  	p.numTrackersExpected = int64(numTrackers)
   177  }
   178  
   179  // SetOutputWriter redirects the output of Render to an io.writer object like
   180  // os.Stdout or os.Stderr or a file. Warning: redirecting the output to a file
   181  // may not work well as the Render() logic moves the cursor around a lot.
   182  func (p *Progress) SetOutputWriter(writer io.Writer) {
   183  	p.outputWriter = writer
   184  }
   185  
   186  // SetSortBy defines the sorting mechanism to use to sort the Active Trackers
   187  // before rendering the. Default: no-sorting == sort-by-insertion-order.
   188  func (p *Progress) SetSortBy(sortBy SortBy) {
   189  	p.sortBy = sortBy
   190  }
   191  
   192  // SetStyle sets the Style to use for rendering.
   193  func (p *Progress) SetStyle(style Style) {
   194  	p.style = &style
   195  }
   196  
   197  // SetTrackerLength sets the text-length of all the Trackers.
   198  func (p *Progress) SetTrackerLength(length int) {
   199  	p.lengthTracker = length
   200  }
   201  
   202  // SetTrackerPosition sets the position of the tracker with respect to the
   203  // Tracker message text.
   204  func (p *Progress) SetTrackerPosition(position Position) {
   205  	p.trackerPosition = position
   206  }
   207  
   208  // SetUpdateFrequency sets the update frequency while rendering the trackers.
   209  // the lower the value, the more number of times the Trackers get refreshed. A
   210  // sane value would be 250ms.
   211  func (p *Progress) SetUpdateFrequency(frequency time.Duration) {
   212  	p.updateFrequency = frequency
   213  }
   214  
   215  // ShowETA toggles showing the ETA for all individual trackers.
   216  // Deprecated: in favor of Style().Visibility.ETA
   217  func (p *Progress) ShowETA(show bool) {
   218  	p.Style().Visibility.ETA = show
   219  }
   220  
   221  // ShowPercentage toggles showing the Percent complete for each Tracker.
   222  // Deprecated: in favor of Style().Visibility.Percentage
   223  func (p *Progress) ShowPercentage(show bool) {
   224  	p.Style().Visibility.Percentage = show
   225  }
   226  
   227  // ShowOverallTracker toggles showing the Overall progress tracker with an ETA.
   228  // Deprecated: in favor of Style().Visibility.TrackerOverall
   229  func (p *Progress) ShowOverallTracker(show bool) {
   230  	p.Style().Visibility.TrackerOverall = show
   231  }
   232  
   233  // ShowTime toggles showing the Time taken by each Tracker.
   234  // Deprecated: in favor of Style().Visibility.Time
   235  func (p *Progress) ShowTime(show bool) {
   236  	p.Style().Visibility.Time = show
   237  }
   238  
   239  // ShowTracker toggles showing the Tracker (the progress bar).
   240  // Deprecated: in favor of Style().Visibility.Tracker
   241  func (p *Progress) ShowTracker(show bool) {
   242  	p.Style().Visibility.Tracker = show
   243  }
   244  
   245  // ShowValue toggles showing the actual Value of the Tracker.
   246  // Deprecated: in favor of Style().Visibility.Value
   247  func (p *Progress) ShowValue(show bool) {
   248  	p.Style().Visibility.Value = show
   249  }
   250  
   251  // Stop stops the Render() logic that is in progress.
   252  func (p *Progress) Stop() {
   253  	if p.IsRenderInProgress() {
   254  		p.done <- true
   255  	}
   256  }
   257  
   258  // Style returns the current Style.
   259  func (p *Progress) Style() *Style {
   260  	if p.style == nil {
   261  		tempStyle := StyleDefault
   262  		p.style = &tempStyle
   263  	}
   264  	return p.style
   265  }
   266  
   267  func (p *Progress) initForRender() {
   268  	// pick a default style
   269  	p.Style()
   270  	if p.style.Options.SpeedOverallFormatter == nil {
   271  		p.style.Options.SpeedOverallFormatter = FormatNumber
   272  	}
   273  
   274  	// reset the signals
   275  	p.done = make(chan bool, 1)
   276  
   277  	// pick default lengths if no valid ones set
   278  	if p.lengthTracker <= 0 {
   279  		p.lengthTracker = DefaultLengthTracker
   280  	}
   281  
   282  	// calculate length of the actual progress bar by discounting the left/right
   283  	// border/box chars
   284  	p.lengthProgress = p.lengthTracker -
   285  		utf8.RuneCountInString(p.style.Chars.BoxLeft) -
   286  		utf8.RuneCountInString(p.style.Chars.BoxRight)
   287  	p.lengthProgressOverall = p.messageWidth +
   288  		text.RuneWidthWithoutEscSequences(p.style.Options.Separator) +
   289  		p.lengthProgress + 1
   290  	if p.style.Visibility.Percentage {
   291  		p.lengthProgressOverall += text.RuneWidthWithoutEscSequences(fmt.Sprintf(p.style.Options.PercentFormat, 0.0))
   292  	}
   293  
   294  	// if not output write has been set, output to STDOUT
   295  	if p.outputWriter == nil {
   296  		p.outputWriter = os.Stdout
   297  	}
   298  
   299  	// pick a sane update frequency if none set
   300  	if p.updateFrequency <= 0 {
   301  		p.updateFrequency = DefaultUpdateFrequency
   302  	}
   303  }
   304  
   305  // renderHint has hints for the Render*() logic
   306  type renderHint struct {
   307  	hideTime         bool // hide the time
   308  	hideValue        bool // hide the value
   309  	isOverallTracker bool // is the Overall Progress tracker
   310  }
   311  

View as plain text