github.com/mitranim/gg@v0.1.17/io.go (about) 1 package gg 2 3 import ( 4 "bytes" 5 "io" 6 "io/fs" 7 "os" 8 r "reflect" 9 "strings" 10 ) 11 12 /* 13 Creates a read-closer able to read from the given string or byte slice. Similar 14 to the following, but shorter and avoids allocation in case of bytes-to-string 15 or string-to-bytes conversion: 16 17 gg.NewReadCloser(string(`some_data`)) 18 gg.NewReadCloser([]byte(`some_data`)) 19 io.NopCloser(strings.NewReader(string(`some_data`))) 20 io.NopCloser(bytes.NewReader([]byte(`some_data`))) 21 */ 22 func NewReadCloser[A Text](val A) io.ReadCloser { 23 if Kind[A]() == r.String { 24 return new(StringReadCloser).Reset(ToString(val)) 25 } 26 return new(BytesReadCloser).Reset(ToBytes(val)) 27 } 28 29 // Variant of `strings.Reader` that also implements nop `io.Closer`. 30 type StringReadCloser struct{ strings.Reader } 31 32 // Calls `(*strings.Reader).Reset`. 33 func (self *StringReadCloser) Reset(src string) *StringReadCloser { 34 self.Reader.Reset(src) 35 return self 36 } 37 38 // Implement `io.Closer`. This is a nop. The error is always nil. 39 func (*StringReadCloser) Close() error { return nil } 40 41 // Variant of `bytes.Reader` that also implements nop `io.Closer`. 42 type BytesReadCloser struct{ bytes.Reader } 43 44 // Calls `(*bytes.Reader).Reset`. 45 func (self *BytesReadCloser) Reset(src []byte) *BytesReadCloser { 46 self.Reader.Reset(src) 47 return self 48 } 49 50 // Implement `io.Closer`. This is a nop. The error is always nil. 51 func (*BytesReadCloser) Close() error { return nil } 52 53 /* 54 Same as `io.ReadAll` but with different error handling. 55 If reader is nil, returns nil. Panics on errors. 56 */ 57 func ReadAll(src io.Reader) []byte { 58 if src == nil { 59 return nil 60 } 61 return Try1(io.ReadAll(src)) 62 } 63 64 /* 65 Variant of `ReadAll` that closes the provided reader when done. 66 If reader is nil, returns nil. Panics on errors. 67 */ 68 func ReadCloseAll(src io.ReadCloser) []byte { 69 if src == nil { 70 return nil 71 } 72 defer src.Close() 73 return Try1(io.ReadAll(src)) 74 } 75 76 /* 77 Shortcut for using `os.Stat` to check if there is an existing file or directory 78 at the given path. 79 */ 80 func PathExists(path string) bool { 81 info := fileInfo(path) 82 return info != nil 83 } 84 85 /* 86 Shortcut for using `os.Stat` to check if there is an existing directory at the 87 given path. 88 */ 89 func DirExists(path string) bool { 90 info := fileInfo(path) 91 return info != nil && info.IsDir() 92 } 93 94 /* 95 Shortcut for using `os.Stat` to check if the file at the given path exists, 96 and is not a directory. 97 */ 98 func FileExists(path string) bool { 99 info := fileInfo(path) 100 return info != nil && !info.IsDir() 101 } 102 103 func fileInfo(path string) os.FileInfo { 104 if path == `` { 105 return nil 106 } 107 info, _ := os.Stat(path) 108 return info 109 } 110 111 // Shortcut for `os.ReadDir`. Panics on error. 112 func ReadDir(path string) []fs.DirEntry { return Try1(os.ReadDir(path)) } 113 114 /* 115 Shortcut for `os.ReadFile`. Panics on error. Converts the content to the 116 requested text type without an additional allocation. 117 */ 118 func ReadFile[A Text](path string) A { 119 return ToText[A](Try1(os.ReadFile(path))) 120 } 121 122 /* 123 Shortcut for `os.WriteFile` with default permissions `os.ModePerm`. Panics on 124 error. Takes an arbitrary text type conforming to `Text` and converts it to 125 bytes without an additional allocation. 126 */ 127 func WriteFile[A Text](path string, body A) { 128 Try(os.WriteFile(path, ToBytes(body), os.ModePerm)) 129 } 130 131 /* 132 Fully reads the given stream via `io.ReadAll` and returns two "forks". If 133 reading fails, panics. If the input is nil, both outputs are nil. 134 */ 135 func ForkReader(src io.Reader) (_, _ io.Reader) { 136 if src == nil { 137 return nil, nil 138 } 139 140 defer Detail(`failed to read for forking`) 141 text := ReadAll(src) 142 return NewReadCloser(text), NewReadCloser(text) 143 } 144 145 /* 146 Fully reads the given stream via `io.ReadAll`, closing it at the end, and 147 returns two "forks". Used internally by `(*gh.Req).CloneBody` and 148 `(*gh.Res).CloneBody`. If reading fails, panics. If the input is nil, both 149 outputs are nil. 150 */ 151 func ForkReadCloser(src io.ReadCloser) (_, _ io.ReadCloser) { 152 if src == nil { 153 return nil, nil 154 } 155 156 defer Detail(`failed to read for forking`) 157 text := ReadCloseAll(src) 158 return NewReadCloser(text), NewReadCloser(text) 159 } 160 161 // Shortcut for `os.Getwd` that panics on error. 162 func Cwd() string { return Try1(os.Getwd()) } 163 164 // If the given closer is non-nil, closes it. Panics on error. 165 func Close(val io.Closer) { 166 if val != nil { 167 Try(val.Close()) 168 } 169 } 170 171 // Shortcut for `os.MkdirAll` with `os.ModePerm`. Panics on error. 172 func MkdirAll(path string) { Try(os.MkdirAll(path, os.ModePerm)) } 173 174 // Shortcut for `os.Stat` that panics on error. 175 func Stat(path string) fs.FileInfo { return Try1(os.Stat(path)) } 176 177 /* 178 Shortcut for writing the given text to the given `io.Writer`. 179 Automatically converts text to bytes and panics on errors. 180 */ 181 func Write[Out io.Writer, Src Text](out Out, src Src) { 182 Try1(out.Write(ToBytes(src))) 183 } 184 185 // https://en.wikipedia.org/wiki/ANSI_escape_code 186 const ( 187 // Standard terminal escape sequence. Same as "\x1b" or "\033". 188 TermEsc = string(rune(27)) 189 190 // Control Sequence Introducer. Used for other codes. 191 TermEscCsi = TermEsc + `[` 192 193 // Update cursor position to first row, first column. 194 TermEscCup = TermEscCsi + `1;1H` 195 196 // Supposed to clear the screen without clearing the scrollback, aka soft 197 // clear. Seems insufficient on its own, at least in some terminals. 198 TermEscErase2 = TermEscCsi + `2J` 199 200 // Supposed to clear the screen and the scrollback, aka hard clear. Seems 201 // insufficient on its own, at least in some terminals. 202 TermEscErase3 = TermEscCsi + `3J` 203 204 // Supposed to reset the terminal to initial state, aka super hard clear. 205 // Seems insufficient on its own, at least in some terminals. 206 TermEscReset = TermEsc + `c` 207 208 // Clear screen without clearing scrollback. 209 TermEscClearSoft = TermEscCup + TermEscErase2 210 211 // Clear screen AND scrollback. 212 TermEscClearHard = TermEscCup + TermEscReset + TermEscErase3 213 ) 214 215 /* 216 Prints `TermEscClearSoft` to `os.Stdout`, causing the current TTY to clear the 217 screen but not the scrollback, pushing existing content out of view. 218 */ 219 func TermClearSoft() { _, _ = io.WriteString(os.Stdout, TermEscClearSoft) } 220 221 /* 222 Prints `TermEscClearHard` to `os.Stdout`, clearing the current TTY completely 223 (both screen and scrollback). 224 */ 225 func TermClearHard() { _, _ = io.WriteString(os.Stdout, TermEscClearHard) }