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