github.com/IBM/fsgo@v0.0.0-20220920202152-e16fd2119d49/iofs.go (about)

     1  // Copyright 2022 IBM Inc. All rights reserved
     2  // Copyright © 2014 Steve Francia <spf@spf13.com>
     3  //
     4  // SPDX-License-Identifier: Apache2.0
     5  package fsgo
     6  
     7  import (
     8  	"io"
     9  	"io/fs"
    10  	"os"
    11  	"path"
    12  	"sort"
    13  	"time"
    14  
    15  	"github.com/IBM/fsgo/internal/common"
    16  )
    17  
    18  // IOFS adopts fsgo.Fs to stdlib io/fs.FS
    19  type IOFS struct {
    20  	Fs
    21  }
    22  
    23  func NewIOFS(fs Fs) IOFS {
    24  	return IOFS{Fs: fs}
    25  }
    26  
    27  var (
    28  	_ fs.FS         = IOFS{}
    29  	_ fs.GlobFS     = IOFS{}
    30  	_ fs.ReadDirFS  = IOFS{}
    31  	_ fs.ReadFileFS = IOFS{}
    32  	_ fs.StatFS     = IOFS{}
    33  	_ fs.SubFS      = IOFS{}
    34  )
    35  
    36  func (iofs IOFS) Open(name string) (fs.File, error) {
    37  	const op = "open"
    38  
    39  	// by convention for fs.FS implementations we should perform this check
    40  	if !fs.ValidPath(name) {
    41  		return nil, iofs.wrapError(op, name, fs.ErrInvalid)
    42  	}
    43  
    44  	file, err := iofs.Fs.Open(name)
    45  	if err != nil {
    46  		return nil, iofs.wrapError(op, name, err)
    47  	}
    48  
    49  	// file should implement fs.ReadDirFile
    50  	if _, ok := file.(fs.ReadDirFile); !ok {
    51  		file = readDirFile{file}
    52  	}
    53  
    54  	return file, nil
    55  }
    56  
    57  func (iofs IOFS) Glob(pattern string) ([]string, error) {
    58  	const op = "glob"
    59  
    60  	// fsgo.Glob does not perform this check but it's required for implementations
    61  	if _, err := path.Match(pattern, ""); err != nil {
    62  		return nil, iofs.wrapError(op, pattern, err)
    63  	}
    64  
    65  	items, err := Glob(iofs.Fs, pattern)
    66  	if err != nil {
    67  		return nil, iofs.wrapError(op, pattern, err)
    68  	}
    69  
    70  	return items, nil
    71  }
    72  
    73  func (iofs IOFS) ReadDir(name string) ([]fs.DirEntry, error) {
    74  	f, err := iofs.Fs.Open(name)
    75  	if err != nil {
    76  		return nil, iofs.wrapError("readdir", name, err)
    77  	}
    78  
    79  	defer f.Close()
    80  
    81  	if rdf, ok := f.(fs.ReadDirFile); ok {
    82  		items, err := rdf.ReadDir(-1)
    83  		if err != nil {
    84  			return nil, iofs.wrapError("readdir", name, err)
    85  		}
    86  		sort.Slice(items, func(i, j int) bool { return items[i].Name() < items[j].Name() })
    87  		return items, nil
    88  	}
    89  
    90  	items, err := f.Readdir(-1)
    91  	if err != nil {
    92  		return nil, iofs.wrapError("readdir", name, err)
    93  	}
    94  	sort.Sort(byName(items))
    95  
    96  	ret := make([]fs.DirEntry, len(items))
    97  	for i := range items {
    98  		ret[i] = common.FileInfoDirEntry{FileInfo: items[i]}
    99  	}
   100  
   101  	return ret, nil
   102  }
   103  
   104  func (iofs IOFS) ReadFile(name string) ([]byte, error) {
   105  	const op = "readfile"
   106  
   107  	if !fs.ValidPath(name) {
   108  		return nil, iofs.wrapError(op, name, fs.ErrInvalid)
   109  	}
   110  
   111  	bytes, err := ReadFile(iofs.Fs, name)
   112  	if err != nil {
   113  		return nil, iofs.wrapError(op, name, err)
   114  	}
   115  
   116  	return bytes, nil
   117  }
   118  
   119  func (iofs IOFS) Sub(dir string) (fs.FS, error) { return IOFS{NewBasePathFs(iofs.Fs, dir)}, nil }
   120  
   121  func (IOFS) wrapError(op, path string, err error) error {
   122  	if _, ok := err.(*fs.PathError); ok {
   123  		return err // don't need to wrap again
   124  	}
   125  
   126  	return &fs.PathError{
   127  		Op:   op,
   128  		Path: path,
   129  		Err:  err,
   130  	}
   131  }
   132  
   133  // readDirFile provides adapter from fsgo.File to fs.ReadDirFile needed for correct Open
   134  type readDirFile struct {
   135  	File
   136  }
   137  
   138  var _ fs.ReadDirFile = readDirFile{}
   139  
   140  func (r readDirFile) ReadDir(n int) ([]fs.DirEntry, error) {
   141  	items, err := r.File.Readdir(n)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	ret := make([]fs.DirEntry, len(items))
   147  	for i := range items {
   148  		ret[i] = common.FileInfoDirEntry{FileInfo: items[i]}
   149  	}
   150  
   151  	return ret, nil
   152  }
   153  
   154  // FromIOFS adopts io/fs.FS to use it as fsgo.Fs
   155  // Note that io/fs.FS is read-only so all mutating methods will return fs.PathError with fs.ErrPermission
   156  // To store modifications you may use fsgo.CopyOnWriteFs
   157  type FromIOFS struct {
   158  	fs.FS
   159  }
   160  
   161  var _ Fs = FromIOFS{}
   162  
   163  func (f FromIOFS) Create(name string) (File, error) { return nil, notImplemented("create", name) }
   164  
   165  func (f FromIOFS) Mkdir(name string, perm os.FileMode) error { return notImplemented("mkdir", name) }
   166  
   167  func (f FromIOFS) MkdirAll(path string, perm os.FileMode) error {
   168  	return notImplemented("mkdirall", path)
   169  }
   170  
   171  func (f FromIOFS) Open(name string) (File, error) {
   172  	file, err := f.FS.Open(name)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	return fromIOFSFile{File: file, name: name}, nil
   178  }
   179  
   180  func (f FromIOFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
   181  	return f.Open(name)
   182  }
   183  
   184  func (f FromIOFS) Remove(name string) error {
   185  	return notImplemented("remove", name)
   186  }
   187  
   188  func (f FromIOFS) RemoveAll(path string) error {
   189  	return notImplemented("removeall", path)
   190  }
   191  
   192  func (f FromIOFS) Rename(oldname, newname string) error {
   193  	return notImplemented("rename", oldname)
   194  }
   195  
   196  func (f FromIOFS) Stat(name string) (os.FileInfo, error) { return fs.Stat(f.FS, name) }
   197  
   198  func (f FromIOFS) Name() string { return "fromiofs" }
   199  
   200  func (f FromIOFS) Chmod(name string, mode os.FileMode) error {
   201  	return notImplemented("chmod", name)
   202  }
   203  
   204  func (f FromIOFS) Chown(name string, uid, gid int) error {
   205  	return notImplemented("chown", name)
   206  }
   207  
   208  func (f FromIOFS) Chtimes(name string, atime time.Time, mtime time.Time) error {
   209  	return notImplemented("chtimes", name)
   210  }
   211  
   212  type fromIOFSFile struct {
   213  	fs.File
   214  	name string
   215  }
   216  
   217  func (f fromIOFSFile) ReadAt(p []byte, off int64) (n int, err error) {
   218  	readerAt, ok := f.File.(io.ReaderAt)
   219  	if !ok {
   220  		return -1, notImplemented("readat", f.name)
   221  	}
   222  
   223  	return readerAt.ReadAt(p, off)
   224  }
   225  
   226  func (f fromIOFSFile) Seek(offset int64, whence int) (int64, error) {
   227  	seeker, ok := f.File.(io.Seeker)
   228  	if !ok {
   229  		return -1, notImplemented("seek", f.name)
   230  	}
   231  
   232  	return seeker.Seek(offset, whence)
   233  }
   234  
   235  func (f fromIOFSFile) Write(p []byte) (n int, err error) {
   236  	return -1, notImplemented("write", f.name)
   237  }
   238  
   239  func (f fromIOFSFile) WriteAt(p []byte, off int64) (n int, err error) {
   240  	return -1, notImplemented("writeat", f.name)
   241  }
   242  
   243  func (f fromIOFSFile) Name() string { return f.name }
   244  
   245  func (f fromIOFSFile) Readdir(count int) ([]os.FileInfo, error) {
   246  	rdfile, ok := f.File.(fs.ReadDirFile)
   247  	if !ok {
   248  		return nil, notImplemented("readdir", f.name)
   249  	}
   250  
   251  	entries, err := rdfile.ReadDir(count)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	ret := make([]os.FileInfo, len(entries))
   257  	for i := range entries {
   258  		ret[i], err = entries[i].Info()
   259  
   260  		if err != nil {
   261  			return nil, err
   262  		}
   263  	}
   264  
   265  	return ret, nil
   266  }
   267  
   268  func (f fromIOFSFile) Readdirnames(n int) ([]string, error) {
   269  	rdfile, ok := f.File.(fs.ReadDirFile)
   270  	if !ok {
   271  		return nil, notImplemented("readdir", f.name)
   272  	}
   273  
   274  	entries, err := rdfile.ReadDir(n)
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  
   279  	ret := make([]string, len(entries))
   280  	for i := range entries {
   281  		ret[i] = entries[i].Name()
   282  	}
   283  
   284  	return ret, nil
   285  }
   286  
   287  func (f fromIOFSFile) Sync() error { return nil }
   288  
   289  func (f fromIOFSFile) Truncate(size int64) error {
   290  	return notImplemented("truncate", f.name)
   291  }
   292  
   293  func (f fromIOFSFile) WriteString(s string) (ret int, err error) {
   294  	return -1, notImplemented("writestring", f.name)
   295  }
   296  
   297  func notImplemented(op, path string) error {
   298  	return &fs.PathError{Op: op, Path: path, Err: fs.ErrPermission}
   299  }