From ce502af30f767acb3663834638f8e24006171609 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Thu, 5 Sep 2019 10:53:24 +0200 Subject: [PATCH] Add rainbow colouring of the steps We were showing the same blue color for every steps, let's now cycle of a list of colors. Signed-off-by: Chmouel Boudjnah --- pkg/cmd/pipelinerun/log_writer.go | 3 +- pkg/cmd/taskrun/log_writer.go | 2 +- pkg/formatted/color.go | 64 ++++++++++++++++++++++++++++--- pkg/formatted/color_test.go | 41 ++++++++++++++++++++ 4 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 pkg/formatted/color_test.go diff --git a/pkg/cmd/pipelinerun/log_writer.go b/pkg/cmd/pipelinerun/log_writer.go index cabb5f31f3..d79962f628 100644 --- a/pkg/cmd/pipelinerun/log_writer.go +++ b/pkg/cmd/pipelinerun/log_writer.go @@ -45,7 +45,8 @@ func (lw *LogWriter) Write(s *cli.Stream, logC <-chan Log, errC <-chan error) { fmt.Fprintf(s.Out, "\n") continue } - lw.fmt.Header(s.Out, "[%s : %s] ", l.Task, l.Step) + + lw.fmt.Rainbow.Fprintf(l.Step, s.Out, "[%s : %s] ", l.Task, l.Step) fmt.Fprintf(s.Out, "%s\n", l.Log) case e, ok := <-errC: if !ok { diff --git a/pkg/cmd/taskrun/log_writer.go b/pkg/cmd/taskrun/log_writer.go index 1d62471958..786b2717a2 100644 --- a/pkg/cmd/taskrun/log_writer.go +++ b/pkg/cmd/taskrun/log_writer.go @@ -46,7 +46,7 @@ func (lw *LogWriter) Write(s *cli.Stream, logC <-chan Log, errC <-chan error) { continue } - lw.fmt.Header(s.Out, "[%s] ", l.Step) + lw.fmt.Rainbow.Fprintf(l.Step, s.Out, "[%s] ", l.Step) fmt.Fprintf(s.Out, "%s\n", l.Log) case e, ok := <-errC: if !ok { diff --git a/pkg/formatted/color.go b/pkg/formatted/color.go index 53bb500f7a..83dcb454d4 100644 --- a/pkg/formatted/color.go +++ b/pkg/formatted/color.go @@ -16,12 +16,69 @@ package formatted import ( "io" + "sync" + "sync/atomic" "github.com/fatih/color" ) +var ( + // Red is avoided as keeping it for errors + palette = []color.Attribute{ + color.FgHiGreen, + color.FgHiYellow, + color.FgHiBlue, + color.FgHiMagenta, + color.FgHiCyan, + } +) + +type atomicCounter struct { + value uint32 + threshold int +} + +func (c *atomicCounter) next() int { + v := atomic.AddUint32(&c.value, 1) + next := int(v-1) % c.threshold + atomic.CompareAndSwapUint32(&c.value, uint32(c.threshold), 0) + return next + +} + +type rainbow struct { + cache sync.Map + counter atomicCounter +} + +func newRainbow() *rainbow { + return &rainbow{ + counter: atomicCounter{threshold: len(palette)}, + } +} + +func (r *rainbow) get(x string) color.Attribute { + if value, ok := r.cache.Load(x); ok { + return value.(color.Attribute) + } + + clr := palette[r.counter.next()] + r.cache.Store(x, clr) + return clr +} + +// Fprintf formats according to a format specifier and writes to w. +// the first argument is a label to keep the same colour on. +func (r *rainbow) Fprintf(label string, w io.Writer, format string, args ...interface{}) { + attribute := r.get(label) + crainbow := color.Set(attribute).Add(color.Bold) + crainbow.Fprintf(w, format, args...) +} + //Color formatter to print the colored output on streams type Color struct { + Rainbow *rainbow + red *color.Color blue *color.Color } @@ -29,6 +86,8 @@ type Color struct { //NewColor returns a new instance color formatter func NewColor() *Color { return &Color{ + Rainbow: newRainbow(), + red: color.New(color.FgRed), blue: color.New(color.FgBlue), } @@ -39,11 +98,6 @@ func (c *Color) PrintBlue(w io.Writer, format string, args ...interface{}) { c.blue.Fprintf(w, format, args...) } -//Header prints the formatted content to given destination in blue color -func (c *Color) Header(w io.Writer, format string, args ...interface{}) { - c.PrintBlue(w, format, args...) -} - //PrintBlue prints the formatted content to given destination in red color func (c *Color) PrintRed(w io.Writer, format string, args ...interface{}) { c.red.Fprintf(w, format, args...) diff --git a/pkg/formatted/color_test.go b/pkg/formatted/color_test.go new file mode 100644 index 0000000000..d4132ce9ce --- /dev/null +++ b/pkg/formatted/color_test.go @@ -0,0 +1,41 @@ +// Copyright © 2019 The Tekton Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package formatted + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRainbowsColours(t *testing.T) { + rb := newRainbow() + assert.Equal(t, rb.counter.value, uint32(0)) // nothing + + c := rb.get("a") // get a label + assert.NotNil(t, c) + assert.Equal(t, rb.counter.value, uint32(1)) + + _ = rb.get("b") // incremented + assert.Equal(t, rb.counter.value, uint32(2)) + + _ = rb.get("a") // no increment (cached) + assert.Equal(t, rb.counter.value, uint32(2)) + + rb = newRainbow() + for c := range palette { + rb.get(string(c)) + } + assert.Equal(t, rb.counter.value, uint32(0)) // Looped back to 0 +}