pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/spinner/spinner.go (about)

     1  // Package spinner provides methods for creating spinner animation for
     2  // long-running tasks
     3  package spinner
     4  
     5  // ////////////////////////////////////////////////////////////////////////////////// //
     6  //                                                                                    //
     7  //                         Copyright (c) 2022 ESSENTIAL KAOS                          //
     8  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
     9  //                                                                                    //
    10  // ////////////////////////////////////////////////////////////////////////////////// //
    11  
    12  import (
    13  	"fmt"
    14  	"sync"
    15  	"time"
    16  
    17  	"pkg.re/essentialkaos/ek.v12/fmtc"
    18  	"pkg.re/essentialkaos/ek.v12/timeutil"
    19  )
    20  
    21  // ////////////////////////////////////////////////////////////////////////////////// //
    22  
    23  //  SpinnerColorTag is spinner animation color tag (see fmtc package)
    24  var SpinnerColorTag = "{y}"
    25  
    26  // OkColorTag is check color tag (see fmtc package)
    27  var OkColorTag = "{g}"
    28  
    29  // ErrColorTag is cross color tag (see fmtc package)
    30  var ErrColorTag = "{r}"
    31  
    32  // TimeColorTag is time color tag (see fmtc package)
    33  var TimeColorTag = "{s-}"
    34  
    35  // DisableAnimation is global animation off switch flag
    36  var DisableAnimation = false
    37  
    38  // ////////////////////////////////////////////////////////////////////////////////// //
    39  
    40  var spinnerFrames = []string{"⠸", "⠴", "⠤", "⠦", "⠇", "⠋", "⠉", "⠙"}
    41  
    42  var framesDelay = []time.Duration{
    43  	75 * time.Millisecond,
    44  	55 * time.Millisecond,
    45  	35 * time.Millisecond,
    46  	55 * time.Millisecond,
    47  	75 * time.Millisecond,
    48  	75 * time.Millisecond,
    49  	75 * time.Millisecond,
    50  	75 * time.Millisecond,
    51  }
    52  
    53  var desc string
    54  var start time.Time
    55  
    56  var isActive = false
    57  var isHidden = true
    58  
    59  var mu = &sync.RWMutex{}
    60  
    61  // ////////////////////////////////////////////////////////////////////////////////// //
    62  
    63  // Show shows spinner with given task description
    64  func Show(message string, args ...interface{}) {
    65  	mu.RLock()
    66  	if !isHidden {
    67  		mu.RUnlock()
    68  		return
    69  	}
    70  	mu.RUnlock()
    71  
    72  	mu.Lock()
    73  	desc = fmt.Sprintf(message, args...)
    74  	isActive, isHidden = true, false
    75  	start = time.Now()
    76  
    77  	if DisableAnimation {
    78  		isHidden = true
    79  	} else {
    80  		go showSpinner()
    81  	}
    82  	mu.Unlock()
    83  }
    84  
    85  // Update updates task description
    86  func Update(message string, args ...interface{}) {
    87  	mu.RLock()
    88  	if isHidden {
    89  		mu.RUnlock()
    90  		return
    91  	}
    92  	mu.RUnlock()
    93  
    94  	mu.Lock()
    95  	desc = fmt.Sprintf(message, args...)
    96  	mu.Unlock()
    97  }
    98  
    99  // Done finishes spinner animation and shows task status
   100  func Done(ok bool) {
   101  	mu.RLock()
   102  
   103  	if !isActive {
   104  		mu.RUnlock()
   105  		return
   106  	}
   107  
   108  	mu.RUnlock()
   109  
   110  	mu.Lock()
   111  	isActive = false
   112  	mu.Unlock()
   113  
   114  	for {
   115  		mu.RLock()
   116  		if isHidden {
   117  			mu.RUnlock()
   118  			break
   119  		}
   120  		mu.RUnlock()
   121  	}
   122  
   123  	mu.RLock()
   124  
   125  	if ok {
   126  		fmtc.Printf(
   127  			OkColorTag+"✔  {!}%s "+TimeColorTag+"(%s){!}\n",
   128  			desc, timeutil.ShortDuration(time.Since(start), true),
   129  		)
   130  	} else {
   131  		fmtc.Printf(
   132  			ErrColorTag+"✖  {!}%s "+TimeColorTag+"(%s){!}\n",
   133  			desc, timeutil.ShortDuration(time.Since(start), true),
   134  		)
   135  	}
   136  
   137  	mu.RUnlock()
   138  
   139  	mu.Lock()
   140  	desc, isActive, isHidden, start = "", false, true, time.Time{}
   141  	mu.Unlock()
   142  }
   143  
   144  // ////////////////////////////////////////////////////////////////////////////////// //
   145  
   146  func showSpinner() {
   147  	for {
   148  		for i, frame := range spinnerFrames {
   149  			mu.RLock()
   150  			fmtc.Printf(
   151  				SpinnerColorTag+"%s  {!}%s… "+TimeColorTag+"[%s]{!}",
   152  				frame, desc, timeutil.ShortDuration(time.Since(start)),
   153  			)
   154  			mu.RUnlock()
   155  			time.Sleep(framesDelay[i])
   156  			fmtc.Printf("\033[2K\r")
   157  
   158  			if !isActive {
   159  				mu.Lock()
   160  				isHidden = true
   161  				mu.Unlock()
   162  				return
   163  			}
   164  		}
   165  	}
   166  }