fileutils

package module
v0.29.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 4, 2026 License: Apache-2.0 Imports: 14 Imported by: 4

Documentation

Overview

Package fileutils exposes utilities to deal with files and paths.

It provides:

  • File, an abstraction of an uploaded file. It is used by github.com/go-openapi/runtime.File.
  • Implementations of fs.FS: OsFS and GlobOsFS wrap the os package, MapFS serves files held in memory, OverlayFS stacks file systems on top of one another, OpaqueFS lets a layer claim a directory for itself, and FileReaderFS adds a ReadFile method to any fs.FS.
  • MustSub, to re-root a file system inline when the directory is a constant of the program.
  • path search utilities, to locate a package in the go search path.

Index

Examples

Constants

View Source
const (
	// DefaultFileMode is the mode reported by a file with no [MapFile.Mode] of its own.
	DefaultFileMode fs.FileMode = 0o444

	// DefaultDirMode is the mode reported by the directories of a [MapFS].
	DefaultDirMode fs.FileMode = fs.ModeDir | 0o555
)

Default modes of the entries of a MapFS.

A MapFS is read-only, so its files and directories are readable and never writable.

View Source
const GOPATHKey = "GOPATH"

GOPATHKey is the name of the environment variable that holds the go search path.

View Source
const OpaqueDirsAll = "*"

OpaqueDirsAll declares that a file system owns every directory it holds, except its root.

It is the only pattern recognized by NewOpaqueFS: directories are otherwise matched by their exact name.

Variables

This section is empty.

Functions

func FindInGoSearchPath deprecated

func FindInGoSearchPath(pkg string) string

FindInGoSearchPath finds a package in $GOPATH and $GOROOT.

It returns an empty string when the package is not found.

Deprecated: this function is no longer relevant with modern go. It uses runtime.GOROOT under the hood, which is deprecated as of go1.24.

func FindInSearchPath

func FindInSearchPath(searchPath, pkg string) string

FindInSearchPath finds a package in a list of search paths.

searchPath lists directories separated by the OS path separator, in the form accepted by filepath.SplitList. Each directory is probed for a src/pkg subdirectory.

It returns the first match, with symlinks resolved, or an empty string when the package is not found in any of the directories.

func FromRawMap added in v0.29.0

func FromRawMap(raw map[string][]byte) map[string]MapFile

FromRawMap builds the files of a MapFS from raw contents, leaving every metadata field to its preset.

It is the shortest way to a MapFS when all the caller holds is bytes:

mapFS, err := NewMapFS(FromRawMap(map[string][]byte{
	"folder/file1": raw1,
	"folder/file2": raw2,
}))

func FullGoSearchPath deprecated

func FullGoSearchPath() string

FullGoSearchPath returns the search paths in which a package may be found.

It joins $GOPATH, which defaults to $HOME/go when unset, with runtime.GOROOT. The two are separated by a colon, so the result is not usable on windows.

Deprecated: this function is no longer relevant with modern go. It uses runtime.GOROOT under the hood, which is deprecated as of go1.24.

func MustSub added in v0.29.0

func MustSub(fsys fs.FS, dir string) fs.FS

MustSub re-roots a file system at one of its directories, and panics when it cannot.

It is fs.Sub for the cases where the directory is a constant of the program, such as a folder of an embed.FS assembled at initialization time: there, a failure means the program is wrong, not that its input is.

Use fs.Sub itself whenever the directory comes from the outside, such as a flag, a configuration file or a request, so that an invalid one is reported rather than fatal.

It is meant to be composed inline:

assets := NewOverlayFS(
	MustSub(embedded, "templates"),
	MustSub(embedded, "templates/contrib/mine"),
)

Types

type File

type File struct {
	Data   multipart.File
	Header *multipart.FileHeader
}

File represents an uploaded file.

Data holds the payload, and Header the multipart metadata. File implements io.ReadCloser by delegating both methods to Data.

The zero File is not usable: File.Read and File.Close both panic when Data is nil.

func (*File) Close

func (f *File) Close() error

Close closes the payload.

func (*File) Read

func (f *File) Read(p []byte) (n int, err error)

Read reads bytes from the payload.

type FileReaderFS added in v0.29.0

type FileReaderFS struct {
	fs.FS
}

FileReaderFS makes a fs.FS into a fs.ReadFileFS, with a FileReaderFS.ReadFile method.

func NewFileReaderFS added in v0.29.0

func NewFileReaderFS(base fs.FS) *FileReaderFS

NewFileReaderFS transforms a fs.FS into a fs.ReadFileFS.

func (*FileReaderFS) ReadFile added in v0.29.0

func (f *FileReaderFS) ReadFile(name string) ([]byte, error)

ReadFile reads the named file from the base file system and returns its content.

type GlobOsFS added in v0.29.0

type GlobOsFS struct {
	*OsFS
}

GlobOsFS is an OsFS that also implements fs.GlobFS, with a GlobOsFS.Glob method.

func NewGlobOsFS added in v0.29.0

func NewGlobOsFS() *GlobOsFS

NewGlobOsFS is like NewReadOnlyOsFS, augmented to match the fs.GlobFS interface.

func (*GlobOsFS) Glob added in v0.29.0

func (f *GlobOsFS) Glob(pattern string) ([]string, error)

Glob returns the names matching pattern, sorted in lexical order.

It returns a nil slice and no error when nothing matches, and path.ErrBadPattern when the pattern is malformed.

type MapFS added in v0.29.0

type MapFS struct {
	// contains filtered or unexported fields
}

MapFS is a read-only in-memory fs.FS, built from a map of file names to content.

It is intended for the cases where the files to serve are held in memory rather than on disk: an overlay assembled from raw bytes, assets that a configuration provides, or a fixture in a test.

Names are slash-separated paths, as accepted by fs.ValidPath. NewMapFS cleans them, so a name may be given with a leading "./" or "/", or with redundant elements. A name that remains invalid once cleaned, such as one climbing above the root, is reported as an error rather than dropped.

Separators are never translated, so that the same input yields the same file system on every platform: a caller holding os paths converts them with path/filepath.ToSlash beforehand.

Directories are implied by the names of the files, and are indexed once, when the file system is built: a name holds a file, and every one of its parents holds a directory. The root "." always exists, even when the file system holds no file at all.

MapFS implements fs.FS, fs.ReadFileFS, fs.StatFS and fs.ReadDirFS, but not fs.GlobFS: fs.Glob resolves against it all the same, through MapFS.ReadDir.

func NewMapFS added in v0.29.0

func NewMapFS(files map[string]MapFile) (*MapFS, error)

NewMapFS builds an in-memory file system from a map of file names to content.

Names are normalized, and reported as an error when they remain invalid, or when the same name is held both as a file and as the parent directory of another one. A MapFile left with a zero MapFile.Mode reports DefaultFileMode.

The map is copied, so adding or removing an entry afterwards leaves the file system alone. The contents are not: a caller that writes to a MapFile.Data slice it still holds changes what the file system serves. Hand over a slice nothing else keeps, or copy it first.

Example

ExampleNewMapFS shows how to serve files held in memory, and stack them on top of another file system as an overlay.

The map holds only what it overrides or adds: a directory of the base still reports the files the base holds beside them.

package main

import (
	"fmt"
	"io/fs"
	"testing/fstest"

	"github.com/go-openapi/swag/fileutils"
)

func main() {
	shipped := fstest.MapFS{
		"templates/index.html": &fstest.MapFile{Data: []byte("<h1>index</h1>")},
		"templates/about.html": &fstest.MapFile{Data: []byte("<h1>about</h1>")},
	}

	// templates that a configuration provides, as raw bytes
	configured, err := fileutils.NewMapFS(fileutils.FromRawMap(map[string][]byte{
		"templates/index.html":   []byte("<h1>configured</h1>"),
		"templates/contact.html": []byte("<h1>contact</h1>"),
	}))
	if err != nil {
		fmt.Println("error:", err)

		return
	}

	assets := fileutils.NewOverlayFS(shipped, configured)

	index, err := fs.ReadFile(assets, "templates/index.html")
	if err != nil {
		fmt.Println("error:", err)

		return
	}
	fmt.Printf("index.html: %s\n", index)

	entries, err := fs.ReadDir(assets, "templates")
	if err != nil {
		fmt.Println("error:", err)

		return
	}

	fmt.Println("templates:")
	for _, entry := range entries {
		fmt.Println("  -", entry.Name())
	}

}
Output:
index.html: <h1>configured</h1>
templates:
  - about.html
  - contact.html
  - index.html

func (*MapFS) Open added in v0.29.0

func (f *MapFS) Open(name string) (fs.File, error)

Open opens the named file or directory.

Opening a directory yields a fs.ReadDirFile reporting the same entries as MapFS.ReadDir.

func (*MapFS) ReadDir added in v0.29.0

func (f *MapFS) ReadDir(name string) ([]fs.DirEntry, error)

ReadDir lists the named directory, with its entries sorted by file name.

func (*MapFS) ReadFile added in v0.29.0

func (f *MapFS) ReadFile(name string) ([]byte, error)

ReadFile reads the named file and returns a copy of its content.

func (*MapFS) Stat added in v0.29.0

func (f *MapFS) Stat(name string) (fs.FileInfo, error)

Stat returns the fs.FileInfo of the named file or directory.

type MapFile added in v0.29.0

type MapFile struct {
	// Data is the content of the file.
	Data []byte

	// Mode is the file mode reported by [fs.FileInfo.Mode]. Zero means [DefaultFileMode].
	Mode fs.FileMode

	// ModTime is the modification time reported by [fs.FileInfo.ModTime].
	//
	// The zero value is left as is, so that a [MapFS] built from the same input twice
	// reports the same metadata.
	ModTime time.Time

	// Sys is the opaque value reported by [fs.FileInfo.Sys].
	Sys any
}

MapFile is a file held by a MapFS.

Only MapFile.Data is required. NewMapFS fills the remaining fields with presets when they are left to their zero value.

type OpaqueDirFS added in v0.29.0

type OpaqueDirFS interface {
	fs.FS

	// IsOpaqueDir reports whether a directory was declared as entirely owned by this file system.
	//
	// It answers from the declarations given to [NewOpaqueFS], and does not check that the file
	// system holds the directory: a name declared opaque reports true either way.
	// [OverlayFS] consults it only for a layer that holds the directory.
	IsOpaqueDir(name string) bool
}

OpaqueDirFS is a file system that entirely owns some of its directories.

OverlayFS neither merges an opaque directory with the layers below it, nor resolves any name under it against them: what the owning layer holds replaces what they hold under the same name.

OpaqueFS is the implementation provided by this package.

type OpaqueFS added in v0.29.0

type OpaqueFS struct {
	fs.FS
	// contains filtered or unexported fields
}

OpaqueFS makes a fs.FS into an OpaqueDirFS, by declaring the directories that it owns.

func NewOpaqueFS added in v0.29.0

func NewOpaqueFS(base fs.FS, dirs ...string) *OpaqueFS

NewOpaqueFS declares the directories that a file system entirely owns, so that they shadow the layers below when it is stacked in an OverlayFS.

Directories are slash-separated paths, as accepted by fs.ValidPath, and are cleaned. Declaring "." makes the whole file system opaque, down from its root. Declaring OpaqueDirsAll makes every directory opaque but the root, so that the root still merges and the layers below keep contributing what they hold beside it.

Declaring a directory that this file system does not hold changes nothing once it is stacked. OverlayFS skips a layer that does not hold the directory before it consults opacity, so the layers below keep resolving that directory and everything under it.

Example

ExampleNewOpaqueFS shows how an overlay may take a directory over entirely, instead of merging its entries with the layers below.

The deployment owns "templates", so the templates shipped with the program are neither listed nor readable. The rest of the tree keeps merging, so "config.yaml" still resolves.

package main

import (
	"fmt"
	"io/fs"
	"testing/fstest"

	"github.com/go-openapi/swag/fileutils"
)

func main() {
	shipped := fstest.MapFS{
		"config.yaml":          &fstest.MapFile{Data: []byte("theme: default")},
		"templates/index.html": &fstest.MapFile{Data: []byte("<h1>index</h1>")},
		"templates/about.html": &fstest.MapFile{Data: []byte("<h1>about</h1>")},
	}

	// this deployment provides the whole set of templates, and wants no other
	site := fstest.MapFS{
		"templates/index.html": &fstest.MapFile{Data: []byte("<h1>home</h1>")},
	}

	assets := fileutils.NewOverlayFS(shipped, fileutils.NewOpaqueFS(site, "templates"))

	entries, err := fs.ReadDir(assets, "templates")
	if err != nil {
		fmt.Println("error:", err)

		return
	}

	fmt.Println("templates:")
	for _, entry := range entries {
		fmt.Println("  -", entry.Name())
	}

	// the shipped template is hidden, not merely shadowed in the listing
	_, err = fs.ReadFile(assets, "templates/about.html")
	fmt.Println("about.html:", err)

	// the directories that are not owned keep merging
	config, err := fs.ReadFile(assets, "config.yaml")
	if err != nil {
		fmt.Println("error:", err)

		return
	}
	fmt.Printf("config.yaml: %s\n", config)

}
Output:
templates:
  - index.html
about.html: open templates/about.html: file does not exist
config.yaml: theme: default

func (*OpaqueFS) IsOpaqueDir added in v0.29.0

func (f *OpaqueFS) IsOpaqueDir(name string) bool

IsOpaqueDir reports whether a directory was declared as entirely owned by this file system.

It answers from the declarations given to NewOpaqueFS, and does not check that the file system holds the directory: a name declared opaque reports true either way. OverlayFS consults it only for a layer that holds the directory.

func (*OpaqueFS) ReadDir added in v0.29.0

func (f *OpaqueFS) ReadDir(name string) ([]fs.DirEntry, error)

ReadDir lists a directory of the wrapped file system.

func (*OpaqueFS) ReadFile added in v0.29.0

func (f *OpaqueFS) ReadFile(name string) ([]byte, error)

ReadFile reads a file from the wrapped file system.

func (*OpaqueFS) Stat added in v0.29.0

func (f *OpaqueFS) Stat(name string) (fs.FileInfo, error)

Stat returns the fs.FileInfo of a name in the wrapped file system.

type OsFS added in v0.29.0

type OsFS struct {
}

OsFS exposes package os features as an fs.FS, without having to use os.Root.

Existing alternatives from the standard library are os.DirFS, which requires a base directory, and os.Root.FS, which requires a root. OsFS is intended to be used when none of these alternatives are workable, that is when the caller does not know which root it should run in.

Names are passed to the os package unchanged, so OsFS accepts absolute and relative paths, which a conforming fs.FS rejects. It offers no containment: every file that the process may read is reachable.

OsFS implements fs.FS, fs.ReadFileFS and fs.ReadDirFS.

func NewReadOnlyOsFS added in v0.29.0

func NewReadOnlyOsFS() *OsFS

NewReadOnlyOsFS builds an OsFS, a read-only view of the os file system.

func (*OsFS) Open added in v0.29.0

func (f *OsFS) Open(name string) (fs.File, error)

Open opens the named file for reading.

func (*OsFS) ReadDir added in v0.29.0

func (f *OsFS) ReadDir(name string) ([]fs.DirEntry, error)

ReadDir reads the named directory and returns its entries sorted by file name.

func (*OsFS) ReadFile added in v0.29.0

func (f *OsFS) ReadFile(name string) ([]byte, error)

ReadFile reads the named file and returns its content.

type OverlayFS added in v0.29.0

type OverlayFS struct {
	// contains filtered or unexported fields
}

OverlayFS is a read-only fs.FS that stacks overlays on top of a base file system.

A name is resolved in the topmost layer that holds it, then down to the base. See NewOverlayFS for the order in which layers are stacked. When the name is absent from all layers, every method returns a fs.PathError that reports the name and matches fs.ErrNotExist.

OverlayFS implements fs.FS, fs.ReadFileFS, fs.StatFS and fs.ReadDirFS, but not fs.GlobFS. A directory returns an error when the resolved layer does not support reading directories.

Directories are merged: a directory reports the union of the entries held by every layer, and the topmost layer wins whenever the same name is held by several of them. A layer may claim a directory for itself with NewOpaqueFS, which stops the merge and hides everything the lower layers hold under that directory.

func NewOverlayFS added in v0.29.0

func NewOverlayFS(base fs.FS, overlays ...fs.FS) *OverlayFS

NewOverlayFS builds an overlay file system from a base file system and a list of overlays.

Overlays are stacked in the order in which they are provided: the last one sits on top and is resolved first, then the preceding ones in reverse order. The base is always resolved last.

An empty list of overlays yields a file system that resolves against the base alone.

Example

ExampleNewOverlayFS shows how to patch a set of assets shipped with a program, with a site-specific overlay that only holds what it changes.

A file resolves in the topmost layer that holds it, so the overlay overrides the assets without having to repeat them. A directory reports the entries of every layer at once, so the templates of both layers are listed together.

package main

import (
	"fmt"
	"io/fs"
	"testing/fstest"

	"github.com/go-openapi/swag/fileutils"
)

func main() {
	// the assets shipped with the program: in a real program, this could be an embed.FS
	shipped := fstest.MapFS{
		"config.yaml":          &fstest.MapFile{Data: []byte("theme: default")},
		"templates/index.html": &fstest.MapFile{Data: []byte("<h1>index</h1>")},
		"templates/about.html": &fstest.MapFile{Data: []byte("<h1>about</h1>")},
	}

	// a deployment overrides the configuration, and adds a template of its own
	site := fstest.MapFS{
		"config.yaml":            &fstest.MapFile{Data: []byte("theme: dark")},
		"templates/contact.html": &fstest.MapFile{Data: []byte("<h1>contact</h1>")},
	}

	assets := fileutils.NewOverlayFS(shipped, site)

	// the overlay wins for a file that both layers hold
	config, err := fs.ReadFile(assets, "config.yaml")
	if err != nil {
		fmt.Println("error:", err)

		return
	}
	fmt.Printf("config.yaml: %s\n", config)

	// a file that only the shipped assets hold still resolves
	about, err := fs.ReadFile(assets, "templates/about.html")
	if err != nil {
		fmt.Println("error:", err)

		return
	}
	fmt.Printf("about.html: %s\n", about)

	// the directory reports the templates of both layers, sorted by file name
	entries, err := fs.ReadDir(assets, "templates")
	if err != nil {
		fmt.Println("error:", err)

		return
	}

	fmt.Println("templates:")
	for _, entry := range entries {
		fmt.Println("  -", entry.Name())
	}

}
Output:
config.yaml: theme: dark
about.html: <h1>about</h1>
templates:
  - about.html
  - contact.html
  - index.html

func (*OverlayFS) Open added in v0.29.0

func (f *OverlayFS) Open(name string) (fs.File, error)

Open opens a file, resolving layers from the topmost overlay down to the base.

Opening a directory yields a fs.ReadDirFile that reports the same entries as OverlayFS.ReadDir, so that a merged directory reads alike either way.

func (*OverlayFS) ReadDir added in v0.29.0

func (f *OverlayFS) ReadDir(name string) ([]fs.DirEntry, error)

ReadDir lists a directory, resolving layers from the topmost overlay down to the base.

The entries of every layer holding the directory are merged, sorted by file name, and a name held by several layers is reported by the topmost of them. The merge stops at the topmost layer that owns the directory, as declared by NewOpaqueFS.

func (*OverlayFS) ReadFile added in v0.29.0

func (f *OverlayFS) ReadFile(name string) ([]byte, error)

ReadFile reads a file, resolving layers from the topmost overlay down to the base.

func (*OverlayFS) Stat added in v0.29.0

func (f *OverlayFS) Stat(name string) (fs.FileInfo, error)

Stat returns the fs.FileInfo of a file, resolving layers from the topmost overlay down to the base.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL