testmark-go

filesystem.go

package testmark

import (
	"io/fs"
	"path"
	"strings"
	"time"
)

// FS returns an fs.FS implementation that provides filesystem-like access
// to the testmark document structure.
func (d *Document) FS() fs.FS {
	return &testmarkFS{
		doc:  d,
		root: d.Root(),
	}
}

// testmarkFS implements fs.FS using testmark document structure
type testmarkFS struct {
	doc  *Document
	root *DirEntry
}

// Open implements fs.FS.Open
func (tfs *testmarkFS) Open(name string) (fs.File, error) {
	if !fs.ValidPath(name) {
		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
	}

	entry := tfs.findEntry(name)
	if entry == nil {
		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
	}

	return &testmarkFile{
		entry: entry,
		name:  name,
	}, nil
}

// findEntry locates a DirEntry by path
func (tfs *testmarkFS) findEntry(name string) *DirEntry {
	if name == "." {
		return tfs.root
	}

	parts := strings.Split(name, "/")
	current := tfs.root

	for _, part := range parts {
		if part == "" || part == "." {
			continue
		}
		child := current.Child(part)
		if child == nil {
			return nil
		}
		current = child
	}

	return current
}

// testmarkFile implements fs.File
type testmarkFile struct {
	entry  *DirEntry
	name   string
	offset int64
}

// Stat implements fs.File.Stat
func (tf *testmarkFile) Stat() (fs.FileInfo, error) {
	return &testmarkFileInfo{
		entry: tf.entry,
		name:  path.Base(tf.name),
	}, nil
}

// Read implements fs.File.Read
func (tf *testmarkFile) Read(b []byte) (int, error) {
	if tf.entry.hunk == nil {
		// Directory - cannot read
		return 0, &fs.PathError{Op: "read", Path: tf.name, Err: fs.ErrInvalid}
	}

	body := tf.entry.hunk.Body()
	if tf.offset >= int64(len(body)) {
		return 0, fs.ErrClosed
	}

	n := copy(b, body[tf.offset:])
	tf.offset += int64(n)
	return n, nil
}

// Close implements fs.File.Close
func (tf *testmarkFile) Close() error {
	return nil
}

// ReadDir implements fs.ReadDirFile.ReadDir
func (tf *testmarkFile) ReadDir(n int) ([]fs.DirEntry, error) {
	if !tf.entry.IsDir() {
		return nil, &fs.PathError{Op: "readdir", Path: tf.name, Err: fs.ErrInvalid}
	}

	var entries []fs.DirEntry
	count := 0
	for child := range tf.entry.Children() {
		if n >= 0 && count >= n {
			break
		}
		entries = append(entries, &testmarkDirEntry{entry: child})
		count++
	}

	return entries, nil
}

// testmarkFileInfo implements fs.FileInfo
type testmarkFileInfo struct {
	entry *DirEntry
	name  string
}

// Name implements fs.FileInfo.Name
func (tfi *testmarkFileInfo) Name() string {
	return tfi.name
}

// Size implements fs.FileInfo.Size
func (tfi *testmarkFileInfo) Size() int64 {
	if tfi.entry.hunk == nil {
		return 0 // Directory size
	}
	return int64(len(tfi.entry.hunk.Body()))
}

// Mode implements fs.FileInfo.Mode
func (tfi *testmarkFileInfo) Mode() fs.FileMode {
	if tfi.entry.IsDir() {
		return fs.ModeDir | 0755
	}
	return 0644
}

// ModTime implements fs.FileInfo.ModTime
func (tfi *testmarkFileInfo) ModTime() time.Time {
	return time.Time{} // No modification time available
}

// IsDir implements fs.FileInfo.IsDir
func (tfi *testmarkFileInfo) IsDir() bool {
	return tfi.entry.IsDir()
}

// Sys implements fs.FileInfo.Sys
func (tfi *testmarkFileInfo) Sys() any {
	return tfi.entry
}

// testmarkDirEntry implements fs.DirEntry
type testmarkDirEntry struct {
	entry *DirEntry
}

// Name implements fs.DirEntry.Name
func (tde *testmarkDirEntry) Name() string {
	return tde.entry.Name()
}

// IsDir implements fs.DirEntry.IsDir
func (tde *testmarkDirEntry) IsDir() bool {
	return tde.entry.IsDir()
}

// Type implements fs.DirEntry.Type
func (tde *testmarkDirEntry) Type() fs.FileMode {
	if tde.entry.IsDir() {
		return fs.ModeDir
	}
	return 0
}

// Info implements fs.DirEntry.Info
func (tde *testmarkDirEntry) Info() (fs.FileInfo, error) {
	return &testmarkFileInfo{
		entry: tde.entry,
		name:  tde.entry.Name(),
	}, nil
}

v2/filesystem.go · 3.8 KiB · b9084569f7