Documentation
¶
Overview ¶
Package fs provides a filesystem abstraction for local and remote filesystems.
Package fs is inspired by both io/fs and os. It follows io/fs's philosophy of minimal core interfaces with optional capabilities, but extends it with write operations and requires context.Context for all operations. The core FS interface requires only a single method, Open, which returns a standard io.ReadCloser. All other capabilities are optional and discovered through type assertions.
Every operation accepts a context.Context as the first parameter, enabling cancellation of long-running transfers, timeouts for remote operations, and request-scoped values like file modes. This is critical for network filesystems (SSH, S3, WebDAV) but local filesystem implementations may ignore context while still accepting it for interface compatibility.
Example with timeout:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() data, err := fs.ReadFile(ctx, remoteFS, "large-file.dat")
Path Handling ¶
Path operations are primarily lexical, handled by the lesiw.io/fs/path subpackage. This package auto-detects path styles (Unix, Windows, URLs) and applies appropriate rules without accessing the filesystem.
import "lesiw.io/fs/path"
path.Join("foo", "bar") // "foo/bar" (Unix-style)
path.Join("C:\\", "Users", "foo") // "C:\Users\foo" (Windows-style)
path.Join("https://example.com", "api") // "https://example.com/api"
For the rare cases requiring OS-specific path resolution, use Localize:
// Converts "dir/file.txt" to "dir\file.txt" on Windows resolved, err := fs.Localize(ctx, fsys, "dir/file.txt")
Operations like Open and Create automatically call Localize when the filesystem implements LocalizeFS, so explicit calls are rarely needed.
A trailing slash indicates a directory path. Create("foo") creates a file named "foo" and opens it for writing, while Create("foo/") creates a directory named "foo" and opens it for writing as a tar stream. This convention applies to Open, Create, Append, and Truncate.
Virtual Directories ¶
Some filesystems use virtual directories that don't physically exist. Object stores like S3 commonly use this pattern, treating paths as object keys rather than traditional directories.
For these filesystems, implementations can automatically create parent directories when writing files. This enables writing to nested paths without explicit directory creation:
// Automatically creates "logs/2025/" if needed ctx = fs.WithDirMode(ctx, 0755) fs.WriteFile(ctx, fsys, "logs/2025/app.log", data)
Object stores typically don't need this feature because they can create files at arbitrarily nested paths without directory creation. However, the virtual directory pattern works across both traditional filesystems and object stores, simplifying code portability.
Optional Interfaces ¶
The core FS interface is read-only, requiring only Open which returns io.ReadCloser. Write operations and other capabilities are provided through optional interfaces. Unlike io/fs which defines a custom File type, this package uses standard Go interfaces like io.ReadCloser and io.WriteCloser, maximizing composability with existing code. Files may also implement io.Seeker, io.ReaderAt, io.WriterAt, or other standard interfaces depending on filesystem capabilities.
Optional write and metadata interfaces:
- AbsFS - Absolute path resolution
- AppendDirFS - Write tar streams to directories
- AppendFS - Append to existing files
- ChmodFS - Change file permissions
- ChownFS - Change file ownership
- ChtimesFS - Change file timestamps
- CreateFS - Create or truncate files for writing
- DirFS - Read directories as tar streams
- GlobFS - Pattern-based file matching
- LocalizeFS - OS-specific path formatting
- MkdirFS - Create directories
- ReadDirFS - List directory contents
- ReadLinkFS - Read symlink targets and stat without following
- RemoveAllFS - Recursively delete directories
- RemoveFS - Delete files and empty directories
- RenameFS - Move or rename files
- StatFS - Query file metadata
- SymlinkFS - Create symbolic links
- TempDirFS - Native temporary directory support
- TempFS - Native temporary file support
- TruncateDirFS - Efficiently empty directories
- TruncateFS - Change file size
- WalkFS - Efficient directory traversal
Helper functions check capabilities automatically and return ErrUnsupported when an operation isn't available.
w, err := fs.Create(ctx, fsys, "file.txt")
if errors.Is(err, fs.ErrUnsupported) {
// Filesystem doesn't support writing
}
File Modes via Context ¶
File permissions can be set via context and apply throughout the operation chain. This is particularly useful with implicit directory creation, where multiple operations may occur (creating parent directories, then the file).
ctx = fs.WithFileMode(ctx, 0600) // Private files ctx = fs.WithDirMode(ctx, 0700) // Private directories // All operations use these modes fs.Create(ctx, fsys, "secret.key") // Creates file with mode 0600 fs.MkdirAll(ctx, fsys, "private/data") // Creates dirs with mode 0700
Default modes are 0644 for files and 0755 for directories. Different call chains can use different modes simultaneously by deriving separate contexts.
File modes are request-scoped values that pass through multiple API calls (Create, Mkdir, MkdirAll) within a single operation chain. This makes context the natural place to store them, similar to how request-scoped credentials or deadlines flow through API boundaries.
For filesystems that don't support file permissions (like many object stores), these values are ignored.
Bulk Operations ¶
Directory operations use tar streams and match file operation semantics. A trailing slash indicates a directory path. This is valuable for remote filesystems where transferring many small files individually would be slow.
// Read directory as tar (like reading a file)
r, err := fs.Open(ctx, fsys, "project/")
if err != nil {
log.Fatal(err)
}
io.Copy(archiveWriter, r)
r.Close()
// Add files to directory (like appending to a file)
w, err := fs.Append(ctx, fsys, "project/")
if err != nil {
log.Fatal(err)
}
io.Copy(w, newFilesArchive)
w.Close()
// Empty directory (like truncating a file to zero)
err = fs.Truncate(ctx, fsys, "project/", 0)
// Replace directory contents (truncate + append)
w, err = fs.Create(ctx, fsys, "restore/")
if err != nil {
log.Fatal(err)
}
io.Copy(w, archiveReader)
w.Close()
Optional interfaces enable native tar implementations: DirFS for reading, AppendDirFS for writing, and TruncateDirFS for emptying. When not implemented, operations fall back to walking the filesystem and using archive/tar. Implementations should use native tar commands when available. For implementations that natively support other archive formats (like zip), consider writing stream converters to and from tar format.
Fallback Implementations ¶
Many operations provide fallback implementations when native support is unavailable.
- Append falls back to reading the existing file and rewriting it with new content appended when AppendFS is not implemented.
- Rename falls back to copying and deleting when RenameFS is not implemented.
- Truncate falls back to creating an empty file when size is 0, or reading, removing, and recreating the file with adjusted size for non-zero sizes, when TruncateFS is not implemented.
- ReadDir calls Walk with depth 1 when ReadDirFS is not implemented.
- Walk recursively calls ReadDir when WalkFS is not implemented.
- Temp creates temporary files or directories with random names when TempFS or TempDirFS are not implemented.
These operations succeed regardless of native support. Native implementations should be added when more efficient approaches are available for specific operations.
Range-Over-Func Iterators ¶
ReadDir and Walk use Go 1.23+ range-over-func iterators for directory traversal.
for entry, err := range fs.ReadDir(ctx, fsys, "dir") {
if err != nil {
return err
}
fmt.Println(entry.Name())
}
This enables early termination without reading entire directories and provides natural error handling within the loop.
Testing ¶
The lesiw.io/fs/fstest package provides a test suite for filesystem implementations.
func TestMyFS(t *testing.T) {
fsys := myfs.New(...)
t.Cleanup(func() { fsys.Close() })
fstest.TestFS(t.Context(), t, fsys)
}
The test suite automatically detects capabilities through type assertions and validates all supported operations.
Example Implementations ¶
The internal/example directory contains reference implementations demonstrating the abstraction across diverse backends:
- internal/example/http - Read-only HTTP filesystem
- internal/example/s3 - Amazon S3 via MinIO SDK
- internal/example/sftp - SSH File Transfer Protocol
- internal/example/smb - SMB/CIFS network shares
- internal/example/ssh - SSH with tar for bulk operations
- internal/example/webdav - WebDAV protocol
These implementations require Docker to run tests.
Compatibility ¶
This package is not a drop-in replacement for io/fs or os because every operation requires a context.Context parameter. However, the design philosophy is closely aligned with io/fs: minimal core interface, optional capabilities through type assertions, and reliance on standard Go interfaces.
The package follows a conservative design that will only be modified carefully over time. New capabilities may be added based on how broadly useful and applicable they are across different filesystem implementations.
The lesiw.io/fs/osfs subpackage provides a maintained reference implementation backed by the os package, demonstrating all optional interfaces
Index ¶
- Constants
- Variables
- func Abs(ctx context.Context, fsys FS, name string) (string, error)
- func AppendBuffer(ctx context.Context, fsys FS, name string) io.WriteCloser
- func Chmod(ctx context.Context, fsys FS, name string, mode Mode) error
- func Chown(ctx context.Context, fsys FS, name string, uid, gid int) error
- func Chtimes(ctx context.Context, fsys FS, name string, atime, mtime time.Time) error
- func Close(fsys FS) error
- func CreateBuffer(ctx context.Context, fsys FS, name string) io.WriteCloser
- func Glob(ctx context.Context, fsys FS, pattern string) ([]string, error)
- func Localize(ctx context.Context, fsys FS, path string) (string, error)
- func Mkdir(ctx context.Context, fsys FS, name string) error
- func MkdirAll(ctx context.Context, fsys FS, name string) error
- func OpenBuffer(ctx context.Context, fsys FS, name string) io.ReadCloser
- func ReadDir(ctx context.Context, fsys FS, name string) iter.Seq2[DirEntry, error]
- func ReadFile(ctx context.Context, fsys FS, name string) ([]byte, error)
- func ReadLink(ctx context.Context, fsys FS, name string) (string, error)
- func Remove(ctx context.Context, fsys FS, name string) error
- func RemoveAll(ctx context.Context, fsys FS, name string) error
- func Rename(ctx context.Context, fsys FS, oldname, newname string) error
- func Symlink(ctx context.Context, fsys FS, oldname, newname string) error
- func Truncate(ctx context.Context, fsys FS, name string, size int64) error
- func Walk(ctx context.Context, fsys FS, root string, depth int) iter.Seq2[DirEntry, error]
- func WithDirMode(ctx context.Context, mode Mode) context.Context
- func WithFileMode(ctx context.Context, mode Mode) context.Context
- func WithWorkDir(ctx context.Context, dir string) context.Context
- func WorkDir(ctx context.Context) string
- func WriteFile(ctx context.Context, fsys FS, name string, data []byte) error
- type AbsFS
- type AppendDirFS
- type AppendFS
- type ChmodFS
- type ChownFS
- type ChtimesFS
- type CreateFS
- type DirEntry
- type DirFS
- type FS
- type FileInfo
- type GlobFS
- type LocalizeFS
- type MkdirAllFS
- type MkdirFS
- type Mode
- type PathError
- type Pather
- type ReadDirFS
- type ReadLinkFS
- type ReadPathCloser
- type RemoveAllFS
- type RemoveFS
- type RenameFS
- type StatFS
- type SymlinkFS
- type TempDirFS
- type TempFS
- type TruncateDirFS
- type TruncateFS
- type WalkFS
- type WritePathCloser
Examples ¶
Constants ¶
const ( // The single letters are the abbreviations // used by the String method's formatting. ModeDir = fs.ModeDir // d: is a directory ModeAppend = fs.ModeAppend // a: append-only ModeExclusive = fs.ModeExclusive // l: exclusive use ModeTemporary = fs.ModeTemporary // T: temporary file; Plan 9 only ModeSymlink = fs.ModeSymlink // L: symbolic link ModeDevice = fs.ModeDevice // D: device file ModeNamedPipe = fs.ModeNamedPipe // p: named pipe (FIFO) ModeSocket = fs.ModeSocket // S: Unix domain socket ModeSetuid = fs.ModeSetuid // u: setuid ModeSetgid = fs.ModeSetgid // g: setgid ModeCharDevice = fs.ModeCharDevice // c: Unix character device, when ModeDevice is set ModeSticky = fs.ModeSticky // t: sticky ModeIrregular = fs.ModeIrregular // ?: non-regular file; nothing else is known about this file // Mask for the type bits. For regular files, none will be set. ModeType = fs.ModeType ModePerm = fs.ModePerm // Unix permission bits )
Valid values for Mode.
Variables ¶
var ( ErrInvalid = fs.ErrInvalid ErrPermission = fs.ErrPermission ErrExist = fs.ErrExist ErrNotExist = fs.ErrNotExist ErrClosed = fs.ErrClosed ErrUnsupported = errors.ErrUnsupported ErrNotDir = errors.New("not a directory") )
Generic file system errors.
Functions ¶
func Abs ¶
Abs returns an absolute representation of path within the filesystem.
If the path is already absolute, Abs returns it cleaned. If the path is relative, Abs attempts to resolve it to an absolute path.
The returned path format depends on the filesystem implementation. Local filesystems return OS-specific absolute paths (e.g., /home/user/file or C:\Users\file). Remote filesystems may return URLs (e.g., s3://bucket/key or https://server/path).
Files ¶
Returns an absolute representation of the file path.
Requires: AbsFS || (absolute WorkDir in ctx)
Directories ¶
Returns an absolute representation of the directory path.
Requires: AbsFS || (absolute WorkDir in ctx)
Similar capabilities: path/filepath.Abs, realpath, pwd.
func AppendBuffer ¶ added in v0.3.0
AppendBuffer returns a lazy-executing writer for appending to the file at name. The file is opened for appending on first Write(), not when AppendBuffer is called.
Example:
io.Copy(fs.AppendBuffer(ctx, fsys, "log.txt"), src)
func Chmod ¶
Chmod changes the mode of the named file to mode. Analogous to: os.Chmod, chmod, 9P Twstat.
Requires: ChmodFS
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.WriteFile(ctx, fsys, "perms.txt", []byte("data"))
if err != nil {
log.Fatal(err)
}
err = fs.Chmod(ctx, fsys, "perms.txt", 0444)
if err != nil {
log.Fatal(err)
}
info, err := fs.Stat(ctx, fsys, "perms.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Permissions: %o\n", info.Mode().Perm())
}
Output: Permissions: 444
func Chown ¶
Chown changes the numeric uid and gid of the named file. Analogous to: os.Chown, os.Lchown, chown, 9P Twstat. This is typically a Unix-specific operation.
Requires: ChownFS
func Chtimes ¶
Chtimes changes the access and modification times of the named file. A zero time.Time value will leave the corresponding file time unchanged. Analogous to: os.Chtimes, touch -t, 9P Twstat.
Requires: ChtimesFS
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.WriteFile(ctx, fsys, "timestamps.txt", []byte("data"))
if err != nil {
log.Fatal(err)
}
mtime := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
atime := time.Now()
err = fs.Chtimes(ctx, fsys, "timestamps.txt", atime, mtime)
if err != nil {
log.Fatal(err)
}
info, err := fs.Stat(ctx, fsys, "timestamps.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Modified: %s\n", info.ModTime().Format("2006-01-02"))
}
Output: Modified: 2009-01-01
func CreateBuffer ¶ added in v0.3.0
CreateBuffer returns a lazy-executing writer for the file at name. The file is created on first Write(), not when CreateBuffer is called.
Example:
io.Copy(fs.CreateBuffer(ctx, fsys, "output.txt"), src)
func Glob ¶
Glob returns the names of all files matching pattern. Analogous to: io/fs.Glob, path.Match, glob, find, 9P walk.
The pattern syntax is the same as in path.Match. The pattern may describe hierarchical names such as usr/*/bin/ed.
Glob ignores file system errors such as I/O errors reading directories. The only possible returned error is path.ErrBadPattern, reporting that the pattern is malformed.
Requires: GlobFS || (StatFS && (ReadDirFS || WalkFS))
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
files := []string{"test1.txt", "test2.txt", "data.csv"}
for _, name := range files {
err := fs.WriteFile(ctx, fsys, name, []byte("content"))
if err != nil {
log.Fatal(err)
}
}
matches, err := fs.Glob(ctx, fsys, "*.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d .txt files\n", len(matches))
}
Output: Found 2 .txt files
func Localize ¶
Localize converts a path from Unix-style to native.
This is typically a lexical operation. For canonical path representation, use Abs.
Localize may be called with an already-localized path and should return the same path unchanged (idempotent behavior).
Requires: LocalizeFS
func Mkdir ¶
Mkdir creates a new directory. Analogous to: os.Mkdir, mkdir.
The directory mode is obtained from DirMode(ctx). If not set in the context, the default mode 0755 is used:
ctx = fs.WithDirMode(ctx, 0700) fs.Mkdir(ctx, fsys, "private") // Creates with mode 0700
Mkdir returns an error if the directory already exists or if the parent directory does not exist. Use MkdirAll to create parent directories automatically.
Requires: MkdirFS
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.Mkdir(ctx, fsys, "mydir")
if err != nil {
log.Fatal(err)
}
info, err := fs.Stat(ctx, fsys, "mydir")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created: %s (isDir: %v)\n", info.Name(), info.IsDir())
}
Output: Created: mydir (isDir: true)
func MkdirAll ¶
MkdirAll creates a directory named name, along with any necessary parents. Analogous to: os.MkdirAll, mkdir -p.
The directory mode is obtained from DirMode(ctx). If not set in the context, the default mode 0755 is used:
ctx = fs.WithDirMode(ctx, 0700) fs.MkdirAll(ctx, fsys, "a/b/c") // All created with mode 0700
If name is already a directory, MkdirAll does nothing and returns nil.
Requires: MkdirAllFS || (MkdirFS && StatFS)
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.MkdirAll(ctx, fsys, "path/to/nested/dir")
if err != nil {
log.Fatal(err)
}
info, err := fs.Stat(ctx, fsys, "path/to/nested/dir")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created: %v\n", info.IsDir())
}
Output: Created: true
func OpenBuffer ¶ added in v0.3.0
OpenBuffer returns a lazy-executing reader for the file at name. The file is opened on first Read(), not when OpenBuffer is called.
Example:
io.Copy(dst, fs.OpenBuffer(ctx, fsys, "input.txt"))
func ReadDir ¶
ReadDir reads the named directory and returns an iterator over its entries. Analogous to: os.ReadDir, io/fs.ReadDir, ls, 9P Tread on directory.
func ReadFile ¶
ReadFile reads the named file and returns its contents. Analogous to: io/fs.ReadFile, os.ReadFile, cat.
Requires: FS
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
content := []byte("Hello, World!")
err := fs.WriteFile(ctx, fsys, "greeting.txt", content)
if err != nil {
log.Fatal(err)
}
data, err := fs.ReadFile(ctx, fsys, "greeting.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", data)
}
Output: Hello, World!
func ReadLink ¶
ReadLink returns the destination of the named symbolic link. Analogous to: os.Readlink, readlink, 9P2000.u Treadlink. If the link destination is relative, ReadLink returns the relative path without resolving it to an absolute one.
Requires: ReadLinkFS
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.Symlink(ctx, fsys, "target.txt", "link.txt")
if err != nil {
log.Fatal(err)
}
target, err := fs.ReadLink(ctx, fsys, "link.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(target)
}
Output: target.txt
func Remove ¶
Remove removes the named file or empty directory. Analogous to: os.Remove, rm, 9P Tremove, S3 DeleteObject. Returns an error if the file does not exist or if a directory is not empty.
Requires: RemoveFS
Example ¶
package main
import (
"context"
"errors"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.WriteFile(ctx, fsys, "delete-me.txt", []byte("temporary"))
if err != nil {
log.Fatal(err)
}
err = fs.Remove(ctx, fsys, "delete-me.txt")
if err != nil {
log.Fatal(err)
}
_, err = fs.Stat(ctx, fsys, "delete-me.txt")
if errors.Is(err, fs.ErrNotExist) {
fmt.Println("File successfully removed")
}
}
Output: File successfully removed
func RemoveAll ¶
RemoveAll removes name and any children it contains. Analogous to: os.RemoveAll, rm -rf.
Requires: RemoveAllFS || (RemoveFS && StatFS && (ReadDirFS || WalkFS))
Example ¶
package main
import (
"context"
"errors"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.MkdirAll(ctx, fsys, "tree/branch/leaf")
if err != nil {
log.Fatal(err)
}
err = fs.WriteFile(ctx, fsys, "tree/file.txt", []byte("data"))
if err != nil {
log.Fatal(err)
}
err = fs.RemoveAll(ctx, fsys, "tree")
if err != nil {
log.Fatal(err)
}
_, err = fs.Stat(ctx, fsys, "tree")
if errors.Is(err, fs.ErrNotExist) {
fmt.Println("Directory tree successfully removed")
}
}
Output: Directory tree successfully removed
func Rename ¶
Rename renames (moves) oldname to newname. Analogous to: os.Rename, mv, 9P2000.u Trename. If newname already exists and is not a directory, Rename replaces it.
Requires: RenameFS || (FS && CreateFS && RemoveFS)
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.WriteFile(ctx, fsys, "old-name.txt", []byte("content"))
if err != nil {
log.Fatal(err)
}
err = fs.Rename(ctx, fsys, "old-name.txt", "new-name.txt")
if err != nil {
log.Fatal(err)
}
data, err := fs.ReadFile(ctx, fsys, "new-name.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Content: %s\n", data)
}
Output: Content: content
func Symlink ¶
Symlink creates newname as a symbolic link to oldname. Analogous to: os.Symlink, ln -s, 9P2000.u Tsymlink.
Requires: SymlinkFS
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.WriteFile(ctx, fsys, "original.txt", []byte("content"))
if err != nil {
log.Fatal(err)
}
err = fs.Symlink(ctx, fsys, "original.txt", "link.txt")
if err != nil {
log.Fatal(err)
}
data, err := fs.ReadFile(ctx, fsys, "link.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", data)
}
Output: content
func Truncate ¶
Truncate changes the size of the named file or empties a directory. Analogous to: os.Truncate, truncate, 9P Twstat.
Like os.Truncate, Truncate returns an error if the path doesn't exist. If StatFS is available, the existence check happens before attempting the operation. Otherwise, the error comes from the truncate operation itself.
Files ¶
If the file is larger than size, it is truncated. If it is smaller, it is extended with zeros.
Requires: TruncateFS || (FS && RemoveFS && CreateFS)
Directories ¶
A trailing slash indicates a directory. Removes all contents, leaving an empty directory.
Requires: TruncateDirFS || (RemoveAllFS && MkdirFS)
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.WriteFile(ctx, fsys, "shrink.txt", []byte("Hello, World!"))
if err != nil {
log.Fatal(err)
}
err = fs.Truncate(ctx, fsys, "shrink.txt", 5)
if err != nil {
log.Fatal(err)
}
data, err := fs.ReadFile(ctx, fsys, "shrink.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", data)
}
Output: Hello
func Walk ¶
Walk traverses the filesystem rooted at root. Analogous to: io/fs.WalkDir, find, tree.
The depth parameter controls how deep to traverse (like find -maxdepth):
- depth <= 0: unlimited depth (like find without -maxdepth)
- depth >= 1: root directory plus n-1 levels of subdirectories (like find -maxdepth n)
Walk does not guarantee any particular order (lexicographic or breadth-first). Implementations may choose whatever order is most efficient.
Walk does not follow symbolic links. Entries are yielded for symbolic links themselves, but they are not traversed.
Entries returned by Walk have Path() populated with the full path.
If an error occurs reading a directory, the iteration yields a zero DirEntry and the error. The caller can choose to continue iterating (skip that directory) or break to stop the walk.
Example (Recursive) ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.MkdirAll(ctx, fsys, "walk/sub")
if err != nil {
log.Fatal(err)
}
err = fs.WriteFile(ctx, fsys, "walk/file1.txt", []byte("one"))
if err != nil {
log.Fatal(err)
}
err = fs.WriteFile(ctx, fsys, "walk/sub/file2.txt", []byte("two"))
if err != nil {
log.Fatal(err)
}
count := 0
for entry, err := range fs.Walk(ctx, fsys, "walk", -1) {
if err != nil {
log.Fatal(err)
}
if !entry.IsDir() {
count++
}
}
fmt.Printf("Found %d files\n", count)
}
Output: Found 2 files
Example (SingleLevel) ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.Mkdir(ctx, fsys, "testdir")
if err != nil {
log.Fatal(err)
}
err = fs.WriteFile(ctx, fsys, "testdir/file1.txt", []byte("one"))
if err != nil {
log.Fatal(err)
}
err = fs.WriteFile(ctx, fsys, "testdir/file2.txt", []byte("two"))
if err != nil {
log.Fatal(err)
}
var entries []fs.DirEntry
for entry, err := range fs.Walk(ctx, fsys, "testdir", 0) {
if err != nil {
log.Fatal(err)
}
entries = append(entries, entry)
}
fmt.Printf("Found %d entries:\n", len(entries))
for _, entry := range entries {
fmt.Printf("- %s (dir: %v)\n", entry.Name(), entry.IsDir())
}
}
Output: Found 2 entries: - file1.txt (dir: false) - file2.txt (dir: false)
func WithDirMode ¶
WithDirMode returns a context that carries a directory mode for automatic directory creation. When Create or WriteFile operations fail because a parent directory doesn't exist, and the filesystem supports MkdirFS, the parent directories will be created with this mode.
If no directory mode is set in the context, the default mode 0755 is used.
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
ctx = fs.WithDirMode(ctx, 0700)
err := fs.MkdirAll(ctx, fsys, "private/data")
if err != nil {
log.Fatal(err)
}
info, err := fs.Stat(ctx, fsys, "private")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Mode: %04o\n", info.Mode().Perm())
}
Output: Mode: 0700
func WithFileMode ¶
WithFileMode returns a context that carries a file mode for file creation. When Create or WriteFile operations create files, they use this mode.
If no file mode is set in the context, the default mode 0644 is used.
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
ctx = fs.WithFileMode(ctx, 0600)
err := fs.WriteFile(ctx, fsys, "private.txt", []byte("secret"))
if err != nil {
log.Fatal(err)
}
info, err := fs.Stat(ctx, fsys, "private.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Mode: %04o\n", info.Mode().Perm())
}
Output: Mode: 0600
func WithWorkDir ¶
WithWorkDir returns a context that carries a working directory for relative path resolution. Filesystem implementations should resolve relative paths relative to this directory.
If dir is relative and ctx already has a working directory, WithWorkDir composes them, like cd. If dir is absolute, it replaces any existing working directory.
If no working directory is set, implementations should use their default working directory (typically the current working directory).
func WorkDir ¶
WorkDir retrieves the working directory from context. Returns an empty string if no working directory is set.
func WriteFile ¶
WriteFile writes data to the named file in the filesystem. It creates the file or truncates it if it already exists. The file is created with mode 0644 (or the mode specified via WithFileMode).
Like os.WriteFile, this always truncates existing files to zero length before writing.
If the parent directory does not exist and the filesystem implements MkdirFS, WriteFile automatically creates the parent directories with mode 0755 (or the mode specified via WithDirMode).
This is analogous to os.WriteFile and io/fs.ReadFile.
Requires: CreateFS
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
data := []byte("Hello, filesystem!")
err := fs.WriteFile(ctx, fsys, "output.txt", data)
if err != nil {
log.Fatal(err)
}
readData, err := fs.ReadFile(ctx, fsys, "output.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", readData)
}
Output: Hello, filesystem!
Types ¶
type AbsFS ¶
type AbsFS interface {
FS
// Abs returns an absolute representation of path.
// If the path is not absolute, it will be joined with the filesystem's
// root and any working directory from ctx to produce an absolute path.
//
// The returned path format depends on the filesystem implementation:
// local filesystems return OS paths, remote filesystems may return URLs.
Abs(ctx context.Context, name string) (string, error)
}
An AbsFS is a file system with the Abs method.
type AppendDirFS ¶
type AppendDirFS interface {
FS
// AppendDir creates a tar stream for writing to the specified directory.
// Data written to the returned writer is extracted as a tar archive into
// the directory. The directory will be created if it doesn't exist.
// Existing files with the same names will be overwritten, but other files
// in the directory are preserved.
//
// The returned writer must be closed to complete the operation.
AppendDir(ctx context.Context, dir string) (io.WriteCloser, error)
}
An AppendDirFS is a file system that can write tar streams to directories.
AppendDirFS is an optional interface that enables efficient bulk writes via tar archives, particularly useful for transferring many small files to remote filesystems. When not implemented, directory operations fall back to extracting tar archives file-by-file using CreateFS.
type AppendFS ¶
type AppendFS interface {
FS
// Append opens a file for appending. Writes are added to the
// end of the file.
// If the file does not exist, it is created with mode 0644 (or the mode
// specified via WithFileMode).
//
// The returned writer must be closed when done.
Append(ctx context.Context, name string) (io.WriteCloser, error)
}
An AppendFS is a file system with the Append method.
type ChmodFS ¶
type ChmodFS interface {
FS
// Chmod changes the mode of the named file to mode.
Chmod(ctx context.Context, name string, mode Mode) error
}
A ChmodFS is a file system with the Chmod method.
type ChownFS ¶
type ChownFS interface {
FS
// Chown changes the numeric uid and gid of the named file.
// This is typically a Unix-specific operation.
Chown(ctx context.Context, name string, uid, gid int) error
}
A ChownFS is a file system with the Chown method.
type ChtimesFS ¶
type ChtimesFS interface {
FS
// Chtimes changes the access and modification times of the named file.
// A zero time.Time value will leave the corresponding file time unchanged.
Chtimes(ctx context.Context, name string, atime, mtime time.Time) error
}
A ChtimesFS is a file system with the Chtimes method.
type CreateFS ¶
type CreateFS interface {
FS
// Create creates a new file or truncates an existing file for writing.
// If the file already exists, it is truncated. If the file does not exist,
// it is created with mode 0644 (or the mode specified via WithFileMode).
//
// The returned writer must be closed when done. The writer may also
// implement io.Seeker, io.WriterAt, or other interfaces depending
// on the implementation.
Create(ctx context.Context, name string) (io.WriteCloser, error)
}
A CreateFS is a file system with the Create method.
type DirEntry ¶
type DirEntry interface {
// Name returns the name of the file (or subdirectory) described
// by the entry. This name is only the final element of the path
// (the base name), not the entire path.
Name() string
// IsDir reports whether the entry describes a directory.
IsDir() bool
// Type returns the type bits for the entry. The type bits are a
// subset of the usual FileMode bits, those returned by the
// FileMode.Type method.
Type() fs.FileMode
// Info returns the FileInfo for the file or subdirectory
// described by the entry. The returned FileInfo may be from the
// time of the original directory read or from the time of the
// call to Info. If the file has been removed or renamed since
// the directory read, Info may return an error satisfying
// errors.Is(err, ErrNotExist). If the entry denotes a symbolic
// link, Info reports the information about the link itself, not
// the link's target.
Info() (FileInfo, error)
// Path returns the path of this entry, matching the semantics of the
// path argument to fs.WalkDirFunc. The path contains the walk root as a
// prefix. For example, if Walk is called with root "dir" and finds a
// file named "a" in that directory, Path() will return "dir/a".
//
// For entries returned by ReadDir, Path() returns an empty string since
// ReadDir only provides entries within a single directory without path
// context.
Path() string
}
DirEntry describes a directory entry.
This interface extends the standard io/fs.DirEntry with path information. The Path() method returns the path of the entry matching the semantics of the path parameter in fs.WalkDirFunc. For entries returned by ReadDir, Path() returns an empty string since ReadDir only provides entries within a single directory.
type DirFS ¶
type DirFS interface {
FS
// OpenDir opens a tar stream for reading from the specified directory.
// The directory is archived as a tar stream that can be read until EOF.
//
// The returned reader must be closed when done.
OpenDir(ctx context.Context, dir string) (io.ReadCloser, error)
}
A DirFS is a file system that can read directories as tar streams.
DirFS is an optional interface that enables efficient bulk reads via tar archives, particularly useful for read-only filesystems or transferring many small files from remote filesystems. When not implemented, directory operations fall back to walking the filesystem and creating tar archives manually.
type FS ¶
type FS interface {
// Open opens the named file for reading.
//
// The returned reader must be closed when done. The reader may also
// implement io.Seeker, io.ReaderAt, or other interfaces depending
// on the implementation.
Open(ctx context.Context, name string) (io.ReadCloser, error)
}
An FS is a file system with the Open method.
type FileInfo ¶
A FileInfo describes a file and is returned by Stat.
func Lstat ¶
Lstat returns FileInfo describing the named file. Analogous to: os.Lstat, stat (without -L). If the file is a symbolic link, the returned FileInfo describes the symbolic link. Lstat makes no attempt to follow the link.
Requires: ReadLinkFS || StatFS
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.WriteFile(ctx, fsys, "target.txt", []byte("content"))
if err != nil {
log.Fatal(err)
}
err = fs.Symlink(ctx, fsys, "target.txt", "link.txt")
if err != nil {
log.Fatal(err)
}
info, err := fs.Lstat(ctx, fsys, "link.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("IsSymlink: %v\n", info.Mode()&fs.ModeSymlink != 0)
}
Output: IsSymlink: true
func Stat ¶
Stat returns file metadata for the named file. Analogous to: io/fs.Stat, os.Stat, stat, ls -l, 9P Tstat, S3 HeadObject.
Requires: StatFS
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.WriteFile(ctx, fsys, "example.txt", []byte("hello"))
if err != nil {
log.Fatal(err)
}
info, err := fs.Stat(ctx, fsys, "example.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Name: %s\n", info.Name())
fmt.Printf("Size: %d bytes\n", info.Size())
fmt.Printf("IsDir: %v\n", info.IsDir())
}
Output: Name: example.txt Size: 5 bytes IsDir: false
type GlobFS ¶
type GlobFS interface {
FS
// Glob returns the names of all files matching pattern.
// The pattern syntax is the same as in [path.Match].
Glob(ctx context.Context, pattern string) ([]string, error)
}
A GlobFS is a file system with the Glob method.
If not implemented, Glob falls back to pattern matching using StatFS and ReadDirFS.
type LocalizeFS ¶
type LocalizeFS interface {
FS
// Localize converts a Unix-style path to a native representation.
//
// Localize must be idempotent: calling Localize on an already-localized
// path should return the same path. That is, Localize(Localize(p))
// should equal Localize(p).
Localize(ctx context.Context, path string) (string, error)
}
A LocalizeFS is a filesystem that can convert Unix-style paths to native paths.
In most circumstances, this should be a lexical operation. For true path canonicalization, see AbsFS.
Operations like Open, Create, Append, and Truncate automatically call Localize internally when the filesystem implements LocalizeFS. The localized path is returned via the Path() method on the returned ReadPathCloser or WritePathCloser.
type MkdirAllFS ¶ added in v0.2.0
type MkdirAllFS interface {
FS
// MkdirAll creates a directory named name, along with any necessary
// parents. If name is already a directory, MkdirAll does nothing and
// returns nil.
MkdirAll(ctx context.Context, name string) error
}
A MkdirAllFS is a file system with the MkdirAll method.
If not implemented, MkdirAll falls back to recursive creation using MkdirFS and StatFS.
type MkdirFS ¶
type MkdirFS interface {
FS
// Mkdir creates a new directory.
//
// The directory mode is obtained from DirMode(ctx). If not set in
// the context, the default mode 0755 is used.
//
// Mkdir returns an error if the directory already exists or if the
// parent directory does not exist. Use MkdirAll to create parent
// directories automatically.
Mkdir(ctx context.Context, name string) error
}
A MkdirFS is a file system with the Mkdir method.
type Mode ¶
A Mode represents a file's mode and permission bits. The bits have the same definition on all systems, so that information about files can be moved from one system to another portably. Not all bits apply to all systems. The only required bit is ModeDir for directories.
func DirMode ¶
DirMode retrieves the directory mode from context. Returns 0755 if no mode is set.
Example ¶
package main
import (
"context"
"fmt"
"lesiw.io/fs"
)
func main() {
ctx := context.Background()
ctx = fs.WithDirMode(ctx, 0700)
mode := fs.DirMode(ctx)
fmt.Printf("Mode: %04o\n", mode)
}
Output: Mode: 0700
func FileMode ¶
FileMode retrieves the file mode from context. Returns 0644 if no mode is set.
Example ¶
package main
import (
"context"
"fmt"
"lesiw.io/fs"
)
func main() {
ctx := context.Background()
ctx = fs.WithFileMode(ctx, 0600)
mode := fs.FileMode(ctx)
fmt.Printf("Mode: %04o\n", mode)
}
Output: Mode: 0600
type Pather ¶
type Pather interface {
Path() string
}
Pather is the interface that wraps the Path method.
Path returns the native filesystem path for this resource.
type ReadDirFS ¶
type ReadDirFS interface {
FS
// ReadDir reads the directory and returns an iterator over its entries.
// For entries from ReadDir, Path() returns empty string.
ReadDir(ctx context.Context, name string) iter.Seq2[DirEntry, error]
}
A ReadDirFS is a file system with the ReadDir method.
If not implemented, ReadDir falls back to Walk with depth 1.
type ReadLinkFS ¶
type ReadLinkFS interface {
FS
// ReadLink returns the destination of the named symbolic link.
// If the link destination is relative, ReadLink returns the relative
// path without resolving it to an absolute one.
ReadLink(ctx context.Context, name string) (string, error)
// Lstat returns FileInfo describing the named file.
// If the file is a symbolic link, the returned FileInfo
// describes the symbolic link. Lstat makes no attempt to follow
// the link.
Lstat(ctx context.Context, name string) (FileInfo, error)
}
A ReadLinkFS is a file system with the ReadLink and Lstat methods.
type ReadPathCloser ¶
ReadPathCloser is the interface that groups the Read, Path, and Close methods.
func Open ¶
Open opens the named file or directory for reading. Analogous to: io/fs.Open, os.Open, cat, tar, 9P Topen, S3 GetObject.
All paths use forward slashes (/) regardless of the operating system, following io/fs conventions. Use the path package (not path/filepath) for path manipulation. Implementations handle OS-specific conversion internally.
The returned ReadPathCloser must be closed when done. The Path() method returns the native filesystem path, or the input path if localization is not supported.
Files ¶
Returns a ReadPathCloser for reading the file contents.
Requires: FS
Directories ¶
A trailing slash returns a tar archive stream of the directory contents. A path identified as a directory via StatFS also returns a tar archive.
Requires: DirFS || (FS && (ReadDirFS || WalkFS))
Example ¶
package main
import (
"context"
"fmt"
"io"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.WriteFile(ctx, fsys, "data.txt", []byte("example content"))
if err != nil {
log.Fatal(err)
}
f, err := fs.Open(ctx, fsys, "data.txt")
if err != nil {
log.Fatal(err)
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", data)
}
Output: example content
Example (Directory) ¶
package main
import (
"bytes"
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.MkdirAll(ctx, fsys, "project/src")
if err != nil {
log.Fatal(err)
}
err = fs.WriteFile(ctx, fsys, "project/README.md", []byte("# Project"))
if err != nil {
log.Fatal(err)
}
data := []byte("package main")
err = fs.WriteFile(ctx, fsys, "project/src/main.go", data)
if err != nil {
log.Fatal(err)
}
tarReader, err := fs.Open(ctx, fsys, "project/")
if err != nil {
log.Fatal(err)
}
defer tarReader.Close()
var buf bytes.Buffer
n, err := buf.ReadFrom(tarReader)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created tar archive: %d bytes\n", n)
}
Output: Created tar archive: 3584 bytes
type RemoveAllFS ¶
type RemoveAllFS interface {
FS
// RemoveAll removes name and any children it contains.
RemoveAll(ctx context.Context, name string) error
}
A RemoveAllFS is a file system with the RemoveAll method.
If not implemented, RemoveAll falls back to recursive removal using RemoveFS, StatFS, and ReadDirFS.
type RemoveFS ¶
type RemoveFS interface {
FS
// Remove removes the named file or empty directory.
// It returns an error if the file does not exist or if a directory
// is not empty.
Remove(ctx context.Context, name string) error
}
A RemoveFS is a file system with the Remove method.
type RenameFS ¶
type RenameFS interface {
FS
// Rename renames (moves) oldname to newname.
// If newname already exists and is not a directory, Rename replaces it.
Rename(ctx context.Context, oldname, newname string) error
}
A RenameFS is a file system with the Rename method.
type StatFS ¶
type StatFS interface {
FS
// Stat returns file metadata for the named file.
Stat(ctx context.Context, name string) (FileInfo, error)
}
A StatFS is a file system with the Stat method.
type SymlinkFS ¶
type SymlinkFS interface {
FS
// Symlink creates newname as a symbolic link to oldname.
Symlink(ctx context.Context, oldname, newname string) error
}
A SymlinkFS is a file system with the Symlink method.
type TempDirFS ¶
type TempDirFS interface {
FS
// TempDir creates a temporary directory and returns its path.
// The name parameter is used to generate the directory name
// (implementation-specific). The directory will be created in an
// OS-appropriate temporary location.
//
// Returns the path to the created directory. The caller will use
// Append with trailing slash to open it for tar writing.
//
// If the filesystem cannot determine an appropriate temp location,
// it should return ErrUnsupported to trigger the fallback behavior.
TempDir(ctx context.Context, name string) (string, error)
}
A TempDirFS is a file system with the TempDir method.
If not implemented, TempDir falls back to creating a directory with a random name using MkdirFS.
type TempFS ¶
type TempFS interface {
FS
// Temp creates a temporary file and returns its path.
// The name parameter is used to generate the filename
// (implementation-specific). The file will be created in an
// OS-appropriate temporary location.
//
// Returns the path to the created file. The caller will use Create
// or Append to open it for writing.
//
// If the filesystem cannot determine an appropriate temp location,
// it should return ErrUnsupported to trigger the fallback behavior.
Temp(ctx context.Context, name string) (string, error)
}
A TempFS is a file system with the Temp method.
If not implemented, Temp falls back to TempDirFS.TempDir (creating a file inside a temp directory), then to CreateFS (creating a file with a random name).
type TruncateDirFS ¶
type TruncateDirFS interface {
FS
// TruncateDir removes all contents from the specified directory, leaving
// it empty. Returns an error if the directory doesn't exist.
TruncateDir(ctx context.Context, dir string) error
}
A TruncateDirFS is a file system that can efficiently empty directories.
TruncateDirFS is an optional interface for efficient directory truncation. When not implemented, falls back to RemoveAll + Mkdir when available.
type TruncateFS ¶
type TruncateFS interface {
FS
// Truncate changes the size of the named file.
// If the file is larger than size, it is truncated.
// If it is smaller, it is extended with zeros.
Truncate(ctx context.Context, name string, size int64) error
}
A TruncateFS is a file system with the Truncate method.
type WalkFS ¶
type WalkFS interface {
FS
// Walk traverses the filesystem starting at root.
// The depth parameter controls how deep to traverse:
// depth <= 0: unlimited depth (like find without -maxdepth)
// depth >= 1: root directory plus n-1 levels of subdirectories
// (like find -maxdepth n)
//
// Entries returned by Walk have Path() populated with full paths.
Walk(
ctx context.Context, root string, depth int,
) iter.Seq2[DirEntry, error]
}
A WalkFS is a file system with the Walk method.
If not implemented, Walk falls back to breadth-first traversal using ReadDirFS.
type WritePathCloser ¶
WritePathCloser is the interface that groups the Write, Path, and Close methods.
func Append ¶
Append opens a file for appending or adds files to a directory. Analogous to: os.OpenFile with O_APPEND, echo >>, tar (append mode), 9P Topen with OAPPEND.
If the parent directory does not exist and the filesystem implements MkdirFS, Append automatically creates the parent directories with mode 0755 (or the mode specified via WithDirMode).
The returned WritePathCloser must be closed when done. The Path() method returns the native filesystem path, or the input path if localization is not supported.
Files ¶
Writes are added to the end of the file. If the file does not exist, it is created with mode 0644 (or the mode specified via WithFileMode).
Requires: AppendFS || (FS && CreateFS)
Directories ¶
A trailing slash returns a tar stream writer that extracts files into the directory. The directory is created if it doesn't exist. Existing files with the same names are overwritten, but other files in the directory are preserved.
Requires: AppendDirFS || CreateFS
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.WriteFile(ctx, fsys, "log.txt", []byte("first line\n"))
if err != nil {
log.Fatal(err)
}
f, err := fs.Append(ctx, fsys, "log.txt")
if err != nil {
log.Fatal(err)
}
_, err = f.Write([]byte("second line\n"))
if err != nil {
_ = f.Close()
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
data, err := fs.ReadFile(ctx, fsys, "log.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", data)
}
Output: first line second line
func Create ¶
Create creates or truncates the named file for writing. Analogous to: os.Create, touch, echo >, tar, 9P Tcreate, S3 PutObject.
If the parent directory does not exist and the filesystem implements MkdirFS, Create automatically creates the parent directories with mode 0755 (or the mode specified via WithDirMode).
The returned WritePathCloser must be closed when done. The Path() method returns the native filesystem path, or the input path if localization is not supported.
Files ¶
If the file already exists, it is truncated. If the file does not exist, it is created with mode 0644 (or the mode specified via WithFileMode).
Requires: CreateFS
Directories ¶
A trailing slash empties the directory (or creates it if it doesn't exist) and returns a tar stream writer for extracting files into it. This is equivalent to Truncate(name, 0) followed by Append(name).
Requires: See Truncate and Append requirements
Example ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
f, err := fs.Create(ctx, fsys, "newfile.txt")
if err != nil {
log.Fatal(err)
}
_, err = f.Write([]byte("Creating a new file"))
if err != nil {
_ = f.Close()
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
data, err := fs.ReadFile(ctx, fsys, "newfile.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", data)
}
Output: Creating a new file
Example (Directory) ¶
package main
import (
"context"
"fmt"
"io"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
err := fs.MkdirAll(ctx, fsys, "source/data")
if err != nil {
log.Fatal(err)
}
err = fs.WriteFile(ctx, fsys, "source/data/file.txt", []byte("content"))
if err != nil {
log.Fatal(err)
}
err = func() error {
tr, err := fs.Open(ctx, fsys, "source/")
if err != nil {
return err
}
defer tr.Close()
tw, err := fs.Create(ctx, fsys, "dest/")
if err != nil {
return err
}
defer tw.Close()
_, err = io.Copy(tw, tr)
return err
}()
if err != nil {
log.Fatal(err)
}
data, err := fs.ReadFile(ctx, fsys, "dest/data/file.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", data)
}
Output: content
func Temp ¶
Temp creates a temporary file or directory. Analogous to: os.CreateTemp, os.MkdirTemp, mktemp.
The returned WritePathCloser must be closed when done. Path() returns the full path to the created resource. The caller is responsible for removing the temporary resource when done (typically with RemoveAll).
Files ¶
Without a trailing separator, creates a temporary file. The name parameter serves as a prefix or pattern (implementation-specific). The file name will typically have the pattern: name-randomhex
Requires: TempFS || TempDirFS || CreateFS
Directories ¶
With a trailing separator, creates a temporary directory and returns a tar stream writer for extracting files into it. The directory name will typically have the pattern: name-randomhex
Requires: TempDirFS || MkdirFS
Example (Dir) ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
// Create temp directory (trailing slash indicates directory)
w, err := fs.Temp(ctx, fsys, "myapp/")
if err != nil {
log.Fatal(err)
}
defer w.Close()
// Get the directory path and defer cleanup
dir := w.Path()
defer fs.RemoveAll(ctx, fsys, dir)
// Create a file in the temp directory
err = fs.WriteFile(ctx, fsys, dir+"/data.txt", []byte("temporary data"))
if err != nil {
log.Fatal(err)
}
// Read it back
data, err := fs.ReadFile(ctx, fsys, dir+"/data.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", data)
}
Output: temporary data
Example (File) ¶
package main
import (
"context"
"fmt"
"log"
"lesiw.io/fs"
"lesiw.io/fs/osfs"
)
func main() {
fsys, ctx := osfs.NewTemp(), context.Background()
defer fs.Close(fsys)
// Create temp file (no trailing slash)
w, err := fs.Temp(ctx, fsys, "myapp")
if err != nil {
log.Fatal(err)
}
// Get the file path and defer cleanup
path := w.Path()
defer fs.Remove(ctx, fsys, path)
// Write to the temp file
_, err = w.Write([]byte("temporary data"))
if err != nil {
_ = w.Close()
log.Fatal(err)
}
if err := w.Close(); err != nil {
log.Fatal(err)
}
// Read it back
data, err := fs.ReadFile(ctx, fsys, path)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", data)
}
Output: temporary data
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package memfs implements lesiw.io/fs.FS using an in-memory file tree.
|
Package memfs implements lesiw.io/fs.FS using an in-memory file tree. |
|
Package osfs implements lesiw.io/fs.FS using the os package.
|
Package osfs implements lesiw.io/fs.FS using the os package. |
|
Package path implements utility routines for manipulating filesystem paths in a lexical manner, supporting Unix, Windows, and URL path styles.
|
Package path implements utility routines for manipulating filesystem paths in a lexical manner, supporting Unix, Windows, and URL path styles. |