github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/filex/filex.go (about) 1 package filex 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "io" 8 "log" 9 "os" 10 "strings" 11 12 "github.com/bingoohuang/gg/pkg/iox" 13 ) 14 15 // LinesChan read file into lines. 16 func LinesChan(filePath string, chSize int) (ch chan string, err error) { 17 f, err := os.Open(filePath) 18 if err != nil { 19 return nil, err 20 } 21 22 s := bufio.NewScanner(f) 23 s.Split(ScanLines) 24 ch = make(chan string, chSize) 25 go func() { 26 defer iox.Close(f) 27 defer close(ch) 28 29 for s.Scan() { 30 t := s.Text() 31 t = strings.TrimSpace(t) 32 if len(t) > 0 { 33 ch <- t 34 } 35 } 36 37 if err := s.Err(); err != nil { 38 log.Printf("E! scan file %s lines error: %v", filePath, err) 39 } 40 }() 41 42 return ch, nil 43 } 44 45 // ScanLines is a split function for a Scanner that returns each line of 46 // text, with end-of-line marker. The returned line may 47 // be empty. The end-of-line marker is one optional carriage return followed 48 // by one mandatory newline. In regular expression notation, it is `\r?\n`. 49 // The last non-empty line of input will be returned even if it has no 50 // newline. 51 func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error) { 52 if atEOF && len(data) == 0 { 53 return 0, nil, nil 54 } 55 if i := bytes.IndexByte(data, '\n'); i >= 0 { 56 // We have a full newline-terminated line. 57 return i + 1, data[0 : i+1], nil 58 } 59 // If we're at EOF, we have a final, non-terminated line. Return it. 60 if atEOF { 61 return len(data), data, nil 62 } 63 // Request more data. 64 return 0, nil, nil 65 } 66 67 // Lines read file into lines. 68 func Lines(filePath string) (lines []string, err error) { 69 f, err := os.Open(filePath) 70 if err != nil { 71 return nil, err 72 } 73 defer f.Close() 74 75 s := bufio.NewScanner(f) 76 for s.Scan() { 77 lines = append(lines, s.Text()) 78 } 79 80 return lines, s.Err() 81 } 82 83 // Open opens file successfully or panic. 84 func Open(f string) *os.File { 85 r, err := os.Open(f) 86 if err != nil { 87 panic(err) 88 } 89 90 return r 91 } 92 93 type AppendOptions struct { 94 BackOffset int64 95 } 96 97 type AppendOptionsFn func(*AppendOptions) 98 99 func WithBackOffset(backOffset int64) AppendOptionsFn { 100 return func(o *AppendOptions) { 101 o.BackOffset = backOffset 102 } 103 } 104 105 func Append(name string, data []byte, options ...AppendOptionsFn) (int, error) { 106 option := &AppendOptions{} 107 for _, fn := range options { 108 fn(option) 109 } 110 111 // If the file doesn't exist, create it, or append to the file 112 113 f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, 0o644) 114 if err != nil { 115 return 0, err 116 } 117 defer f.Close() 118 119 if _, err := f.Seek(option.BackOffset, io.SeekEnd); err != nil { 120 return 0, err 121 } 122 123 n, err := f.Write(data) 124 if err != nil { 125 return n, err 126 } 127 128 return n, nil 129 } 130 131 func Exists(name string) bool { 132 ok, _ := ExistsErr(name) 133 return ok 134 } 135 136 func ExistsErr(name string) (bool, error) { 137 if _, err := os.Stat(name); err == nil { 138 return true, nil 139 } else if errors.Is(err, os.ErrNotExist) { 140 return false, nil 141 } else { 142 // Schrodinger: file may or may not exist. See err for details. 143 // Therefore, do *NOT* use !os.IsNotExist(err) to test for file existence 144 return false, err 145 } 146 }