This is an automated email from the ASF dual-hosted git repository.

nferraro pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit 28ff8dc47511c214dac9cd1d72fa37c3808ce9b1
Author: lburgazzoli <[email protected]>
AuthorDate: Fri Oct 12 16:51:50 2018 +0200

    chore(kamel) : add some colours
---
 Gopkg.lock                                         |  13 ++
 pkg/client/cmd/run.go                              |  31 +++-
 vendor/github.com/arsham/blush/LICENSE             |  21 +++
 vendor/github.com/arsham/blush/blush/blush.go      | 188 ++++++++++++++++++++
 vendor/github.com/arsham/blush/blush/colour.go     | 196 +++++++++++++++++++++
 vendor/github.com/arsham/blush/blush/doc.go        |  30 ++++
 vendor/github.com/arsham/blush/blush/errors.go     |  16 ++
 vendor/github.com/arsham/blush/blush/find.go       | 168 ++++++++++++++++++
 .../arsham/blush/internal/reader/reader.go         | 150 ++++++++++++++++
 .../github.com/arsham/blush/internal/tools/dir.go  | 118 +++++++++++++
 .../arsham/blush/internal/tools/strings.go         |  20 +++
 11 files changed, 943 insertions(+), 8 deletions(-)

diff --git a/Gopkg.lock b/Gopkg.lock
index 0c22817..26413b2 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -26,6 +26,18 @@
   revision = "de5bf2ad457846296e2031421a34e2568e304e35"
 
 [[projects]]
+  digest = "1:69ebf3eaf09a9528d0fa78baecf58a816acd73b6b500eb714c54b9d8a01cff0c"
+  name = "github.com/arsham/blush"
+  packages = [
+    "blush",
+    "internal/reader",
+    "internal/tools",
+  ]
+  pruneopts = "NUT"
+  revision = "a87294e47998d46b608c76cecb35b712103ad45b"
+  version = "v0.5.3"
+
+[[projects]]
   branch = "master"
   digest = "1:707ebe952a8b3d00b343c01536c79c73771d100f63ec6babeaed5c79e2b8a8dd"
   name = "github.com/beorn7/perks"
@@ -739,6 +751,7 @@
   analyzer-name = "dep"
   analyzer-version = 1
   input-imports = [
+    "github.com/arsham/blush/blush",
     "github.com/fatih/structs",
     "github.com/mitchellh/mapstructure",
     "github.com/openshift/api/apps/v1",
diff --git a/pkg/client/cmd/run.go b/pkg/client/cmd/run.go
index 39ef9a8..558244e 100644
--- a/pkg/client/cmd/run.go
+++ b/pkg/client/cmd/run.go
@@ -28,6 +28,7 @@ import (
        "strings"
 
        "github.com/apache/camel-k/pkg/trait"
+       "github.com/arsham/blush/blush"
 
        "github.com/apache/camel-k/pkg/util"
 
@@ -193,15 +194,29 @@ func (o *runCmdOptions) 
waitForIntegrationReady(integration *v1alpha1.Integratio
 func (o *runCmdOptions) printLogs(integration *v1alpha1.Integration) error {
        scraper := log.NewSelectorScraper(integration.Namespace, 
"camel.apache.org/integration="+integration.Name)
        reader := scraper.Start(o.Context)
-       for {
-               str, err := reader.ReadString('\n')
-               if err == io.EOF || o.Context.Err() != nil {
-                       break
-               } else if err != nil {
-                       return err
-               }
-               fmt.Print(str)
+
+       b := &blush.Blush{
+               Finders: []blush.Finder{
+                       blush.NewExact("FATAL", blush.Red),
+                       blush.NewExact("ERROR", blush.Red),
+                       blush.NewExact("WARN", blush.Yellow),
+                       blush.NewExact("INFO", blush.Green),
+                       blush.NewExact("DEBUG", blush.Colour{
+                               Foreground: blush.RGB{R: 170, G: 170, B: 170},
+                               Background: blush.NoRGB,
+                       }),
+                       blush.NewExact("TRACE", blush.Colour{
+                               Foreground: blush.RGB{R: 170, G: 170, B: 170},
+                               Background: blush.NoRGB,
+                       }),
+               },
+               Reader: ioutil.NopCloser(reader),
+       }
+
+       if _, err := io.Copy(os.Stdout, b); err != nil {
+               fmt.Println(err.Error())
        }
+
        return nil
 }
 
diff --git a/vendor/github.com/arsham/blush/LICENSE 
b/vendor/github.com/arsham/blush/LICENSE
new file mode 100644
index 0000000..03bc557
--- /dev/null
+++ b/vendor/github.com/arsham/blush/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Arsham Shirvani
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/arsham/blush/blush/blush.go 
b/vendor/github.com/arsham/blush/blush/blush.go
new file mode 100644
index 0000000..1e194a7
--- /dev/null
+++ b/vendor/github.com/arsham/blush/blush/blush.go
@@ -0,0 +1,188 @@
+package blush
+
+import (
+       "bufio"
+       "io"
+
+       "github.com/arsham/blush/internal/reader"
+)
+
+type mode int
+
+const (
+       // Separator string between name of the reader and the contents.
+       Separator = ": "
+
+       // DefaultLineCache is minimum lines to cache.
+       DefaultLineCache = 50
+
+       // DefaultCharCache is minimum characters to cache for each line. This 
is in
+       // effect only if Read() function is used.
+       DefaultCharCache = 1000
+
+       readMode mode = iota
+       writeToMode
+)
+
+// Blush reads from reader and matches against all finders. If NoCut is true,
+// any unmatched lines are printed as well. If WithFileName is true, blush will
+// write the filename before it writes the output. Read and WriteTo will return
+// ErrReadWriteMix if both Read and WriteTo are called on the same object. See
+// package docs for more details.
+type Blush struct {
+       Finders      []Finder
+       Reader       io.ReadCloser
+       LineCache    uint
+       CharCache    uint
+       NoCut        bool // do not cut out non-matched lines.
+       WithFileName bool
+       closed       bool
+       readLineCh   chan []byte
+       readCh       chan byte
+       mode         mode
+}
+
+// Read creates a goroutine on first invocation to read from the underlying
+// reader. It is considerably slower than WriteTo as it reads the bytes one by
+// one in order to produce the results, therefore you should use WriteTo
+// directly or use io.Copy() on blush.
+func (b *Blush) Read(p []byte) (n int, err error) {
+       if b.closed {
+               return 0, ErrClosed
+       }
+       if b.mode == writeToMode {
+               return 0, ErrReadWriteMix
+       }
+       if b.mode != readMode {
+               if err = b.setup(readMode); err != nil {
+                       return 0, err
+               }
+       }
+       for n = 0; n < cap(p); n++ {
+               c, ok := <-b.readCh
+               if !ok {
+                       return n, io.EOF
+               }
+               p[n] = c
+       }
+       return n, err
+}
+
+// WriteTo writes matches to w. It returns an error if the writer is nil or
+// there are not paths defined or there is no files found in the Reader.
+func (b *Blush) WriteTo(w io.Writer) (int64, error) {
+       if b.closed {
+               return 0, ErrClosed
+       }
+       if b.mode == readMode {
+               return 0, ErrReadWriteMix
+       }
+       if b.mode != writeToMode {
+               if err := b.setup(writeToMode); err != nil {
+                       return 0, err
+               }
+       }
+       var total int
+       if w == nil {
+               return 0, ErrNoWriter
+       }
+       for line := range b.readLineCh {
+               if n, err := w.Write(line); err != nil {
+                       return int64(n), err
+               }
+               total += len(line)
+       }
+       return int64(total), nil
+}
+
+func (b *Blush) setup(m mode) error {
+       if b.Reader == nil {
+               return reader.ErrNoReader
+       }
+       if len(b.Finders) < 1 {
+               return ErrNoFinder
+       }
+
+       b.mode = m
+       if b.LineCache == 0 {
+               b.LineCache = DefaultLineCache
+       }
+       if b.CharCache == 0 {
+               b.CharCache = DefaultCharCache
+       }
+       b.readLineCh = make(chan []byte, b.LineCache)
+       b.readCh = make(chan byte, b.CharCache)
+       go b.readLines()
+       if m == readMode {
+               go b.transfer()
+       }
+       return nil
+}
+
+func (b Blush) decorate(input string) (string, bool) {
+       str, ok := lookInto(b.Finders, input)
+       if ok || b.NoCut {
+               var prefix string
+               if b.WithFileName {
+                       prefix = fileName(b.Reader)
+               }
+               return prefix + str, true
+       }
+       return "", false
+}
+
+func (b Blush) readLines() {
+       var (
+               ok bool
+               sc = bufio.NewReader(b.Reader)
+       )
+       for {
+               line, err := sc.ReadString('\n')
+               if line, ok = b.decorate(line); ok {
+                       b.readLineCh <- []byte(line)
+               }
+               if err != nil {
+                       break
+               }
+       }
+       close(b.readLineCh)
+}
+
+func (b Blush) transfer() {
+       for line := range b.readLineCh {
+               for _, c := range line {
+                       b.readCh <- c
+               }
+       }
+       close(b.readCh)
+}
+
+// Close closes the reader and returns whatever error it returns.
+func (b *Blush) Close() error {
+       b.closed = true
+       return b.Reader.Close()
+}
+
+// lookInto returns a new decorated line if any of the finders decorate it, or
+// the given line as it is.
+func lookInto(f []Finder, line string) (string, bool) {
+       var found bool
+       for _, a := range f {
+               if s, ok := a.Find(line); ok {
+                       line = s
+                       found = true
+               }
+       }
+       return line, found
+}
+
+// fileName returns an empty string if it could not query the fileName from r.
+func fileName(r io.Reader) string {
+       type namer interface {
+               FileName() string
+       }
+       if o, ok := r.(namer); ok {
+               return o.FileName() + Separator
+       }
+       return ""
+}
diff --git a/vendor/github.com/arsham/blush/blush/colour.go 
b/vendor/github.com/arsham/blush/blush/colour.go
new file mode 100644
index 0000000..f2459cb
--- /dev/null
+++ b/vendor/github.com/arsham/blush/blush/colour.go
@@ -0,0 +1,196 @@
+package blush
+
+import (
+       "fmt"
+       "strconv"
+       "strings"
+)
+
+// BgLevel is the colour value of R, G, or B when the colour is shown in the
+// background.
+const BgLevel = 70
+
+// These are colour settings. NoRGB results in no colouring in the terminal.
+var (
+       NoRGB     = RGB{-1, -1, -1}
+       FgRed     = RGB{255, 0, 0}
+       FgBlue    = RGB{0, 0, 255}
+       FgGreen   = RGB{0, 255, 0}
+       FgBlack   = RGB{0, 0, 0}
+       FgWhite   = RGB{255, 255, 255}
+       FgCyan    = RGB{0, 255, 255}
+       FgMagenta = RGB{255, 0, 255}
+       FgYellow  = RGB{255, 255, 0}
+       BgRed     = RGB{BgLevel, 0, 0}
+       BgBlue    = RGB{0, 0, BgLevel}
+       BgGreen   = RGB{0, BgLevel, 0}
+       BgBlack   = RGB{0, 0, 0}
+       BgWhite   = RGB{BgLevel, BgLevel, BgLevel}
+       BgCyan    = RGB{0, BgLevel, BgLevel}
+       BgMagenta = RGB{BgLevel, 0, BgLevel}
+       BgYellow  = RGB{BgLevel, BgLevel, 0}
+)
+
+// Some stock colours. There will be no colouring when NoColour is used.
+var (
+       NoColour = Colour{NoRGB, NoRGB}
+       Red      = Colour{FgRed, NoRGB}
+       Blue     = Colour{FgBlue, NoRGB}
+       Green    = Colour{FgGreen, NoRGB}
+       Black    = Colour{FgBlack, NoRGB}
+       White    = Colour{FgWhite, NoRGB}
+       Cyan     = Colour{FgCyan, NoRGB}
+       Magenta  = Colour{FgMagenta, NoRGB}
+       Yellow   = Colour{FgYellow, NoRGB}
+)
+
+//DefaultColour is the default colour if no colour is set via arguments.
+var DefaultColour = Blue
+
+// RGB represents colours that can be printed in terminals. R, G and B should 
be
+// between 0 and 255.
+type RGB struct {
+       R, G, B int
+}
+
+// Colour is a pair of RGB colours for foreground and background.
+type Colour struct {
+       Foreground RGB
+       Background RGB
+}
+
+// Colourise wraps the input between colours.
+func Colourise(input string, c Colour) string {
+       if c.Background == NoRGB && c.Foreground == NoRGB {
+               return input
+       }
+
+       var fg, bg string
+       if c.Foreground != NoRGB {
+               fg = foreground(c.Foreground)
+       }
+       if c.Background != NoRGB {
+               bg = background(c.Background)
+       }
+       return fg + bg + input + unformat()
+}
+
+func foreground(c RGB) string {
+       return fmt.Sprintf("\033[38;5;%dm", colour(c.R, c.G, c.B))
+}
+
+func background(c RGB) string {
+       return fmt.Sprintf("\033[48;5;%dm", colour(c.R, c.G, c.B))
+}
+
+func unformat() string {
+       return "\033[0m"
+}
+
+func colour(red, green, blue int) int {
+       return 16 + baseColor(red, 36) + baseColor(green, 6) + baseColor(blue, 
1)
+}
+
+func baseColor(value int, factor int) int {
+       return int(6*float64(value)/256) * factor
+}
+
+func colorFromArg(colour string) Colour {
+       if strings.HasPrefix(colour, "#") {
+               return hexColour(colour)
+       }
+       if grouping.MatchString(colour) {
+               if c := colourGroup(colour); c != NoColour {
+                       return c
+               }
+       }
+       return stockColour(colour)
+}
+
+func colourGroup(colour string) Colour {
+       g := grouping.FindStringSubmatch(colour)
+       group, err := strconv.Atoi(g[2])
+       if err != nil {
+               return NoColour
+       }
+       c := stockColour(g[1])
+       switch group % 8 {
+       case 0:
+               c.Background = BgRed
+       case 1:
+               c.Background = BgBlue
+       case 2:
+               c.Background = BgGreen
+       case 3:
+               c.Background = BgBlack
+       case 4:
+               c.Background = BgWhite
+       case 5:
+               c.Background = BgCyan
+       case 6:
+               c.Background = BgMagenta
+       case 7:
+               c.Background = BgYellow
+       }
+       return c
+}
+
+func stockColour(colour string) Colour {
+       c := DefaultColour
+       switch colour {
+       case "r", "red":
+               c = Red
+       case "b", "blue":
+               c = Blue
+       case "g", "green":
+               c = Green
+       case "bl", "black":
+               c = Black
+       case "w", "white":
+               c = White
+       case "cy", "cyan":
+               c = Cyan
+       case "mg", "magenta":
+               c = Magenta
+       case "yl", "yellow":
+               c = Yellow
+       case "no-colour", "no-color":
+               c = NoColour
+       }
+       return c
+}
+
+func hexColour(colour string) Colour {
+       var r, g, b int
+       colour = strings.TrimPrefix(colour, "#")
+       switch len(colour) {
+       case 3:
+               c := strings.Split(colour, "")
+               r = getInt(c[0] + c[0])
+               g = getInt(c[1] + c[1])
+               b = getInt(c[2] + c[2])
+       case 6:
+               c := strings.Split(colour, "")
+               r = getInt(c[0] + c[1])
+               g = getInt(c[2] + c[3])
+               b = getInt(c[4] + c[5])
+       default:
+               return DefaultColour
+       }
+       for _, n := range []int{r, g, b} {
+               if n < 0 {
+                       return DefaultColour
+               }
+       }
+       return Colour{RGB{R: r, G: g, B: b}, NoRGB}
+}
+
+// getInt returns a number between 0-255 from a hex code. If the hex is not
+// between 00 and ff, it returns -1.
+func getInt(hex string) int {
+       d, err := strconv.ParseInt("0x"+hex, 0, 64)
+       if err != nil || d > 255 || d < 0 {
+               return -99
+       }
+       return int(d)
+}
diff --git a/vendor/github.com/arsham/blush/blush/doc.go 
b/vendor/github.com/arsham/blush/blush/doc.go
new file mode 100644
index 0000000..7bc2b0f
--- /dev/null
+++ b/vendor/github.com/arsham/blush/blush/doc.go
@@ -0,0 +1,30 @@
+// Package blush reads from a given io.Reader line by line and looks for
+// patterns.
+//
+// Blush struct has a Reader property which can be Stdin in case of it being
+// shell's pipe, or any type that implements io.ReadCloser. If NoCut is set to
+// true, it will show all lines despite being not matched. You cannot call
+// Read() and WriteTo() on the same object. Blush will return ErrReadWriteMix 
on
+// the second consequent call. The first time Read/WriteTo is called, it will
+// start a goroutine and reads up to LineCache lines from Reader. If the Read()
+// is in use, it starts a goroutine that reads up to CharCache bytes from the
+// line cache and fills up the given buffer.
+//
+// The hex number should be in 3 or 6 part format (#aaaaaa or #aaa) and each
+// part will be translated to a number value between 0 and 255 when creating 
the
+// Colour instance. If any of hex parts are not between 00 and ff, it creates
+// the DefaultColour value.
+//
+// Important Notes
+//
+// The Read() method could be slow in case of huge inspections. It is
+// recommended to avoid it and use WriteTo() instead; io.Copy() can take care 
of
+// that for you.
+//
+// When WriteTo() is called with an unavailable or un-writeable writer, there
+// will be no further checks until it tries to write into it. If the Write
+// encounters any errors regarding writes, it will return the amount if writes
+// and stops its search.
+//
+// There always will be a newline after each read.
+package blush
diff --git a/vendor/github.com/arsham/blush/blush/errors.go 
b/vendor/github.com/arsham/blush/blush/errors.go
new file mode 100644
index 0000000..da18717
--- /dev/null
+++ b/vendor/github.com/arsham/blush/blush/errors.go
@@ -0,0 +1,16 @@
+package blush
+
+import "errors"
+
+// ErrNoWriter is returned if a nil object is passed to the WriteTo method.
+var ErrNoWriter = errors.New("no writer defined")
+
+// ErrNoFinder is returned if there is no finder passed to Blush.
+var ErrNoFinder = errors.New("no finders defined")
+
+// ErrClosed is returned if the reader is closed and you try to read from it.
+var ErrClosed = errors.New("reader already closed")
+
+// ErrReadWriteMix is returned when the Read and WriteTo are called on the same
+// object.
+var ErrReadWriteMix = errors.New("you cannot mix Read and WriteTo calls")
diff --git a/vendor/github.com/arsham/blush/blush/find.go 
b/vendor/github.com/arsham/blush/blush/find.go
new file mode 100644
index 0000000..5e4af29
--- /dev/null
+++ b/vendor/github.com/arsham/blush/blush/find.go
@@ -0,0 +1,168 @@
+package blush
+
+import (
+       "fmt"
+       "regexp"
+       "strings"
+)
+
+var (
+       isRegExp = regexp.MustCompile(`[\^\$\.\{\}\[\]\*\?]`)
+       // grouping is used for matching colour groups (b1, etc.).
+       grouping = regexp.MustCompile("^([[:alpha:]]+)([[:digit:]]+)$")
+)
+
+// Finder finds texts based on a plain text or regexp logic. If it doesn't find
+// any match, it will return an empty string. It might decorate the match with 
a
+// given instruction.
+type Finder interface {
+       Find(string) (string, bool)
+}
+
+// NewLocator returns a Rx object if search is a valid regexp, otherwise it
+// returns Exact or Iexact. If insensitive is true, the match will be case
+// insensitive. The colour argument can be in short form (b) or long form
+// (blue). If it cannot find the colour, it will fall-back to DefaultColour. 
The
+// colour also can be in hex format, which should be started with a pound sign
+// (#666).
+func NewLocator(colour, search string, insensitive bool) Finder {
+       c := colorFromArg(colour)
+       if !isRegExp.Match([]byte(search)) {
+               if insensitive {
+                       return NewIexact(search, c)
+               }
+               return NewExact(search, c)
+       }
+
+       decore := fmt.Sprintf("(%s)", search)
+       if insensitive {
+               decore = fmt.Sprintf("(?i)%s", decore)
+               if o, err := regexp.Compile(decore); err == nil {
+                       return NewRx(o, c)
+               }
+               return NewIexact(search, c)
+       }
+
+       if o, err := regexp.Compile(decore); err == nil {
+               return NewRx(o, c)
+       }
+       return NewExact(search, c)
+}
+
+// Exact looks for the exact word in the string.
+type Exact struct {
+       s      string
+       colour Colour
+}
+
+// NewExact returns a new instance of the Exact.
+func NewExact(s string, c Colour) Exact {
+       return Exact{
+               s:      s,
+               colour: c,
+       }
+}
+
+// Find looks for the exact string. Any strings it finds will be decorated with
+// the given Colour.
+func (e Exact) Find(input string) (string, bool) {
+       if strings.Contains(input, e.s) {
+               return e.colourise(input, e.colour), true
+       }
+       return "", false
+}
+
+func (e Exact) colourise(input string, c Colour) string {
+       if c == NoColour {
+               return input
+       }
+       return strings.Replace(input, e.s, Colourise(e.s, c), -1)
+}
+
+// Colour returns the Colour property.
+func (e Exact) Colour() Colour {
+       return e.colour
+}
+
+// String will returned the colourised contents.
+func (e Exact) String() string {
+       return e.colourise(e.s, e.colour)
+}
+
+// Iexact is like Exact but case insensitive.
+type Iexact struct {
+       s      string
+       colour Colour
+}
+
+// NewIexact returns a new instance of the Iexact.
+func NewIexact(s string, c Colour) Iexact {
+       return Iexact{
+               s:      s,
+               colour: c,
+       }
+}
+
+// Find looks for the exact string. Any strings it finds will be decorated with
+// the given Colour.
+func (i Iexact) Find(input string) (string, bool) {
+       if strings.Contains(strings.ToLower(input), strings.ToLower(i.s)) {
+               return i.colourise(input, i.colour), true
+       }
+       return "", false
+}
+
+func (i Iexact) colourise(input string, c Colour) string {
+       if c == NoColour {
+               return input
+       }
+       index := strings.Index(strings.ToLower(input), strings.ToLower(i.s))
+       end := len(i.s) + index
+       match := input[index:end]
+       return strings.Replace(input, match, Colourise(match, c), -1)
+}
+
+// Colour returns the Colour property.
+func (i Iexact) Colour() Colour {
+       return i.colour
+}
+
+// String will returned the colourised contents.
+func (i Iexact) String() string {
+       return i.colourise(i.s, i.colour)
+}
+
+// Rx is the regexp implementation of the Locator.
+type Rx struct {
+       *regexp.Regexp
+       colour Colour
+}
+
+// NewRx returns a new instance of the Rx.
+func NewRx(r *regexp.Regexp, c Colour) Rx {
+       return Rx{
+               Regexp: r,
+               colour: c,
+       }
+}
+
+// Find looks for the string matching `r` regular expression. Any strings it
+// finds will be decorated with the given Colour.
+func (r Rx) Find(input string) (string, bool) {
+       if r.MatchString(input) {
+               return r.colourise(input, r.colour), true
+       }
+       return "", false
+}
+
+func (r Rx) colourise(input string, c Colour) string {
+       if c == NoColour {
+               return input
+       }
+       return r.ReplaceAllString(input, Colourise("$1", c))
+}
+
+// Colour returns the Colour property.
+func (r Rx) Colour() Colour {
+       return r.colour
+}
diff --git a/vendor/github.com/arsham/blush/internal/reader/reader.go 
b/vendor/github.com/arsham/blush/internal/reader/reader.go
new file mode 100644
index 0000000..9f2b258
--- /dev/null
+++ b/vendor/github.com/arsham/blush/internal/reader/reader.go
@@ -0,0 +1,150 @@
+package reader
+
+import (
+       "io"
+       "io/ioutil"
+       "os"
+
+       "github.com/arsham/blush/internal/tools"
+       "github.com/pkg/errors"
+)
+
+// ErrNoReader is returned if there is no reader defined.
+var ErrNoReader = errors.New("no input")
+
+// MultiReader holds one or more io.ReadCloser and reads their contents when
+// Read() method is called in order. The reader is loaded lazily if it is a
+// file to prevent the system going out of file descriptors.
+type MultiReader struct {
+       readers     []*container
+       currentName string
+}
+
+// NewMultiReader creates an instance of the MultiReader and passes it to all
+// input functions.
+func NewMultiReader(input ...Conf) (*MultiReader, error) {
+       m := &MultiReader{
+               readers: make([]*container, 0),
+       }
+       for _, c := range input {
+               if c == nil {
+                       return nil, ErrNoReader
+               }
+               err := c(m)
+               if err != nil {
+                       return nil, err
+               }
+       }
+       return m, nil
+}
+
+// Conf is used to configure the MultiReader.
+type Conf func(*MultiReader) error
+
+// WithReader adds the {name,r} reader to the MultiReader. If name is empty, 
the
+// key will not be written in the output. You can provide as many empty names 
as
+// you need.
+func WithReader(name string, r io.ReadCloser) Conf {
+       return func(m *MultiReader) error {
+               if r == nil {
+                       return errors.Wrap(ErrNoReader, "WithReader")
+               }
+               c := &container{
+                       get: func() (io.ReadCloser, error) {
+                               m.currentName = name
+                               return r, nil
+                       },
+               }
+               m.readers = append(m.readers, c)
+               return nil
+       }
+}
+
+// WithPaths searches through the path and adds any files it finds to the
+// MultiReader. Each path will become its reader's name in the process. It
+// returns an error if any of given files are not found. It ignores any files
+// that cannot be read or opened.
+func WithPaths(paths []string, recursive bool) Conf {
+       return func(m *MultiReader) error {
+               if paths == nil {
+                       return errors.Wrap(ErrNoReader, "WithPaths: nil paths")
+               }
+               if len(paths) == 0 {
+                       return errors.Wrap(ErrNoReader, "WithPaths: empty 
paths")
+               }
+               files, err := tools.Files(recursive, paths...)
+               if err != nil {
+                       return errors.Wrap(err, "WithPaths")
+               }
+               for _, name := range files {
+                       name := name
+                       c := &container{
+                               get: func() (io.ReadCloser, error) {
+                                       m.currentName = name
+                                       f, err := os.Open(name)
+                                       return f, err
+                               },
+                       }
+                       m.readers = append(m.readers, c)
+               }
+               return nil
+       }
+}
+
+// Read is almost the exact implementation of io.MultiReader but keeps track of
+// reader names. It closes each reader once they report they are exhausted, and
+// it will happen on the next read.
+func (m *MultiReader) Read(b []byte) (n int, err error) {
+       for len(m.readers) > 0 {
+               if len(m.readers) == 1 {
+                       if r, ok := m.readers[0].r.(*MultiReader); ok {
+                               m.readers = r.readers
+                               continue
+                       }
+               }
+               n, err = m.readers[0].Read(b)
+               if err == io.EOF {
+                       m.readers[0].r.Close()
+                       c := &container{r: ioutil.NopCloser(nil)}
+                       m.readers[0] = c
+                       m.readers = m.readers[1:]
+               }
+               if n > 0 || err != io.EOF {
+                       if err == io.EOF && len(m.readers) > 0 {
+                               err = nil
+                       }
+                       return
+               }
+       }
+       m.currentName = ""
+       return 0, io.EOF
+}
+
+// Close does nothing.
+func (m *MultiReader) Close() error { return nil }
+
+// FileName returns the current reader's name.
+func (m *MultiReader) FileName() string {
+       return m.currentName
+}
+
+// container takes care of opening the reader on demand. This is particularly
+// useful when searching in thousands of files, because we want to open them on
+// demand, otherwise the system gets out of file descriptors.
+type container struct {
+       r    io.ReadCloser
+       open bool
+       get  func() (io.ReadCloser, error)
+}
+
+func (c *container) Read(b []byte) (int, error) {
+       if !c.open {
+               var err error
+               c.r, err = c.get()
+               if err != nil {
+                       return 0, err
+               }
+               c.open = true
+       }
+       return c.r.Read(b)
+}
diff --git a/vendor/github.com/arsham/blush/internal/tools/dir.go 
b/vendor/github.com/arsham/blush/internal/tools/dir.go
new file mode 100644
index 0000000..a77a4ae
--- /dev/null
+++ b/vendor/github.com/arsham/blush/internal/tools/dir.go
@@ -0,0 +1,118 @@
+// Package tools contains common tools used throughout this application.
+package tools
+
+import (
+       "errors"
+       "io"
+       "io/ioutil"
+       "os"
+       "path"
+       "path/filepath"
+)
+
+// Files returns all files found in paths. If recursive is false, it only
+// returns the immediate files in the paths.
+func Files(recursive bool, paths ...string) ([]string, error) {
+       var (
+               fileList []string
+               fn       = files
+       )
+       if recursive {
+               fn = rfiles
+       }
+
+       for _, p := range paths {
+               f, err := fn(p)
+               if err != nil {
+                       return nil, err
+               }
+               fileList = append(fileList, f...)
+       }
+       if len(fileList) == 0 {
+               return nil, errors.New("no files found")
+       }
+       fileList = unique(fileList)
+       fileList = nonBinary(fileList)
+       return fileList, nil
+}
+
+func unique(fileList []string) []string {
+       var (
+               ret  []string
+               seen = make(map[string]struct{}, len(fileList))
+       )
+       for _, f := range fileList {
+               if _, ok := seen[f]; ok {
+                       continue
+               }
+               seen[f] = struct{}{}
+               ret = append(ret, f)
+       }
+       return ret
+}
+
+func nonBinary(fileList []string) []string {
+       var (
+               ret []string
+       )
+       for _, f := range fileList {
+               if isPlainText(f) {
+                       ret = append(ret, f)
+               }
+       }
+       return ret
+}
+
+func rfiles(location string) ([]string, error) {
+       fileList := []string{}
+       err := filepath.Walk(location, func(location string, f os.FileInfo, err 
error) error {
+               if os.IsPermission(err) {
+                       return nil
+               }
+               if err != nil {
+                       return err
+               }
+               if !f.IsDir() {
+                       fileList = append(fileList, location)
+               }
+               return nil
+       })
+       if err != nil {
+               return nil, err
+       }
+       return fileList, nil
+}
+
+func files(location string) ([]string, error) {
+       if s, err := os.Stat(location); err == nil && !s.IsDir() {
+               return []string{location}, nil
+       }
+       fileList := []string{}
+       files, err := ioutil.ReadDir(location)
+       if err != nil {
+               return nil, err
+       }
+       for _, f := range files {
+               if !f.IsDir() {
+                       p := path.Join(location, f.Name())
+                       fileList = append(fileList, p)
+               }
+       }
+       return fileList, nil
+}
+
+// TODO: we should ignore the line in search stage instead.
+func isPlainText(name string) bool {
+       f, err := os.Open(name)
+       if err != nil {
+               return false
+       }
+       defer f.Close()
+       header := make([]byte, 512)
+       _, err = f.Read(header)
+       if err != nil && err != io.EOF {
+               return false
+       }
+
+       return IsPlainText(string(header))
+}
diff --git a/vendor/github.com/arsham/blush/internal/tools/strings.go 
b/vendor/github.com/arsham/blush/internal/tools/strings.go
new file mode 100644
index 0000000..c61533b
--- /dev/null
+++ b/vendor/github.com/arsham/blush/internal/tools/strings.go
@@ -0,0 +1,20 @@
+package tools
+
+import (
+       "unicode"
+)
+
+// IsPlainText returns false if at least one of the runes in the input is not
+// represented as a plain text in a file. Null is an exception.
+func IsPlainText(input string) bool {
+       for _, r := range input {
+               switch r {
+               case 0, '\n', '\t', '\r':
+                       continue
+               }
+               if r > unicode.MaxASCII || !unicode.IsPrint(r) {
+                       return false
+               }
+       }
+       return true
+}

Reply via email to