github.com/openebs/api@v1.12.0/pkg/util/formatters.go (about)

     1  // Copyright © 2020 The OpenEBS Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package util
    16  
    17  import (
    18  	"bytes"
    19  	"io"
    20  	"time"
    21  
    22  	"github.com/ryanuber/columnize"
    23  )
    24  
    25  // FormatKV takes a set of strings and formats them into properly
    26  // aligned k = v pairs using the columnize library.
    27  func FormatKV(in []string) string {
    28  	columnConf := columnize.DefaultConfig()
    29  	columnConf.Empty = "<none>"
    30  	columnConf.Glue = " = "
    31  	return columnize.Format(in, columnConf)
    32  }
    33  
    34  // FormatList takes a set of strings and formats them into properly
    35  // aligned output, replacing any blank fields with a placeholder
    36  // for awk-ability.
    37  func FormatList(in []string) string {
    38  	columnConf := columnize.DefaultConfig()
    39  	columnConf.Empty = "<none>"
    40  	return columnize.Format(in, columnConf)
    41  }
    42  
    43  // FormatListWithSpaces takes a set of strings and formats them into properly
    44  // aligned output. It should be used sparingly since it doesn't replace empty
    45  // values and hence not awk/sed friendly
    46  func FormatListWithSpaces(in []string) string {
    47  	columnConf := columnize.DefaultConfig()
    48  	return columnize.Format(in, columnConf)
    49  }
    50  
    51  // Limits the length of the string.
    52  func limit(s string, length int) string {
    53  	if len(s) < length {
    54  		return s
    55  	}
    56  
    57  	return s[:length]
    58  }
    59  
    60  // FormatTime formats the time to string based on RFC822
    61  func FormatTime(t time.Time) string {
    62  	return t.Format("01/02/06 15:04:05 MST")
    63  }
    64  
    65  // FormatUnixNanoTime is a helper for formatting time for output.
    66  func FormatUnixNanoTime(nano int64) string {
    67  	t := time.Unix(0, nano)
    68  	return FormatTime(t)
    69  }
    70  
    71  // FormatTimeDifference takes two times and determines their duration difference
    72  // truncating to a passed unit.
    73  // E.g. formatTimeDifference(first=1m22s33ms, second=1m28s55ms, time.Second) -> 6s
    74  func FormatTimeDifference(first, second time.Time, d time.Duration) string {
    75  	return second.Truncate(d).Sub(first.Truncate(d)).String()
    76  }
    77  
    78  // LineLimitReader wraps another reader and provides `tail -n` like behavior.
    79  // LineLimitReader buffers up to the searchLimit and returns `-n` number of
    80  // lines. After those lines have been returned, LineLimitReader streams the
    81  // underlying ReadCloser
    82  type LineLimitReader struct {
    83  	io.ReadCloser
    84  	lines       int
    85  	searchLimit int
    86  
    87  	timeLimit time.Duration
    88  	lastRead  time.Time
    89  
    90  	buffer     *bytes.Buffer
    91  	bufFiled   bool
    92  	foundLines bool
    93  }
    94  
    95  // NewLineLimitReader takes the ReadCloser to wrap, the number of lines to find
    96  // searching backwards in the first searchLimit bytes. timeLimit can optionally
    97  // be specified by passing a non-zero duration. When set, the search for the
    98  // last n lines is aborted if no data has been read in the duration. This
    99  // can be used to flush what is had if no extra data is being received. When
   100  // used, the underlying reader must not block forever and must periodically
   101  // unblock even when no data has been read.
   102  func NewLineLimitReader(r io.ReadCloser, lines, searchLimit int, timeLimit time.Duration) *LineLimitReader {
   103  	return &LineLimitReader{
   104  		ReadCloser:  r,
   105  		searchLimit: searchLimit,
   106  		timeLimit:   timeLimit,
   107  		lines:       lines,
   108  		buffer:      bytes.NewBuffer(make([]byte, 0, searchLimit)),
   109  	}
   110  }
   111  
   112  func (l *LineLimitReader) Read(p []byte) (n int, err error) {
   113  	// Fill up the buffer so we can find the correct number of lines.
   114  	if !l.bufFiled {
   115  		b := make([]byte, len(p))
   116  		n, err := l.ReadCloser.Read(b)
   117  		if n > 0 {
   118  			if _, err := l.buffer.Write(b[:n]); err != nil {
   119  				return 0, err
   120  			}
   121  		}
   122  
   123  		if err != nil {
   124  			if err != io.EOF {
   125  				return 0, err
   126  			}
   127  
   128  			l.bufFiled = true
   129  			goto READ
   130  		}
   131  
   132  		if l.buffer.Len() >= l.searchLimit {
   133  			l.bufFiled = true
   134  			goto READ
   135  		}
   136  
   137  		if l.timeLimit.Nanoseconds() > 0 {
   138  			if l.lastRead.IsZero() {
   139  				l.lastRead = time.Now()
   140  				return 0, nil
   141  			}
   142  
   143  			now := time.Now()
   144  			if n == 0 {
   145  				// We hit the limit
   146  				if l.lastRead.Add(l.timeLimit).Before(now) {
   147  					l.bufFiled = true
   148  					goto READ
   149  				} else {
   150  					return 0, nil
   151  				}
   152  			} else {
   153  				l.lastRead = now
   154  			}
   155  		}
   156  
   157  		return 0, nil
   158  	}
   159  
   160  READ:
   161  	if l.bufFiled && l.buffer.Len() != 0 {
   162  		b := l.buffer.Bytes()
   163  
   164  		// Find the lines
   165  		if !l.foundLines {
   166  			found := 0
   167  			i := len(b) - 1
   168  			sep := byte('\n')
   169  			lastIndex := len(b) - 1
   170  			for ; found < l.lines && i >= 0; i-- {
   171  				if b[i] == sep {
   172  					lastIndex = i
   173  
   174  					// Skip the first one
   175  					if i != len(b)-1 {
   176  						found++
   177  					}
   178  				}
   179  			}
   180  
   181  			// We found them all
   182  			if found == l.lines {
   183  				// Clear the buffer until the last index
   184  				l.buffer.Next(lastIndex + 1)
   185  			}
   186  
   187  			l.foundLines = true
   188  		}
   189  
   190  		// Read from the buffer
   191  		n := copy(p, l.buffer.Next(len(p)))
   192  		return n, nil
   193  	}
   194  
   195  	// Just stream from the underlying reader now
   196  	return l.ReadCloser.Read(p)
   197  }