Documentation
¶
Overview ¶
Package memfs provide a library for mapping file system into memory and/or to embed it inside go source file.
Usage ¶
The first step is to create new instance of memfs using New function. The following example embed all files inside directory named "include" or any files with extension ".css", ".html", and ".js"; but exclude any files inside directory named "exclude".
opts := &Options{ Root: "./mydir", Includes: []string{ `.*/include`, `.*\.(css|html|js)$`, }, Excludes: []string{ `.*/exclude`, }, } mfs, err := memfs.New(opts)
By default only file with size less or equal to 5 MB will be included in memory. To increase the default size set the memfs.Options.MaxFileSize (in bytes). For example, to change maximum file size to 10 MB,
var opts = memfs.Options{ MaxFileSize: 1024 * 1024 * 10, }
Later, if we want to get the file from memory, call MemFS.Get which will return the Node object with content can be accessed from field "Content". If file size is larger than maximum, the content will need to be read manually (as long as the file exist on system),
node, err := mfs.Get("/") if err != nil { // Handle file not exist } if node.mode.IsDir() { // Handle directory. } if node.Content == nil { // Handle large file. node.V, err = os.ReadFile(child.SysPath) } // Do something with content of file system.
Go embed ¶
The memfs package support embedding the files into Go generated source code. After we create the MemFS instance, call the memfs.GoEmbed method to dump all directory and files into Go source code.
First, define global variable as container for all files to be embedded in the same package as generated code,
package mypackage var myFS *memfs.MemFS
Next, create new instance of MemFS with memfs.Options.Embed is set,
var opts = &Options{ Embed: EmbedOptions{ PackageName: `mypackage`, VarName: `myFS`, GoFileName: `mypackage/embed.go`, }, Root: `./mydir`, Includes: []string{ `.*/include`, `.*\.(css|html|js)$`, }, Excludes: []string{ `.*/exclude`, }, } var mfs *memfs.MemFS mfs, err = memfs.New(opts) ...
Finally, call method MemFS.GoEmbed from the instance,
mfs.GoEmbed()
This method will create Go file "mypackage/embed.go" that contains all path and content of files inside the mfs instance, under package named "mypackage". Code that can read "myFS" variable then can access any files using MemFS.Get method, with "/" as root prefix (not "./mydir").
Comparison with builtin go:embed ¶
This section list the disadvantages of "go:embed" directive.
The memfs package created on November 2018, based on my experiences maintains the fork of go-bindata project. The "go:embed" directive introduced into Go tools since Go version 1.16, released February 2021, three years after the first release of memfs package.
Given the following directory structure,
module-root/ +-- cmd/prog/main.go +-- _content/ +-- index.adoc +-- index.html +-- static/ +-- index.png +-- index.png
We want to embed the directory "_content" but only html files and all files inside the "static/" directory.
Cons #1: The "go:embed" only works if files or directory to be embedded is in the same parent directory.
The "go:embed" directive define in "cmd/prog/main.go" will not able to embed files in their parent. The following code will not compile,
//go:embed ../../_content/*.html //go:embed ../../_content/static var contentFS embed.FS // go build output, // pattern ../../_content/*.html: invalid pattern syntax
If we remove the ".." and execute "go build" from module-root, it will still not compile,
//go:embed _content/*.html //go:embed _content/static var contentFS embed.FS // go build or run output, // pattern _content/*.html: no matching files found
The only solution is to create and export the variable "ContentFS" in the same parent directory as "_content".
The memfs package does not have this limitation. As long as the Go commands are executed from the module-root directory, you can define the variable in any packages.
Cons #2: Accessing the embedded file require the original path.
Let say we have embeded the "_content" directory using the following syntax,
//go:embed _content/*.html //go:embed _content/static var ContentFS embed.FS
To access the file "index.html" you need to pass their full path, in this case "_content/index.html". The path "_content" leaked to the parent FS and not portable.
In the memfs package, the content of [Options.Root] directory can be accessed with "/", so it would become "/index.html". This design allow flexibility and consistency between modules and packages. If an external, third-party package accept the MemFS instance and the first thing they do is to read all contents of "/" directory, the caller can embed any path without have specific prefix or name.
Case example, when we embed SQL files for migration under directory "db/migration" using the "go:embed" directive,
//go:embed db/migration/*.sql var DBMigrationFS embed.FS
and then call the Migrate function, it cannot found any ".sql" files inside the "/" directory because the files is stored under "db/migration/" prefix.
Cons #3: No development mode.
Let say we run our server that served the content from FS instance. If we changes the html files, and refresh the browser, the new content will not reflected because it serve the content on the first embed.
The memfs package have [Options.TryDirect] that bypass file in memory and read directly to the file system. This allow quick development when changes only template or non-code files.
Index ¶
- Constants
- type EmbedOptions
- type MemFS
- func (mfs *MemFS) AddChild(parent *Node, fi os.FileInfo) (child *Node, err error)
- func (mfs *MemFS) AddFile(internalPath, externalPath string) (node *Node, err error)
- func (mfs *MemFS) Get(path string) (node *Node, err error)
- func (mfs *MemFS) GoEmbed() (err error)
- func (mfs *MemFS) Init() (err error)
- func (mfs *MemFS) ListNames() (paths []string)
- func (mfs *MemFS) MarshalJSON() ([]byte, error)
- func (mfs *MemFS) Merge(sub *MemFS)
- func (mfs *MemFS) MustGet(path string) (node *Node)
- func (mfs *MemFS) Open(path string) (http.File, error)
- func (mfs *MemFS) Remount() (err error)
- func (mfs *MemFS) RemoveChild(parent *Node, child *Node) (removed *Node)
- func (mfs *MemFS) Search(words []string, snippetLen int) (results []SearchResult)
- func (mfs *MemFS) StopWatch()
- func (mfs *MemFS) Update(node *Node, newInfo os.FileInfo)
- func (mfs *MemFS) Watch(watchopts WatchOptions) (changes <-chan []*Node, err error)
- type Node
- func (node *Node) AddChild(child *Node)
- func (node *Node) Child(name string) (cnode *Node)
- func (node *Node) Close() error
- func (node *Node) GenerateIndexHTML()
- func (node *Node) IsDir() bool
- func (node *Node) JSON(depth int, withContent, withModTime bool) (rawjson []byte, err error)
- func (node *Node) MarshalJSON() ([]byte, error)
- func (node *Node) ModTime() time.Time
- func (node *Node) Mode() os.FileMode
- func (node *Node) Name() string
- func (node *Node) Read(p []byte) (n int, err error)
- func (node *Node) Readdir(count int) (fis []os.FileInfo, err error)
- func (node *Node) Save(content []byte) (err error)
- func (node *Node) Seek(offset int64, whence int) (int64, error)
- func (node *Node) SetModTime(modTime time.Time)
- func (node *Node) SetModTimeUnix(seconds, nanoSeconds int64)
- func (node *Node) SetMode(mode os.FileMode)
- func (node *Node) SetName(name string)
- func (node *Node) SetSize(size int64)
- func (node *Node) Size() int64
- func (node *Node) Stat() (os.FileInfo, error)
- func (node *Node) Sys() any
- func (node *Node) Update(newInfo os.FileInfo, maxFileSize int64) (err error)
- func (node *Node) UpdateContent(maxFileSize int64) (err error)
- type Options
- type PathNode
- type SearchResult
- type WatchOptions
Examples ¶
Constants ¶
const DefaultEmbedGoFileName = `memfs_generate.go`
DefaultEmbedGoFileName default file output for GoEmbed.
const DefaultEmbedPackageName = `main`
DefaultEmbedPackageName default package name for GoEmbed.
const DefaultEmbedVarName = `memFS`
DefaultEmbedVarName default variable name for GoEmbed.
const DefaultWatchFile = `.memfs_rescan`
DefaultWatchFile define default file name to be watch for changes. Any update to this file will trigger rescan on the memfs tree.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type EmbedOptions ¶
type EmbedOptions struct { // CommentHeader define optional comment to be added to the header of // generated file, for example copyright holder and/or license. // The string value is not checked, whether it's a comment or not, it // will rendered as is. // // Due to templating, the value MUST be set using raw // string literal syntax “, NOT "". CommentHeader string // The generated package name for GoEmbed(). // If its not defined it will be default to "main". PackageName string // VarName is the global variable name with type *memfs.MemFS which // will be initialized by generated Go source code on init(). // If its empty it will default to "memFS". VarName string // GoFileName the path to Go generated file, where the file // system will be embedded. // If its not defined it will be default to "memfs_generate.go" // in current directory from where its called. GoFileName string // WithoutModTime if its true, the modification time for all // files and directories are not stored inside generated code, instead // all files will use the current time when the program is running. WithoutModTime bool }
EmbedOptions define an options for GoEmbed.
type MemFS ¶
type MemFS struct { PathNodes *PathNode Root *Node Opts *Options // contains filtered or unexported fields }
MemFS contains directory tree of file system in memory.
func New ¶
New create and initialize new memory file system from directory Root using list of regular expresssion for Including or Excluding files.
Example ¶
package main import ( "fmt" "log" "git.sr.ht/~shulhan/pakakeh.go/lib/memfs" ) func main() { /** Let say we have the "testdata" directory, testdata/ ├── direct │ └── add │ ├── file │ └── file2 ├── exclude │ ├── dir │ ├── index-link.css -> ../index.css │ ├── index-link.html -> ../index.html │ └── index-link.js -> ../index.js ├── include │ ├── dir │ ├── index.css -> ../index.css │ ├── index.html -> ../index.html │ └── index.js -> ../index.js ├── index.css ├── index.html ├── index.js └── plain Assume that we want to embed all files with extension .css, .html, and .js only; but not from directory "exclude". We can create the Options like below, */ opts := &memfs.Options{ Root: `./testdata`, Includes: []string{`.*/include`, `.*\.(css|html|js)$`}, Excludes: []string{`.*/exclude`}, } mfs, err := memfs.New(opts) if err != nil { log.Fatal(err) } node, err := mfs.Get("/index.html") if err != nil { log.Fatal(err) } fmt.Printf("Content of /index.html: %s", node.Content) fmt.Printf("List of embedded files: %+v\n", mfs.ListNames()) _, err = mfs.Get("/exclude/index.html") if err != nil { fmt.Println(`Error:`, err) } }
Output: Content of /index.html: <html></html> List of embedded files: [/ /direct /direct/add /include /include/dir /include/index.css /include/index.html /include/index.js /index.css /index.html /index.js] Error: Get "/exclude/index.html": file does not exist
func (*MemFS) AddChild ¶
AddChild add FileInfo fi as new child of parent node.
It will return nil without an error if,
- the system path of parent+fi.Name() is excluded by one of Options.Excludes pattern, or
- fi is symlink to not existen node.
func (*MemFS) AddFile ¶
AddFile add the external file directly as internal file. If the internal file is already exist it will be replaced. Any directories in the internal path will be generated automatically if its not exist.
func (*MemFS) Get ¶
Get the node representation of file in memory. If path is not exist it will return fs.ErrNotExist.
func (*MemFS) GoEmbed ¶
GoEmbed write the tree nodes as Go generated source file. This method assume that the files inside the mfs instance is already up-to-date. If you are not sure, call Remount.
func (*MemFS) Init ¶
Init initialize the MemFS instance. This method provided to initialize MemFS if its Options is set directly, not through New() function.
func (*MemFS) MarshalJSON ¶
MarshalJSON encode the MemFS object into JSON format.
The field that being encoded is the Root node.
func (*MemFS) Merge ¶
Merge other MemFS instance as sub file system.
When Get method called, each sub fs will be evaluated in order of Merge.
func (*MemFS) MustGet ¶
MustGet return the Node representation of file in memory by its path if its exist or nil the path is not exist.
func (*MemFS) Open ¶
Open the named file for reading. This is an alias to Get() method, to make it implement http.FileSystem.
func (*MemFS) Remount ¶
Remount reset the memfs instance to force rescanning the files again from file system.
func (*MemFS) RemoveChild ¶
RemoveChild remove a child on parent, including its map on PathNode. If child is not part if node's childrens it will return nil.
func (*MemFS) Search ¶
func (mfs *MemFS) Search(words []string, snippetLen int) (results []SearchResult)
Search one or more strings in each content of files.
Example ¶
package main import ( "fmt" "log" "git.sr.ht/~shulhan/pakakeh.go/lib/memfs" ) func main() { opts := &memfs.Options{ Root: `./testdata`, } mfs, err := memfs.New(opts) if err != nil { log.Fatal(err) } q := []string{`body`} results := mfs.Search(q, 0) for _, result := range results { fmt.Println(`Path:`, result.Path) fmt.Printf("Snippets: %q\n", result.Snippets) } }
Output: Path: /include/index.css Snippets: ["body {\n}\n"] Path: /exclude/index-link.css Snippets: ["body {\n}\n"] Path: /index.css Snippets: ["body {\n}\n"]
func (*MemFS) StopWatch ¶
func (mfs *MemFS) StopWatch()
StopWatch stop watching for update, from calling Watch.
func (*MemFS) Update ¶
Update the node content and information in memory based on new file information. This method only check if the node name is equal with file name, but it's not checking whether the node is part of memfs (node is parent or have the same Root node).
func (*MemFS) Watch ¶
func (mfs *MemFS) Watch(watchopts WatchOptions) ( changes <-chan []*Node, err error, )
Watch create and start the watchfs/v2.DirWatcher that re-scan the content of Root directory recursively on every memfs.WatchOptions.Interval, triggered by changes on memfs.WatchOptions.File.
The watcher will remove or update the tree and node content automatically if the included files is being deleted, created, or updated.
The returned channel changes return list of Node that has been deleted, created, or updated. To stop watching for update call the MemFS.StopWatch.
Example ¶
var ( opts memfs.Options err error ) opts.Root, err = os.MkdirTemp(``, `ExampleMemFS_Watch`) if err != nil { log.Fatal(err) } defer func() { _ = os.RemoveAll(opts.Root) }() var mfs *memfs.MemFS mfs, err = memfs.New(&opts) if err != nil { log.Fatal(err) } var fileToWatch = filepath.Join(opts.Root, memfs.DefaultWatchFile) var watchOpts = memfs.WatchOptions{ FileWatcherOptions: watchfs.FileWatcherOptions{ File: fileToWatch, Interval: 200 * time.Millisecond, }, Verbose: true, } var changesq <-chan []*memfs.Node changesq, err = mfs.Watch(watchOpts) if err != nil { log.Fatal(err) } var testFile = filepath.Join(opts.Root, `file`) err = os.WriteFile(testFile, []byte(`dummy content`), 0600) if err != nil { log.Fatal(err) } err = os.WriteFile(fileToWatch, []byte(`x`), 0600) if err != nil { log.Fatal(err) } <-changesq _, err = mfs.Get(`/file`) if err != nil { log.Fatal(err) } err = os.Remove(testFile) if err != nil { log.Fatal(err) } err = os.WriteFile(fileToWatch, []byte(`xx`), 0600) if err != nil { log.Fatal(err) } <-changesq mfs.StopWatch() <-changesq
Output: MemFS: file created: "/file" MemFS: file deleted: "/file"
type Node ¶
type Node struct { Parent *Node // Pointer to parent directory. SysPath string // The original file path in system. Path string // Absolute file path in memory. ContentType string // File type per MIME, for example "application/json". GenFuncName string // The function name for embedded Go code. Childs []*Node // List of files in directory. Content []byte // Content of file. // contains filtered or unexported fields }
Node represent a single file.
This Node implement os.FileInfo and http.File.
func NewNode ¶
NewNode create a new node based on file information "fi".
The parent parameter is required to allow valid system path generated for new node.
If maxFileSize is greater than zero, the file content and its type will be saved in node as Content and ContentType.
func (*Node) GenerateIndexHTML ¶
func (node *Node) GenerateIndexHTML()
GenerateIndexHTML generate simple directory listing as HTML for all childs in this node. This method is only applicable if node is a directory.
func (*Node) JSON ¶
JSON encode the Node into JSON. This method provides an alternative to MarshalJSON with granular options.
The depth set the level of childs to be encoded. The depth=0 only encode the Root Node itself (with its childs), depth=1 encode the Root node and its subdirectories, and so on.
If the withContent set to true, all of the Node content will not be included in the output.
If the withModTime is set to true, all of the node ModTime will not be included in the output.
func (*Node) MarshalJSON ¶
MarshalJSON encode the node into JSON format. If the node is a file it will return the content of file; otherwise it will return the node with list of childs, but not including childs of childs.
func (*Node) Readdir ¶
Readdir reads the contents of the directory associated with file and returns a slice of up to n FileInfo values, as would be returned by os.Stat.
func (*Node) Seek ¶
Seek sets the offset for the next Read offset, interpreted according to whence: SeekStart means relative to the start of the file, SeekCurrent means relative to the current offset, and SeekEnd means relative to the end. Seek returns the new offset relative to the start of the file and an error, if any.
func (*Node) SetModTime ¶
SetModTime set the file modification time.
func (*Node) SetModTimeUnix ¶
SetModTimeUnix set the file modification time using seconds and nanoseconds since January 1, 1970 UTC.
func (*Node) Update ¶
Update the node metadata or content based on new file information.
The newInfo parameter is optional, if its nil, it will read the file information based on node's SysPath.
The maxFileSize parameter is also optional. If its negative, the node content will not be updated. If its zero, it will default to 5 MB.
There are two possible changes that will happen: its either change on mode or change on content (size and modtime). Change on mode will not affect the content of node.
func (*Node) UpdateContent ¶ added in v0.59.0
UpdateContent read the content of file.
type Options ¶
type Options struct { // Embed options for GoEmbed method. Embed EmbedOptions // Root define the path to directory where its contents will be mapped // to memory or to be embedded as Go source code using GoEmbed. Root string // The includes and excludes pattern applied relative to the system // path. // The Excludes patterns will be applied first before the Includes. // If the path is not excluded and Includes is empty, it will be // assumed as included. Includes []string Excludes []string // MaxFileSize define maximum file size that can be stored on memory. // The default value is 5 MB. // If its value is negative, the content of file will not be mapped to // memory, the MemFS will behave as directory tree. MaxFileSize int64 // TryDirect define a flag to bypass file in memory. // If its true, any call to Get will try direct read to file system. // This flag has several use cases. // First, to test serving file system directly from disk during // development. // Second, to combine embedded MemFS instance with non-embedded // instance. // One is reading content from memory, one is reading content from // disk directly. TryDirect bool // contains filtered or unexported fields }
Options to create and initialize the MemFS.
type PathNode ¶
type PathNode struct {
// contains filtered or unexported fields
}
PathNode contains a mapping between path and Node.
func (*PathNode) MarshalJSON ¶
MarshalJSON encode the PathNode into JSON value.
type SearchResult ¶
SearchResult contains the result of searching where the Path will be filled with absolute path of file system in memory and the Snippet will filled with part of the text before and after the search string.
type WatchOptions ¶
type WatchOptions struct { watchfs.FileWatcherOptions // Verbose if true print the file changes information to stdout. Verbose bool }
WatchOptions define an options for the MemFS Watch method.
If the watchfs.FileWatcherOptions.File is empty it will default to DefaultWatchFile inside the memfs.Options.Root. The watchfs.FileWatcherOptions.Interval must be greater than 10 milliseconds, otherwise it will default to 5 seconds.