pkg.re/essentialkaos/ek@v12.36.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) 2021 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 }