11package main
22
33import (
4+ "bytes"
45 "fmt"
56 "io"
67 "io/ioutil"
78 "os"
89 "strings"
910 "sync"
11+ "sync/atomic"
1012 "time"
1113
1214 "github.com/fatih/color"
@@ -31,35 +33,49 @@ type actionLogger struct {
3133
3234 highlight func (a ... interface {}) string
3335
36+ progress * progress
37+ out io.Writer
38+
39+ mu sync.Mutex
3440 logFiles map [string ]* os.File
3541 logWriters map [string ]io.Writer
36- mu sync.Mutex
3742}
3843
3944func newActionLogger (verbose , keepLogs bool ) * actionLogger {
4045 useColor := isatty .IsTerminal (os .Stderr .Fd ()) || isatty .IsCygwinTerminal (os .Stderr .Fd ())
4146 if useColor {
4247 color .NoColor = false
4348 }
49+
50+ progress := new (progress )
51+
4452 return & actionLogger {
45- verbose : verbose ,
46- keepLogs : keepLogs ,
47- highlight : color .New (color .Bold , color .FgGreen ).SprintFunc (),
53+ verbose : verbose ,
54+ keepLogs : keepLogs ,
55+ highlight : color .New (color .Bold , color .FgGreen ).SprintFunc (),
56+ progress : progress ,
57+ out : & progressWriter {
58+ p : progress ,
59+ w : os .Stderr ,
60+ },
4861 logFiles : map [string ]* os.File {},
4962 logWriters : map [string ]io.Writer {},
5063 }
5164}
5265
53- func (a * actionLogger ) Start () {
66+ func (a * actionLogger ) Start (totalSteps int ) {
5467 if a .verbose {
68+ a .progress .SetTotalSteps (int64 (totalSteps ))
5569 fmt .Fprintln (os .Stderr )
5670 }
5771}
5872
73+ func (a * actionLogger ) Infof (format string , args ... interface {}) {
74+ a .log ("" , grey , format , args ... )
75+ }
76+
5977func (a * actionLogger ) Warnf (format string , args ... interface {}) {
60- if a .verbose {
61- yellow .Fprintf (os .Stderr , "WARNING: " + format , args ... )
62- }
78+ a .log ("" , yellow , "WARNING: " + format , args ... )
6379}
6480
6581func (a * actionLogger ) ActionFailed (err error , patches []PatchInput ) {
@@ -89,31 +105,25 @@ func (a *actionLogger) ActionFailed(err error, patches []PatchInput) {
89105}
90106
91107func (a * actionLogger ) ActionSuccess (patches []PatchInput , newLines bool ) {
92- if a .verbose {
93- fmt .Fprintln (os .Stderr )
94- format := "✔ Action produced %d patches."
95- if newLines {
96- format = format + "\n \n "
97- }
98- hiGreen .Fprintf (os .Stderr , format , len (patches ))
108+ if ! a .verbose {
109+ return
99110 }
100- }
101-
102- func (a * actionLogger ) Infof (format string , args ... interface {}) {
103- if a .verbose {
104- grey .Fprintf (os .Stderr , format , args ... )
111+ fmt .Fprintln (os .Stderr )
112+ format := "✔ Action produced %d patches."
113+ if newLines {
114+ format = format + "\n \n "
105115 }
116+ hiGreen .Fprintf (os .Stderr , format , len (patches ))
106117}
107118
108- func (a * actionLogger ) RepoCacheHit (repo ActionRepo , patchProduced bool ) {
109- if a .verbose {
110- if patchProduced {
111- fmt .Fprintf (os .Stderr , "%s -> Cached result found: using cached diff.\n " , boldGreen .Sprint (repo .Name ))
112- return
113- }
114-
115- fmt .Fprintf (os .Stderr , "%s -> Cached result found: no diff produced for this repository.\n " , grey .Sprint (repo .Name ))
119+ func (a * actionLogger ) RepoCacheHit (repo ActionRepo , stepCount int , patchProduced bool ) {
120+ a .progress .IncStepsComplete (int64 (stepCount ))
121+ if patchProduced {
122+ a .progress .IncPatchCount ()
123+ a .log (repo .Name , boldGreen , "Cached result found: using cached diff.\n " )
124+ return
116125 }
126+ a .log (repo .Name , grey , "Cached result found: no diff produced for this repository.\n " )
117127}
118128
119129func (a * actionLogger ) AddRepo (repo ActionRepo ) (string , error ) {
@@ -151,25 +161,14 @@ func (a *actionLogger) RepoStdoutStderr(repoName string) (io.Writer, io.Writer,
151161 }
152162
153163 stderrPrefix := fmt .Sprintf ("%s -> [STDERR]: " , yellow .Sprint (repoName ))
154- stderr := textio .NewPrefixWriter (os . Stderr , stderrPrefix )
164+ stderr := textio .NewPrefixWriter (a . out , stderrPrefix )
155165
156166 stdoutPrefix := fmt .Sprintf ("%s -> [STDOUT]: " , yellow .Sprint (repoName ))
157- stdout := textio .NewPrefixWriter (os . Stderr , stdoutPrefix )
167+ stdout := textio .NewPrefixWriter (a . out , stdoutPrefix )
158168
159169 return io .MultiWriter (stdout , w ), io .MultiWriter (stderr , w ), ok
160170}
161171
162- func (a * actionLogger ) write (repoName string , c * color.Color , format string , args ... interface {}) {
163- if w , ok := a .RepoWriter (repoName ); ok {
164- fmt .Fprintf (w , format , args ... )
165- }
166-
167- if a .verbose {
168- format = fmt .Sprintf ("%s -> %s" , c .Sprint (repoName ), format )
169- fmt .Fprintf (os .Stderr , format , args ... )
170- }
171- }
172-
173172func (a * actionLogger ) RepoFinished (repoName string , patchProduced bool , actionErr error ) error {
174173 a .mu .Lock ()
175174 f , ok := a .logFiles [repoName ]
@@ -186,6 +185,7 @@ func (a *actionLogger) RepoFinished(repoName string, patchProduced bool, actionE
186185 a .write (repoName , boldRed , "Action failed: %q\n " , actionErr )
187186 }
188187 } else if patchProduced {
188+ a .progress .IncPatchCount ()
189189 a .write (repoName , boldGreen , "Finished. Patch produced.\n " )
190190 } else {
191191 a .write (repoName , grey , "Finished. No patch produced.\n " )
@@ -215,10 +215,13 @@ func (a *actionLogger) CommandStepStarted(repoName string, step int, args []stri
215215}
216216
217217func (a * actionLogger ) CommandStepErrored (repoName string , step int , err error ) {
218+ a .progress .IncStepsComplete (1 )
219+ a .progress .IncStepsFailed ()
218220 a .write (repoName , boldRed , "%s %s.\n " , boldBlack .Sprintf ("[Step %d]" , step ), err )
219221}
220222
221223func (a * actionLogger ) CommandStepDone (repoName string , step int ) {
224+ a .progress .IncStepsComplete (1 )
222225 a .write (repoName , yellow , "%s Done.\n " , boldBlack .Sprintf ("[Step %d]" , step ))
223226}
224227
@@ -227,9 +230,126 @@ func (a *actionLogger) DockerStepStarted(repoName string, step int, image string
227230}
228231
229232func (a * actionLogger ) DockerStepErrored (repoName string , step int , err error , elapsed time.Duration ) {
233+ a .progress .IncStepsComplete (1 )
234+ a .progress .IncStepsFailed ()
230235 a .write (repoName , boldRed , "%s %s. (%s)\n " , boldBlack .Sprintf ("[Step %d]" , step ), err , elapsed )
231236}
232237
233238func (a * actionLogger ) DockerStepDone (repoName string , step int , elapsed time.Duration ) {
239+ a .progress .IncStepsComplete (1 )
234240 a .write (repoName , yellow , "%s Done. (%s)\n " , boldBlack .Sprintf ("[Step %d]" , step ), elapsed )
235241}
242+
243+ // write writes to the RepoWriter associated with the given repoName and logs the message using the log method.
244+ func (a * actionLogger ) write (repoName string , c * color.Color , format string , args ... interface {}) {
245+ if w , ok := a .RepoWriter (repoName ); ok {
246+ fmt .Fprintf (w , format , args ... )
247+ }
248+ a .log (repoName , c , format , args ... )
249+ }
250+
251+ // log logs only to stderr, it does not log to our repoWriters. When not in verbose mode, it's a noop.
252+ func (a * actionLogger ) log (repoName string , c * color.Color , format string , args ... interface {}) {
253+ if ! a .verbose {
254+ return
255+ }
256+ if len (repoName ) > 0 {
257+ format = fmt .Sprintf ("%s -> %s" , c .Sprint (repoName ), format )
258+ }
259+ fmt .Fprintf (a .out , format , args ... )
260+ }
261+
262+ type progress struct {
263+ patchCount int64
264+
265+ totalSteps int64
266+ stepsComplete int64
267+ stepsFailed int64
268+ }
269+
270+ func (p * progress ) SetTotalSteps (n int64 ) {
271+ atomic .StoreInt64 (& p .totalSteps , n )
272+ }
273+
274+ func (p * progress ) TotalSteps () int64 {
275+ return atomic .LoadInt64 (& p .totalSteps )
276+ }
277+
278+ func (p * progress ) StepsComplete () int64 {
279+ return atomic .LoadInt64 (& p .stepsComplete )
280+ }
281+
282+ func (p * progress ) IncStepsComplete (delta int64 ) {
283+ atomic .AddInt64 (& p .stepsComplete , delta )
284+ }
285+
286+ func (p * progress ) TotalStepsFailed () int64 {
287+ return atomic .LoadInt64 (& p .stepsFailed )
288+ }
289+
290+ func (p * progress ) IncStepsFailed () {
291+ atomic .AddInt64 (& p .stepsFailed , 1 )
292+ }
293+
294+ func (p * progress ) PatchCount () int64 {
295+ return atomic .LoadInt64 (& p .patchCount )
296+ }
297+
298+ func (p * progress ) IncPatchCount () {
299+ atomic .AddInt64 (& p .patchCount , 1 )
300+ }
301+
302+ type progressWriter struct {
303+ p * progress
304+
305+ mu sync.Mutex
306+ w io.Writer
307+ shouldClear bool
308+ progressLogLength int
309+ }
310+
311+ func (w * progressWriter ) Write (data []byte ) (int , error ) {
312+ w .mu .Lock ()
313+ defer w .mu .Unlock ()
314+
315+ if w .shouldClear {
316+ // Clear current progress
317+ fmt .Fprintf (w .w , "\r " )
318+ fmt .Fprintf (w .w , strings .Repeat (" " , w .progressLogLength ))
319+ fmt .Fprintf (w .w , "\r " )
320+ }
321+
322+ if w .p .TotalSteps () == 0 {
323+ // Don't display bar until we know number of steps
324+ w .shouldClear = false
325+ return w .w .Write (data )
326+ }
327+
328+ if ! bytes .HasSuffix (data , []byte ("\n " )) {
329+ w .shouldClear = false
330+ return w .w .Write (data )
331+ }
332+
333+ n , err := w .w .Write (data )
334+ if err != nil {
335+ return n , err
336+ }
337+ total := w .p .TotalSteps ()
338+ done := w .p .StepsComplete ()
339+ var pctDone float64
340+ if total > 0 {
341+ pctDone = float64 (done ) / float64 (total )
342+ }
343+
344+ maxLength := 50
345+ bar := strings .Repeat ("=" , int (float64 (maxLength )* pctDone ))
346+ if len (bar ) < maxLength {
347+ bar += ">"
348+ }
349+ bar += strings .Repeat (" " , maxLength - len (bar ))
350+ progessText := fmt .Sprintf ("[%s] Steps: %d/%d (%s, %s)" , bar , w .p .StepsComplete (), w .p .TotalSteps (), boldRed .Sprintf ("%d failed" , w .p .TotalStepsFailed ()), hiGreen .Sprintf ("%d patches" , w .p .PatchCount ()))
351+ fmt .Fprintf (w .w , progessText )
352+ w .shouldClear = true
353+ w .progressLogLength = len (progessText )
354+ return n , err
355+ }
0 commit comments