github.com/mattevans/edward@v1.9.2/output/rendering_completion.go (about) 1 package output 2 3 import ( 4 "fmt" 5 "io" 6 "time" 7 8 "github.com/fatih/color" 9 "github.com/pkg/errors" 10 "github.com/mattevans/edward/tracker" 11 ) 12 13 type CompletionRenderer struct { 14 indent string 15 minSpacing int 16 targetTask tracker.Task 17 } 18 19 func NewCompletionRenderer(task tracker.Task) *CompletionRenderer { 20 return &CompletionRenderer{ 21 indent: " ", 22 minSpacing: 3, 23 targetTask: task, 24 } 25 } 26 27 func (r *CompletionRenderer) Render(w io.Writer) error { 28 task := r.targetTask.Lineage()[0] 29 err := errors.WithStack(r.doRenderWithPrefix("", 0, w, task)) 30 if err != nil { 31 return errors.WithStack(err) 32 } 33 return nil 34 } 35 36 func extendPrefix(prefix string, child tracker.Task) string { 37 if prefix == "" { 38 return child.Name() 39 } 40 return fmt.Sprintf("%v > %v", prefix, child.Name()) 41 } 42 43 func (r *CompletionRenderer) doRenderWithPrefix(prefix string, maxNameWidth int, w io.Writer, task tracker.Task) error { 44 newMax := r.getLongestName(w, prefix, task) 45 if newMax > maxNameWidth { 46 maxNameWidth = newMax 47 } 48 49 children := task.Children() 50 for _, child := range children { 51 r.doRenderWithPrefix(extendPrefix(prefix, child), maxNameWidth, w, child) 52 } 53 54 if task != r.targetTask { 55 return nil 56 } 57 58 ts := task.State() 59 // Print name 60 nameFormat := fmt.Sprintf("%%-%ds", maxNameWidth+r.minSpacing) 61 fmt.Fprintf(w, nameFormat, fmt.Sprintf("%v:", prefix)) 62 63 tmpOutput := color.Output 64 defer func() { 65 color.Output = tmpOutput 66 }() 67 color.Output = w 68 fmt.Fprint(w, "[") 69 switch ts { 70 case tracker.TaskStateSuccess: 71 color.Set(color.FgGreen) 72 fmt.Fprint(w, "OK") 73 case tracker.TaskStateFailed: 74 color.Set(color.FgRed) 75 fmt.Fprint(w, "Failed") 76 case tracker.TaskStateWarning: 77 color.Set(color.FgYellow) 78 fmt.Fprint(w, "Warning") 79 case tracker.TaskStatePending: 80 color.Set(color.FgCyan) 81 fmt.Fprint(w, "Pending") 82 default: 83 color.Set(color.FgCyan) 84 fmt.Fprint(w, "In Progress") 85 } 86 color.Unset() 87 fmt.Fprint(w, "]") 88 if ts != tracker.TaskStateInProgress && ts != tracker.TaskStatePending { 89 fmt.Fprintf(w, " (%v)", autoRoundTime(task.Duration())) 90 } 91 fmt.Fprintln(w) 92 if ts == tracker.TaskStateFailed || ts == tracker.TaskStateWarning { 93 for _, line := range task.Messages() { 94 fmt.Fprintln(w, line) 95 } 96 } 97 98 return nil 99 } 100 101 func (r *CompletionRenderer) getLongestName(w io.Writer, prefix string, task tracker.Task) int { 102 children := task.Children() 103 var max = len(prefix) 104 for _, child := range children { 105 childMax := r.getLongestName(w, extendPrefix(prefix, child), child) 106 if childMax > max { 107 max = childMax 108 } 109 } 110 return max 111 } 112 113 func autoRoundTime(d time.Duration) time.Duration { 114 if d > time.Hour { 115 return roundTime(d, time.Second) 116 } 117 if d > time.Minute { 118 return roundTime(d, time.Second) 119 } 120 if d > time.Second { 121 return roundTime(d, time.Millisecond) 122 } 123 if d > time.Millisecond { 124 return roundTime(d, time.Microsecond) 125 } 126 return d 127 } 128 129 // Based on the example at https://play.golang.org/p/QHocTHl8iR 130 func roundTime(d, r time.Duration) time.Duration { 131 if r <= 0 { 132 return d 133 } 134 neg := d < 0 135 if neg { 136 d = -d 137 } 138 if m := d % r; m+m < r { 139 d = d - m 140 } else { 141 d = d + r - m 142 } 143 if neg { 144 return -d 145 } 146 return d 147 }