Source file src/embed/embed.go

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package embed provides access to files embedded in the running Go program.
     6  //
     7  // Go source files that import "embed" can use the //go:embed directive
     8  // to initialize a variable of type string, []byte, or [FS] with the contents of
     9  // files read from the package directory or subdirectories at compile time.
    10  //
    11  // For example, here are three ways to embed a file named hello.txt
    12  // and then print its contents at run time.
    13  //
    14  // Embedding one file into a string:
    15  //
    16  //	import _ "embed"
    17  //
    18  //	//go:embed hello.txt
    19  //	var s string
    20  //	print(s)
    21  //
    22  // Embedding one file into a slice of bytes:
    23  //
    24  //	import _ "embed"
    25  //
    26  //	//go:embed hello.txt
    27  //	var b []byte
    28  //	print(string(b))
    29  //
    30  // Embedded one or more files into a file system:
    31  //
    32  //	import "embed"
    33  //
    34  //	//go:embed hello.txt
    35  //	var f embed.FS
    36  //	data, _ := f.ReadFile("hello.txt")
    37  //	print(string(data))
    38  //
    39  // # Directives
    40  //
    41  // A //go:embed directive above a variable declaration specifies which files to embed,
    42  // using one or more path.Match patterns.
    43  //
    44  // The directive must immediately precede a line containing the declaration of a single variable.
    45  // Only blank lines and ‘//’ line comments are permitted between the directive and the declaration.
    46  //
    47  // The type of the variable must be a string type, or a slice of a byte type,
    48  // or [FS] (or an alias of [FS]).
    49  //
    50  // For example:
    51  //
    52  //	package server
    53  //
    54  //	import "embed"
    55  //
    56  //	// content holds our static web server content.
    57  //	//go:embed image/* template/*
    58  //	//go:embed html/index.html
    59  //	var content embed.FS
    60  //
    61  // The Go build system will recognize the directives and arrange for the declared variable
    62  // (in the example above, content) to be populated with the matching files from the file system.
    63  //
    64  // The //go:embed directive accepts multiple space-separated patterns for
    65  // brevity, but it can also be repeated, to avoid very long lines when there are
    66  // many patterns. The patterns are interpreted relative to the package directory
    67  // containing the source file. The path separator is a forward slash, even on
    68  // Windows systems. Patterns may not contain ‘.’ or ‘..’ or empty path elements,
    69  // nor may they begin or end with a slash. To match everything in the current
    70  // directory, use ‘*’ instead of ‘.’. To allow for naming files with spaces in
    71  // their names, patterns can be written as Go double-quoted or back-quoted
    72  // string literals.
    73  //
    74  // If a pattern names a directory, all files in the subtree rooted at that directory are
    75  // embedded (recursively), except that files with names beginning with ‘.’ or ‘_’
    76  // are excluded. So the variable in the above example is almost equivalent to:
    77  //
    78  //	// content is our static web server content.
    79  //	//go:embed image template html/index.html
    80  //	var content embed.FS
    81  //
    82  // The difference is that ‘image/*’ embeds ‘image/.tempfile’ while ‘image’ does not.
    83  // Neither embeds ‘image/dir/.tempfile’.
    84  //
    85  // If a pattern begins with the prefix ‘all:’, then the rule for walking directories is changed
    86  // to include those files beginning with ‘.’ or ‘_’. For example, ‘all:image’ embeds
    87  // both ‘image/.tempfile’ and ‘image/dir/.tempfile’.
    88  //
    89  // The //go:embed directive can be used with both exported and unexported variables,
    90  // depending on whether the package wants to make the data available to other packages.
    91  // It can only be used with variables at package scope, not with local variables.
    92  //
    93  // Patterns must not match files outside the package's module, such as ‘.git/*’ or symbolic links.
    94  // Patterns must not match files whose names include the special punctuation characters  " * < > ? ` ' | / \ and :.
    95  // Matches for empty directories are ignored. After that, each pattern in a //go:embed line
    96  // must match at least one file or non-empty directory.
    97  //
    98  // If any patterns are invalid or have invalid matches, the build will fail.
    99  //
   100  // # Strings and Bytes
   101  //
   102  // The //go:embed line for a variable of type string or []byte can have only a single pattern,
   103  // and that pattern can match only a single file. The string or []byte is initialized with
   104  // the contents of that file.
   105  //
   106  // The //go:embed directive requires importing "embed", even when using a string or []byte.
   107  // In source files that don't refer to [embed.FS], use a blank import (import _ "embed").
   108  //
   109  // # File Systems
   110  //
   111  // For embedding a single file, a variable of type string or []byte is often best.
   112  // The [FS] type enables embedding a tree of files, such as a directory of static
   113  // web server content, as in the example above.
   114  //
   115  // FS implements the [io/fs] package's [FS] interface, so it can be used with any package that
   116  // understands file systems, including [net/http], [text/template], and [html/template].
   117  //
   118  // For example, given the content variable in the example above, we can write:
   119  //
   120  //	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(content))))
   121  //
   122  //	template.ParseFS(content, "*.tmpl")
   123  //
   124  // # Tools
   125  //
   126  // To support tools that analyze Go packages, the patterns found in //go:embed lines
   127  // are available in “go list” output. See the EmbedPatterns, TestEmbedPatterns,
   128  // and XTestEmbedPatterns fields in the “go help list” output.
   129  package embed
   130  
   131  import (
   132  	"errors"
   133  	"io"
   134  	"io/fs"
   135  	"time"
   136  )
   137  
   138  // An FS is a read-only collection of files, usually initialized with a //go:embed directive.
   139  // When declared without a //go:embed directive, an FS is an empty file system.
   140  //
   141  // An FS is a read-only value, so it is safe to use from multiple goroutines
   142  // simultaneously and also safe to assign values of type FS to each other.
   143  //
   144  // FS implements fs.FS, so it can be used with any package that understands
   145  // file system interfaces, including net/http, text/template, and html/template.
   146  //
   147  // See the package documentation for more details about initializing an FS.
   148  type FS struct {
   149  	// The compiler knows the layout of this struct.
   150  	// See cmd/compile/internal/staticdata's WriteEmbed.
   151  	//
   152  	// The files list is sorted by name but not by simple string comparison.
   153  	// Instead, each file's name takes the form "dir/elem" or "dir/elem/".
   154  	// The optional trailing slash indicates that the file is itself a directory.
   155  	// The files list is sorted first by dir (if dir is missing, it is taken to be ".")
   156  	// and then by base, so this list of files:
   157  	//
   158  	//	p
   159  	//	q/
   160  	//	q/r
   161  	//	q/s/
   162  	//	q/s/t
   163  	//	q/s/u
   164  	//	q/v
   165  	//	w
   166  	//
   167  	// is actually sorted as:
   168  	//
   169  	//	p       # dir=.    elem=p
   170  	//	q/      # dir=.    elem=q
   171  	//	w/      # dir=.    elem=w
   172  	//	q/r     # dir=q    elem=r
   173  	//	q/s/    # dir=q    elem=s
   174  	//	q/v     # dir=q    elem=v
   175  	//	q/s/t   # dir=q/s  elem=t
   176  	//	q/s/u   # dir=q/s  elem=u
   177  	//
   178  	// This order brings directory contents together in contiguous sections
   179  	// of the list, allowing a directory read to use binary search to find
   180  	// the relevant sequence of entries.
   181  	files *[]file
   182  }
   183  
   184  // split splits the name into dir and elem as described in the
   185  // comment in the FS struct above. isDir reports whether the
   186  // final trailing slash was present, indicating that name is a directory.
   187  func split(name string) (dir, elem string, isDir bool) {
   188  	if name[len(name)-1] == '/' {
   189  		isDir = true
   190  		name = name[:len(name)-1]
   191  	}
   192  	i := len(name) - 1
   193  	for i >= 0 && name[i] != '/' {
   194  		i--
   195  	}
   196  	if i < 0 {
   197  		return ".", name, isDir
   198  	}
   199  	return name[:i], name[i+1:], isDir
   200  }
   201  
   202  // trimSlash trims a trailing slash from name, if present,
   203  // returning the possibly shortened name.
   204  func trimSlash(name string) string {
   205  	if len(name) > 0 && name[len(name)-1] == '/' {
   206  		return name[:len(name)-1]
   207  	}
   208  	return name
   209  }
   210  
   211  var (
   212  	_ fs.ReadDirFS  = FS{}
   213  	_ fs.ReadFileFS = FS{}
   214  )
   215  
   216  // A file is a single file in the FS.
   217  // It implements fs.FileInfo and fs.DirEntry.
   218  type file struct {
   219  	// The compiler knows the layout of this struct.
   220  	// See cmd/compile/internal/staticdata's WriteEmbed.
   221  	name string
   222  	data string
   223  	hash [16]byte // truncated SHA256 hash
   224  }
   225  
   226  var (
   227  	_ fs.FileInfo = (*file)(nil)
   228  	_ fs.DirEntry = (*file)(nil)
   229  )
   230  
   231  func (f *file) Name() string               { _, elem, _ := split(f.name); return elem }
   232  func (f *file) Size() int64                { return int64(len(f.data)) }
   233  func (f *file) ModTime() time.Time         { return time.Time{} }
   234  func (f *file) IsDir() bool                { _, _, isDir := split(f.name); return isDir }
   235  func (f *file) Sys() any                   { return nil }
   236  func (f *file) Type() fs.FileMode          { return f.Mode().Type() }
   237  func (f *file) Info() (fs.FileInfo, error) { return f, nil }
   238  
   239  func (f *file) Mode() fs.FileMode {
   240  	if f.IsDir() {
   241  		return fs.ModeDir | 0555
   242  	}
   243  	return 0444
   244  }
   245  
   246  func (f *file) String() string {
   247  	return fs.FormatFileInfo(f)
   248  }
   249  
   250  // dotFile is a file for the root directory,
   251  // which is omitted from the files list in a FS.
   252  var dotFile = &file{name: "./"}
   253  
   254  // lookup returns the named file, or nil if it is not present.
   255  func (f FS) lookup(name string) *file {
   256  	if !fs.ValidPath(name) {
   257  		// The compiler should never emit a file with an invalid name,
   258  		// so this check is not strictly necessary (if name is invalid,
   259  		// we shouldn't find a match below), but it's a good backstop anyway.
   260  		return nil
   261  	}
   262  	if name == "." {
   263  		return dotFile
   264  	}
   265  	if f.files == nil {
   266  		return nil
   267  	}
   268  
   269  	// Binary search to find where name would be in the list,
   270  	// and then check if name is at that position.
   271  	dir, elem, _ := split(name)
   272  	files := *f.files
   273  	i := sortSearch(len(files), func(i int) bool {
   274  		idir, ielem, _ := split(files[i].name)
   275  		return idir > dir || idir == dir && ielem >= elem
   276  	})
   277  	if i < len(files) && trimSlash(files[i].name) == name {
   278  		return &files[i]
   279  	}
   280  	return nil
   281  }
   282  
   283  // readDir returns the list of files corresponding to the directory dir.
   284  func (f FS) readDir(dir string) []file {
   285  	if f.files == nil {
   286  		return nil
   287  	}
   288  	// Binary search to find where dir starts and ends in the list
   289  	// and then return that slice of the list.
   290  	files := *f.files
   291  	i := sortSearch(len(files), func(i int) bool {
   292  		idir, _, _ := split(files[i].name)
   293  		return idir >= dir
   294  	})
   295  	j := sortSearch(len(files), func(j int) bool {
   296  		jdir, _, _ := split(files[j].name)
   297  		return jdir > dir
   298  	})
   299  	return files[i:j]
   300  }
   301  
   302  // Open opens the named file for reading and returns it as an [fs.File].
   303  //
   304  // The returned file implements [io.Seeker] and [io.ReaderAt] when the file is not a directory.
   305  func (f FS) Open(name string) (fs.File, error) {
   306  	file := f.lookup(name)
   307  	if file == nil {
   308  		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
   309  	}
   310  	if file.IsDir() {
   311  		return &openDir{file, f.readDir(name), 0}, nil
   312  	}
   313  	return &openFile{file, 0}, nil
   314  }
   315  
   316  // ReadDir reads and returns the entire named directory.
   317  func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
   318  	file, err := f.Open(name)
   319  	if err != nil {
   320  		return nil, err
   321  	}
   322  	dir, ok := file.(*openDir)
   323  	if !ok {
   324  		return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("not a directory")}
   325  	}
   326  	list := make([]fs.DirEntry, len(dir.files))
   327  	for i := range list {
   328  		list[i] = &dir.files[i]
   329  	}
   330  	return list, nil
   331  }
   332  
   333  // ReadFile reads and returns the content of the named file.
   334  func (f FS) ReadFile(name string) ([]byte, error) {
   335  	file, err := f.Open(name)
   336  	if err != nil {
   337  		return nil, err
   338  	}
   339  	ofile, ok := file.(*openFile)
   340  	if !ok {
   341  		return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("is a directory")}
   342  	}
   343  	return []byte(ofile.f.data), nil
   344  }
   345  
   346  // An openFile is a regular file open for reading.
   347  type openFile struct {
   348  	f      *file // the file itself
   349  	offset int64 // current read offset
   350  }
   351  
   352  var (
   353  	_ io.Seeker   = (*openFile)(nil)
   354  	_ io.ReaderAt = (*openFile)(nil)
   355  )
   356  
   357  func (f *openFile) Close() error               { return nil }
   358  func (f *openFile) Stat() (fs.FileInfo, error) { return f.f, nil }
   359  
   360  func (f *openFile) Read(b []byte) (int, error) {
   361  	if f.offset >= int64(len(f.f.data)) {
   362  		return 0, io.EOF
   363  	}
   364  	if f.offset < 0 {
   365  		return 0, &fs.PathError{Op: "read", Path: f.f.name, Err: fs.ErrInvalid}
   366  	}
   367  	n := copy(b, f.f.data[f.offset:])
   368  	f.offset += int64(n)
   369  	return n, nil
   370  }
   371  
   372  func (f *openFile) Seek(offset int64, whence int) (int64, error) {
   373  	switch whence {
   374  	case 0:
   375  		// offset += 0
   376  	case 1:
   377  		offset += f.offset
   378  	case 2:
   379  		offset += int64(len(f.f.data))
   380  	}
   381  	if offset < 0 || offset > int64(len(f.f.data)) {
   382  		return 0, &fs.PathError{Op: "seek", Path: f.f.name, Err: fs.ErrInvalid}
   383  	}
   384  	f.offset = offset
   385  	return offset, nil
   386  }
   387  
   388  func (f *openFile) ReadAt(b []byte, offset int64) (int, error) {
   389  	if offset < 0 || offset > int64(len(f.f.data)) {
   390  		return 0, &fs.PathError{Op: "read", Path: f.f.name, Err: fs.ErrInvalid}
   391  	}
   392  	n := copy(b, f.f.data[offset:])
   393  	if n < len(b) {
   394  		return n, io.EOF
   395  	}
   396  	return n, nil
   397  }
   398  
   399  // An openDir is a directory open for reading.
   400  type openDir struct {
   401  	f      *file  // the directory file itself
   402  	files  []file // the directory contents
   403  	offset int    // the read offset, an index into the files slice
   404  }
   405  
   406  func (d *openDir) Close() error               { return nil }
   407  func (d *openDir) Stat() (fs.FileInfo, error) { return d.f, nil }
   408  
   409  func (d *openDir) Read([]byte) (int, error) {
   410  	return 0, &fs.PathError{Op: "read", Path: d.f.name, Err: errors.New("is a directory")}
   411  }
   412  
   413  func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {
   414  	n := len(d.files) - d.offset
   415  	if n == 0 {
   416  		if count <= 0 {
   417  			return nil, nil
   418  		}
   419  		return nil, io.EOF
   420  	}
   421  	if count > 0 && n > count {
   422  		n = count
   423  	}
   424  	list := make([]fs.DirEntry, n)
   425  	for i := range list {
   426  		list[i] = &d.files[d.offset+i]
   427  	}
   428  	d.offset += n
   429  	return list, nil
   430  }
   431  
   432  // sortSearch is like sort.Search, avoiding an import.
   433  func sortSearch(n int, f func(int) bool) int {
   434  	// Define f(-1) == false and f(n) == true.
   435  	// Invariant: f(i-1) == false, f(j) == true.
   436  	i, j := 0, n
   437  	for i < j {
   438  		h := int(uint(i+j) >> 1) // avoid overflow when computing h
   439  		// i ≤ h < j
   440  		if !f(h) {
   441  			i = h + 1 // preserves f(i-1) == false
   442  		} else {
   443  			j = h // preserves f(j) == true
   444  		}
   445  	}
   446  	// i == j, f(i-1) == false, and f(j) (= f(i)) == true  =>  answer is i.
   447  	return i
   448  }
   449  

View as plain text