A Go filesystem helpers package for CLIs: atomic writes, safe reads, path resolution, cross-platform user-directory lookup, file watching, archive extraction, scaffolding, advisory file locking, directory-backed caching, log rotation, tail-follow with rotation handling, versioned backups, transactional plan/apply, gitignore-aware walks, memory-mapped reads, and the dozens of small operations a CLI repeatedly needs to get right.
This package is used as the default filesystem package for rotini.
- Atomic writes (temp file +
fsync+ rename + parent-dirfsync) viaWriteFile,WriteFileSecret,WriteFileExclusive,WriteString,Append, with optionsWithPerm,WithSync,WithBackup,WithMkdirAll - Bounded reads (default 100 MiB, overridable with
WithMaxSize) viaReadFile,ReadLines,ReadFirstLine,OpenLines,OpenChunked,ReadAt - Idempotent removal:
Remove,RemoveAll,RemoveContents, plus symlink-safeRemoveAllNoFollow - Path safety primitives:
IsSubpath,MustBeChildOf,EvalSymlinksWithin,SanitizeFilename,IsReservedName,LongPath - TOCTOU-safe open:
OpenNoFollow(refuses final-component symlinks) andOpenAt(resolves through a held directory FD on POSIX) - Cross-platform user and system directories (XDG on Linux/FreeBSD, Apple guidelines on macOS,
%APPDATA%/%LOCALAPPDATA%/%PROGRAMDATA%on Windows):Home,ConfigDir,CacheDir,DataDir,StateDir,RuntimeDir,ExecutableDir,BinaryPath, plusApp*Dir(appName)andSystem*Dir(appName)variants - Path expansion:
Expandhandles~,~user,$VAR,${VAR}with opt-in strict mode - Find-up project discovery:
FindUp,FindUpAll,ProjectRoot,FirstExisting - Directory walking:
WalkwithWalkSkipHidden,WalkSkipNames,WalkSkipPatterns,WalkMaxDepth,WalkFollowSymlinks,WalkErrorHandler,WithWalkGitignore; concurrent variantWalkParallel(ctx, ...) - Glob:
Match,Glob,GlobAnywith path expansion before matching - Atomic-rename-aware file watching: multi-subscriber broadcast, 75 ms trailing-edge debouncing, per-instance pluggable
slog.Logger. v0.1 ships the polling backend; native inotify / kqueue /ReadDirectoryChangesWscheduled as a follow-up release - Archive extraction and creation: tar / tar.gz / zip with path-confinement via
MustBeChildOf(zip-slip / tar-slip defense), bounded viaWithArchiveMaxBytes(default 10 GiB), entry-level filters - Scaffolding from
io/fs.FS(typicallyembed.FS):ScaffoldApplywithtext/template-rendered paths and contents, conflict policies, and aScaffoldExtractfirst-run resource-extraction variant - Advisory file locking:
Lock,LockShared,TryLock,LockTimeout,WithLock,IsLocked,PIDLockwith stale-lock reclamation and opt-inWithPIDLockFingerprintfor recycled-PID defense;syscall.Flockon POSIX,LockFileExon Windows - Directory-backed cache:
*Cachewith TTL, mtime-based eviction, size-bounded LRU, content-versioned namespaces,Get/GetWithError/Set/Delete/Purge/Stats/Entries - Tail-follow with rotation handling:
Tail(ctx, path) iter.Seq2[string, error]detects rename-style rotation and in-place truncation viaos.SameFile, reopens transparently (or opts intoErrTailRotatednotifications) - Log rotation:
*Rotator(io.WriteCloser) with size and age triggers, retention count, optional gzip compression of rotated files - Versioned backups:
WriteFileVersioned/ListVersions/RestoreVersionwithWithVersionsKeep,WithVersionsMaxAge,WithVersionsPerm,WithVersionsMaxBytes - Transactional plan/apply: build a
*PlanofCreate/Update/Delete/Renameops, preview withDiff, execute viaApplyagainst an on-disk journal that supportsResumeandRollback - Gitignore parser:
*Gitignorewith negation, anchoring,**recursive, and per-segment globs; integrates withWalkviaWithWalkGitignore - Memory-mapped reads:
Mmapreturns a*Mapping(syscall.Mmap(PROT_READ, MAP_SHARED)on POSIX,CreateFileMapping+MapViewOfFileon Windows) - Content search:
FindByContent/FindByContentRegexwith binary-file skipping, configurable per-file size cap viaWithFindByContentMaxSize - Disk-info:
DiskUsage,DiskUsageOf,MountPoint,FilesystemType,IsNetworkFS,IsCaseInsensitiveFS,PreflightSpace - Hashing: streaming
Hash, constant-timeHashCompare,HashWriterforio.MultiWriteruse; SHA-256 default, plus SHA-512 / SHA-1 / MD5 (the last two for non-security uses) - Bytes formatting: strict-SI
ParseBytes(1KB == 1000, matching kubectl / docker) plus IEC-binaryParseBytesIEC(1KB == 1024);FormatBytes - Project-kind detection:
ProjectTyperecognizes Go, Node, Rust, Python, Ruby, Java, .NET, PHP, Make, Docker via marker files; extensible viaRegisterProjectKind - Multi-root workspace discovery:
WorkspaceRootsparsesgo.work,package.jsonworkspaces (array + object forms),pnpm-workspace.yaml - Stdio:
ReadStdin,OpenStdinLines,WriteStdout/WriteStderr(translatesEPIPE->ErrBrokenPipe),IsTerminal - Test helpers in
fs/fstest:NewTestHarness(t),MockFS,WithTempEnv,TempFileT,TempDirT. Importinggithub.com/go-rotini/fsdoes not pull stdlib'stestingpackage into your binary - Bi-directional sentinel matching:
errors.Is(err, fs.ErrNotFound)matches both the package sentinel AND stdlib'sio/fs.ErrNotExist;*MultiErrorwith Go 1.20Unwrap() []error - Pluggable
slog.LoggerforApply/Cache/Rotatordebug output via package-levelSetLogger - DoS protection: bounded reads by default, archive extraction size cap, plan/apply journal size split, content-search size cap, gitignore + workspace parser size limits
- Zero non-stdlib runtime dependencies; cross-platform on Linux, macOS, Windows, FreeBSD
go get github.com/go-rotini/fsRequires Go 1.26 or later.
package main
import (
"fmt"
"log"
"github.com/go-rotini/fs"
)
func main() {
dir, cleanup, err := fs.TempDir("", "fs-quickstart-*")
if err != nil {
log.Fatal(err)
}
defer func() { _ = cleanup() }()
cfg := dir + "/config.toml"
// Atomic write: temp + fsync + rename + parent-dir fsync.
if err := fs.WriteFile(cfg, []byte("[server]\nport = 8080\n")); err != nil {
log.Fatal(err)
}
// Bounded read (default cap 100 MiB; override with WithMaxSize).
data, err := fs.ReadFile(cfg)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
// Coordinate with peer processes via an advisory file lock.
if err := fs.WithLock(dir+"/work.lock", func() error {
// Critical section.
return nil
}); err != nil {
log.Fatal(err)
}
// Discover the project root from any subdirectory.
if root, err := fs.ProjectRoot(dir); err == nil {
fmt.Println("project root:", root)
}
}Full API reference is available on pkg.go.dev.
See CONTRIBUTING.md for guidelines on how to contribute to this project.
This project follows a code of conduct to ensure a welcoming community. See CODE_OF_CONDUCT.md.
To report a vulnerability, see SECURITY.md.
This project is licensed under the MIT License. See LICENSE for details.