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 }