github.com/aretext/aretext@v1.3.0/file/load.go (about) 1 package file 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "path/filepath" 8 "time" 9 10 "github.com/aretext/aretext/text" 11 ) 12 13 // Load reads a file from disk and starts a watcher to detect changes. 14 // This will remove the POSIX end-of-file indicator (line feed at end of file). 15 func Load(path string, watcherPollInterval time.Duration) (*text.Tree, *Watcher, error) { 16 path, err := filepath.Abs(path) 17 if err != nil { 18 return nil, nil, fmt.Errorf("filepath.Abs: %w", err) 19 } 20 21 f, err := os.Open(path) 22 if err != nil { 23 return nil, nil, fmt.Errorf("os.Open: %w", err) 24 } 25 defer f.Close() 26 27 lastModifiedTime, size, err := lastModifiedTimeAndSize(f) 28 if err != nil { 29 return nil, nil, fmt.Errorf("lastModifiedTime: %w", err) 30 } 31 32 tree, checksum, err := readContentsAndChecksum(f) 33 if err != nil { 34 return nil, nil, fmt.Errorf("readContentsAndChecksum: %w", err) 35 } 36 37 // POSIX files end with a single line feed to indicate the end of the file. 38 // We remove it from the tree to simplify editor operations; we'll add it back when saving the file. 39 removePosixEof(tree) 40 41 watcher := NewWatcherForExistingFile(watcherPollInterval, path, lastModifiedTime, size, checksum) 42 43 return tree, watcher, nil 44 } 45 46 func readContentsAndChecksum(f *os.File) (*text.Tree, string, error) { 47 checksummer := NewChecksummer() 48 r := io.TeeReader(f, checksummer) 49 tree, err := text.NewTreeFromReader(r) 50 if err != nil { 51 return nil, "", fmt.Errorf("text.NewTreeFromReader: %w", err) 52 } 53 return tree, checksummer.Checksum(), nil 54 } 55 56 func lastModifiedTimeAndSize(f *os.File) (time.Time, int64, error) { 57 fileInfo, err := f.Stat() 58 if err != nil { 59 return time.Time{}, 0, fmt.Errorf("f.Stat: %w", err) 60 } 61 62 return fileInfo.ModTime(), fileInfo.Size(), nil 63 } 64 65 func removePosixEof(tree *text.Tree) { 66 if endsWithLineFeed(tree) { 67 lastPos := tree.NumChars() - 1 68 tree.DeleteAtPosition(lastPos) 69 } 70 } 71 72 func endsWithLineFeed(tree *text.Tree) bool { 73 reader := tree.ReverseReaderAtPosition(tree.NumChars()) 74 var buf [1]byte 75 if n, err := reader.Read(buf[:]); err != nil || n == 0 { 76 return false 77 } 78 return buf[0] == '\n' 79 }