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 +}
