Source File
embed.go
Belonging Package
embed
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package embed provides access to files embedded in the running Go program.
//
// Go source files that import "embed" can use the //go:embed directive
// to initialize a variable of type string, []byte, or [FS] with the contents of
// files read from the package directory or subdirectories at compile time.
//
// For example, here are three ways to embed a file named hello.txt
// and then print its contents at run time.
//
// Embedding one file into a string:
//
// import _ "embed"
//
// //go:embed hello.txt
// var s string
// print(s)
//
// Embedding one file into a slice of bytes:
//
// import _ "embed"
//
// //go:embed hello.txt
// var b []byte
// print(string(b))
//
// Embedded one or more files into a file system:
//
// import "embed"
//
// //go:embed hello.txt
// var f embed.FS
// data, _ := f.ReadFile("hello.txt")
// print(string(data))
//
// # Directives
//
// A //go:embed directive above a variable declaration specifies which files to embed,
// using one or more path.Match patterns.
//
// The directive must immediately precede a line containing the declaration of a single variable.
// Only blank lines and ‘//’ line comments are permitted between the directive and the declaration.
//
// The type of the variable must be a string type, or a slice of a byte type,
// or [FS] (or an alias of [FS]).
//
// For example:
//
// package server
//
// import "embed"
//
// // content holds our static web server content.
// //go:embed image/* template/*
// //go:embed html/index.html
// var content embed.FS
//
// The Go build system will recognize the directives and arrange for the declared variable
// (in the example above, content) to be populated with the matching files from the file system.
//
// The //go:embed directive accepts multiple space-separated patterns for
// brevity, but it can also be repeated, to avoid very long lines when there are
// many patterns. The patterns are interpreted relative to the package directory
// containing the source file. The path separator is a forward slash, even on
// Windows systems. Patterns may not contain ‘.’ or ‘..’ or empty path elements,
// nor may they begin or end with a slash. To match everything in the current
// directory, use ‘*’ instead of ‘.’. To allow for naming files with spaces in
// their names, patterns can be written as Go double-quoted or back-quoted
// string literals.
//
// If a pattern names a directory, all files in the subtree rooted at that directory are
// embedded (recursively), except that files with names beginning with ‘.’ or ‘_’
// are excluded. So the variable in the above example is almost equivalent to:
//
// // content is our static web server content.
// //go:embed image template html/index.html
// var content embed.FS
//
// The difference is that ‘image/*’ embeds ‘image/.tempfile’ while ‘image’ does not.
// Neither embeds ‘image/dir/.tempfile’.
//
// If a pattern begins with the prefix ‘all:’, then the rule for walking directories is changed
// to include those files beginning with ‘.’ or ‘_’. For example, ‘all:image’ embeds
// both ‘image/.tempfile’ and ‘image/dir/.tempfile’.
//
// The //go:embed directive can be used with both exported and unexported variables,
// depending on whether the package wants to make the data available to other packages.
// It can only be used with variables at package scope, not with local variables.
//
// Patterns must not match files outside the package's module, such as ‘.git/*’, symbolic links,
// 'vendor/', or any directories containing go.mod (these are separate modules).
// Patterns must not match files whose names include the special punctuation characters " * < > ? ` ' | / \ and :.
// Matches for empty directories are ignored. After that, each pattern in a //go:embed line
// must match at least one file or non-empty directory.
//
// If any patterns are invalid or have invalid matches, the build will fail.
//
// # Strings and Bytes
//
// The //go:embed line for a variable of type string or []byte can have only a single pattern,
// and that pattern can match only a single file. The string or []byte is initialized with
// the contents of that file.
//
// The //go:embed directive requires importing "embed", even when using a string or []byte.
// In source files that don't refer to [embed.FS], use a blank import (import _ "embed").
//
// # File Systems
//
// For embedding a single file, a variable of type string or []byte is often best.
// The [FS] type enables embedding a tree of files, such as a directory of static
// web server content, as in the example above.
//
// FS implements the [io/fs] package's [FS] interface, so it can be used with any package that
// understands file systems, including [net/http], [text/template], and [html/template].
//
// For example, given the content variable in the example above, we can write:
//
// http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(content))))
//
// template.ParseFS(content, "*.tmpl")
//
// # Tools
//
// To support tools that analyze Go packages, the patterns found in //go:embed lines
// are available in “go list” output. See the EmbedPatterns, TestEmbedPatterns,
// and XTestEmbedPatterns fields in the “go help list” output.
package embed
import (
)
// An FS is a read-only collection of files, usually initialized with a //go:embed directive.
// When declared without a //go:embed directive, an FS is an empty file system.
//
// An FS is a read-only value, so it is safe to use from multiple goroutines
// simultaneously and also safe to assign values of type FS to each other.
//
// FS implements fs.FS, so it can be used with any package that understands
// file system interfaces, including net/http, text/template, and html/template.
//
// See the package documentation for more details about initializing an FS.
type FS struct {
// The compiler knows the layout of this struct.
// See cmd/compile/internal/staticdata's WriteEmbed.
//
// The files list is sorted by name but not by simple string comparison.
// Instead, each file's name takes the form "dir/elem" or "dir/elem/".
// The optional trailing slash indicates that the file is itself a directory.
// The files list is sorted first by dir (if dir is missing, it is taken to be ".")
// and then by base, so this list of files:
//
// p
// q/
// q/r
// q/s/
// q/s/t
// q/s/u
// q/v
// w
//
// is actually sorted as:
//
// p # dir=. elem=p
// q/ # dir=. elem=q
// w # dir=. elem=w
// q/r # dir=q elem=r
// q/s/ # dir=q elem=s
// q/v # dir=q elem=v
// q/s/t # dir=q/s elem=t
// q/s/u # dir=q/s elem=u
//
// This order brings directory contents together in contiguous sections
// of the list, allowing a directory read to use binary search to find
// the relevant sequence of entries.
files *[]file
}
// split splits the name into dir and elem as described in the
// comment in the FS struct above. isDir reports whether the
// final trailing slash was present, indicating that name is a directory.
func split( string) (, string, bool) {
, = stringslite.CutSuffix(, "/")
:= bytealg.LastIndexByteString(, '/')
if < 0 {
return ".", ,
}
return [:], [+1:],
}
var (
_ fs.ReadDirFS = FS{}
_ fs.ReadFileFS = FS{}
)
// A file is a single file in the FS.
// It implements fs.FileInfo and fs.DirEntry.
type file struct {
// The compiler knows the layout of this struct.
// See cmd/compile/internal/staticdata's WriteEmbed.
name string
data string
hash [16]byte // truncated SHA256 hash
}
var (
_ fs.FileInfo = (*file)(nil)
_ fs.DirEntry = (*file)(nil)
)
func ( *file) () string { , , := split(.name); return }
func ( *file) () int64 { return int64(len(.data)) }
func ( *file) () time.Time { return time.Time{} }
func ( *file) () bool { , , := split(.name); return }
func ( *file) () any { return nil }
func ( *file) () fs.FileMode { return .Mode().Type() }
func ( *file) () (fs.FileInfo, error) { return , nil }
func ( *file) () fs.FileMode {
if .IsDir() {
return fs.ModeDir | 0555
}
return 0444
}
func ( *file) () string {
return fs.FormatFileInfo()
}
// dotFile is a file for the root directory,
// which is omitted from the files list in a FS.
var dotFile = &file{name: "./"}
// lookup returns the named file, or nil if it is not present.
func ( FS) ( string) *file {
if !fs.ValidPath() {
// The compiler should never emit a file with an invalid name,
// so this check is not strictly necessary (if name is invalid,
// we shouldn't find a match below), but it's a good backstop anyway.
return nil
}
if == "." {
return dotFile
}
if .files == nil {
return nil
}
// Binary search to find where name would be in the list,
// and then check if name is at that position.
, , := split()
:= *.files
:= sortSearch(len(), func( int) bool {
, , := split([].name)
return > || == && >=
})
if < len() && stringslite.TrimSuffix([].name, "/") == {
return &[]
}
return nil
}
// readDir returns the list of files corresponding to the directory dir.
func ( FS) ( string) []file {
if .files == nil {
return nil
}
// Binary search to find where dir starts and ends in the list
// and then return that slice of the list.
:= *.files
:= sortSearch(len(), func( int) bool {
, , := split([].name)
return >=
})
:= sortSearch(len(), func( int) bool {
, , := split([].name)
return >
})
return [:]
}
// Open opens the named file for reading and returns it as an [fs.File].
//
// The returned file implements [io.Seeker] and [io.ReaderAt] when the file is not a directory.
func ( FS) ( string) (fs.File, error) {
:= .lookup()
if == nil {
return nil, &fs.PathError{Op: "open", Path: , Err: fs.ErrNotExist}
}
if .IsDir() {
return &openDir{, .readDir(), 0}, nil
}
return &openFile{, 0}, nil
}
// ReadDir reads and returns the entire named directory.
func ( FS) ( string) ([]fs.DirEntry, error) {
, := .Open()
if != nil {
return nil,
}
, := .(*openDir)
if ! {
return nil, &fs.PathError{Op: "read", Path: , Err: errors.New("not a directory")}
}
:= make([]fs.DirEntry, len(.files))
for := range {
[] = &.files[]
}
return , nil
}
// ReadFile reads and returns the content of the named file.
func ( FS) ( string) ([]byte, error) {
, := .Open()
if != nil {
return nil,
}
, := .(*openFile)
if ! {
return nil, &fs.PathError{Op: "read", Path: , Err: errors.New("is a directory")}
}
return []byte(.f.data), nil
}
// An openFile is a regular file open for reading.
type openFile struct {
f *file // the file itself
offset int64 // current read offset
}
var (
_ io.Seeker = (*openFile)(nil)
_ io.ReaderAt = (*openFile)(nil)
)
func ( *openFile) () error { return nil }
func ( *openFile) () (fs.FileInfo, error) { return .f, nil }
func ( *openFile) ( []byte) (int, error) {
if .offset >= int64(len(.f.data)) {
return 0, io.EOF
}
if .offset < 0 {
return 0, &fs.PathError{Op: "read", Path: .f.name, Err: fs.ErrInvalid}
}
:= copy(, .f.data[.offset:])
.offset += int64()
return , nil
}
func ( *openFile) ( int64, int) (int64, error) {
switch {
case 0:
// offset += 0
case 1:
+= .offset
case 2:
+= int64(len(.f.data))
}
if < 0 || > int64(len(.f.data)) {
return 0, &fs.PathError{Op: "seek", Path: .f.name, Err: fs.ErrInvalid}
}
.offset =
return , nil
}
func ( *openFile) ( []byte, int64) (int, error) {
if < 0 || > int64(len(.f.data)) {
return 0, &fs.PathError{Op: "read", Path: .f.name, Err: fs.ErrInvalid}
}
:= copy(, .f.data[:])
if < len() {
return , io.EOF
}
return , nil
}
// An openDir is a directory open for reading.
type openDir struct {
f *file // the directory file itself
files []file // the directory contents
offset int // the read offset, an index into the files slice
}
func ( *openDir) () error { return nil }
func ( *openDir) () (fs.FileInfo, error) { return .f, nil }
func ( *openDir) ([]byte) (int, error) {
return 0, &fs.PathError{Op: "read", Path: .f.name, Err: errors.New("is a directory")}
}
func ( *openDir) ( int) ([]fs.DirEntry, error) {
:= len(.files) - .offset
if == 0 {
if <= 0 {
return nil, nil
}
return nil, io.EOF
}
if > 0 && > {
=
}
:= make([]fs.DirEntry, )
for := range {
[] = &.files[.offset+]
}
.offset +=
return , nil
}
// sortSearch is like sort.Search, avoiding an import.
func sortSearch( int, func(int) bool) int {
// Define f(-1) == false and f(n) == true.
// Invariant: f(i-1) == false, f(j) == true.
, := 0,
for < {
:= int(uint(+) >> 1) // avoid overflow when computing h
// i ≤ h < j
if !() {
= + 1 // preserves f(i-1) == false
} else {
= // preserves f(j) == true
}
}
// i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i.
return
}
The pages are generated with Golds v0.7.3. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |