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  }