package txtar
import (
)
func ( *Archive) (fs.FS, error) {
:= &node{fileinfo: fileinfo{path: ".", mode: readOnlyDir}}
:= &filesystem{, map[string]*node{.path: }}
if := initFiles(); != nil {
return nil, fmt.Errorf("cannot create fs.FS from txtar.Archive: %s", )
}
return , nil
}
const (
readOnly fs.FileMode = 0o444
readOnlyDir = readOnly | fs.ModeDir
)
var ErrModified error = errors.New("txtar.Archive has been modified during txtar.FS")
type filesystem struct {
ar *Archive
nodes map[string]*node
}
type node struct {
fileinfo
idx int
entries []fs.DirEntry
}
var _ fs.FS = (*filesystem)(nil)
var _ fs.DirEntry = (*node)(nil)
func ( *filesystem) error {
for , := range .ar.Files {
:= .Name
if !fs.ValidPath() {
return fmt.Errorf("file %q is an invalid path", )
}
:= &node{idx: , fileinfo: fileinfo{path: , size: len(.Data), mode: readOnly}}
if := insert(, ); != nil {
return
}
}
return nil
}
func ( *filesystem, *node) error {
if := .nodes[.path]; != nil {
return fmt.Errorf("duplicate path %q", .path)
}
.nodes[.path] =
, := directory(, path.Dir(.path))
if != nil {
return
}
.entries = append(.entries, )
return nil
}
func ( *filesystem, string) (*node, error) {
if := .nodes[]; != nil && .IsDir() {
return , nil
}
:= &node{fileinfo: fileinfo{path: , mode: readOnlyDir}}
if := insert(, ); != nil {
return nil,
}
return , nil
}
func ( *filesystem, *node) ([]byte, error) {
if .idx >= len(.ar.Files) {
return nil, ErrModified
}
:= .ar.Files[.idx]
if .Name != .path || len(.Data) != .size {
return nil, ErrModified
}
return .Data, nil
}
func ( *filesystem) ( string) (fs.File, error) {
if !fs.ValidPath() {
return nil, &fs.PathError{Op: "open", Path: , Err: fs.ErrInvalid}
}
:= .nodes[]
switch {
case == nil:
return nil, &fs.PathError{Op: "open", Path: , Err: fs.ErrNotExist}
case .IsDir():
return &openDir{fileinfo: .fileinfo, entries: .entries}, nil
default:
, := dataOf(, )
if != nil {
return nil,
}
return &openFile{fileinfo: .fileinfo, data: }, nil
}
}
func ( *filesystem) ( string) ([]byte, error) {
, := .Open()
if != nil {
return nil,
}
if , := .(*openFile); {
return slices.Clone(.data), nil
}
return nil, &fs.PathError{Op: "read", Path: , Err: fs.ErrInvalid}
}
type fileinfo struct {
path string
size int
mode fs.FileMode
}
var _ fs.FileInfo = (*fileinfo)(nil)
var _ fs.DirEntry = (*fileinfo)(nil)
func ( *fileinfo) () string { return path.Base(.path) }
func ( *fileinfo) () int64 { return int64(.size) }
func ( *fileinfo) () fs.FileMode { return .mode }
func ( *fileinfo) () fs.FileMode { return .mode.Type() }
func ( *fileinfo) () time.Time { return time.Time{} }
func ( *fileinfo) () bool { return .mode&fs.ModeDir != 0 }
func ( *fileinfo) () any { return nil }
func ( *fileinfo) () (fs.FileInfo, error) { return , nil }
type openFile struct {
fileinfo
data []byte
offset int64
}
var _ fs.File = (*openFile)(nil)
func ( *openFile) () (fs.FileInfo, error) { return &.fileinfo, nil }
func ( *openFile) () error { return nil }
func ( *openFile) ( []byte) (int, error) {
if .offset >= int64(len(.data)) {
return 0, io.EOF
}
if .offset < 0 {
return 0, &fs.PathError{Op: "read", Path: .path, Err: fs.ErrInvalid}
}
:= copy(, .data[.offset:])
.offset += int64()
return , nil
}
func ( *openFile) ( int64, int) (int64, error) {
switch {
case 0:
case 1:
+= .offset
case 2:
+= int64(len(.data))
}
if < 0 || > int64(len(.data)) {
return 0, &fs.PathError{Op: "seek", Path: .path, Err: fs.ErrInvalid}
}
.offset =
return , nil
}
func ( *openFile) ( []byte, int64) (int, error) {
if < 0 || > int64(len(.data)) {
return 0, &fs.PathError{Op: "read", Path: .path, Err: fs.ErrInvalid}
}
:= copy(, .data[:])
if < len() {
return , io.EOF
}
return , nil
}
type openDir struct {
fileinfo
entries []fs.DirEntry
offset int
}
var _ fs.ReadDirFile = (*openDir)(nil)
func ( *openDir) () (fs.FileInfo, error) { return &.fileinfo, nil }
func ( *openDir) () error { return nil }
func ( *openDir) ( []byte) (int, error) {
return 0, &fs.PathError{Op: "read", Path: .path, Err: fs.ErrInvalid}
}
func ( *openDir) ( int) ([]fs.DirEntry, error) {
:= len(.entries) - .offset
if == 0 && > 0 {
return nil, io.EOF
}
if > 0 && > {
=
}
:= make([]fs.DirEntry, )
copy(, .entries[.offset:.offset+])
.offset +=
return , nil
}