embed.mx raw

   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/*’, symbolic links,
  94  // 'vendor/', or any directories containing go.mod (these are separate modules).
  95  // Patterns must not match files whose names include the special punctuation characters  " * < > ? ` ' | / \ and :.
  96  // Matches for empty directories are ignored. After that, each pattern in a //go:embed line
  97  // must match at least one file or non-empty directory.
  98  //
  99  // If any patterns are invalid or have invalid matches, the build will fail.
 100  //
 101  // # Strings and Bytes
 102  //
 103  // The //go:embed line for a variable of type string or []byte can have only a single pattern,
 104  // and that pattern can match only a single file. The string or []byte is initialized with
 105  // the contents of that file.
 106  //
 107  // The //go:embed directive requires importing "embed", even when using a string or []byte.
 108  // In source files that don't refer to [embed.FS], use a blank import (import _ "embed").
 109  //
 110  // # File Systems
 111  //
 112  // For embedding a single file, a variable of type string or []byte is often best.
 113  // The [FS] type enables embedding a tree of files, such as a directory of static
 114  // web server content, as in the example above.
 115  //
 116  // FS implements the [io/fs] package's [FS] interface, so it can be used with any package that
 117  // understands file systems, including [net/http], [text/template], and [html/template].
 118  //
 119  // For example, given the content variable in the example above, we can write:
 120  //
 121  //	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(content))))
 122  //
 123  //	template.ParseFS(content, "*.tmpl")
 124  //
 125  // # Tools
 126  //
 127  // To support tools that analyze Go packages, the patterns found in //go:embed lines
 128  // are available in “go list” output. See the EmbedPatterns, TestEmbedPatterns,
 129  // and XTestEmbedPatterns fields in the “go help list” output.
 130  package embed
 131  
 132  import (
 133  	"errors"
 134  	"internal/bytealg"
 135  	"internal/stringslite"
 136  	"io"
 137  	"io/fs"
 138  	"time"
 139  )
 140  
 141  // An FS is a read-only collection of files, usually initialized with a //go:embed directive.
 142  // When declared without a //go:embed directive, an FS is an empty file system.
 143  //
 144  // An FS is a read-only value, so it is safe to use from multiple goroutines
 145  // simultaneously and also safe to assign values of type FS to each other.
 146  //
 147  // FS implements fs.FS, so it can be used with any package that understands
 148  // file system interfaces, including net/http, text/template, and html/template.
 149  //
 150  // See the package documentation for more details about initializing an FS.
 151  type FS struct {
 152  	// The compiler knows the layout of this struct.
 153  	// See cmd/compile/internal/staticdata's WriteEmbed.
 154  	//
 155  	// The files list is sorted by name but not by simple string comparison.
 156  	// Instead, each file's name takes the form "dir/elem" or "dir/elem/".
 157  	// The optional trailing slash indicates that the file is itself a directory.
 158  	// The files list is sorted first by dir (if dir is missing, it is taken to be ".")
 159  	// and then by base, so this list of files:
 160  	//
 161  	//	p
 162  	//	q/
 163  	//	q/r
 164  	//	q/s/
 165  	//	q/s/t
 166  	//	q/s/u
 167  	//	q/v
 168  	//	w
 169  	//
 170  	// is actually sorted as:
 171  	//
 172  	//	p       # dir=.    elem=p
 173  	//	q/      # dir=.    elem=q
 174  	//	w       # dir=.    elem=w
 175  	//	q/r     # dir=q    elem=r
 176  	//	q/s/    # dir=q    elem=s
 177  	//	q/v     # dir=q    elem=v
 178  	//	q/s/t   # dir=q/s  elem=t
 179  	//	q/s/u   # dir=q/s  elem=u
 180  	//
 181  	// This order brings directory contents together in contiguous sections
 182  	// of the list, allowing a directory read to use binary search to find
 183  	// the relevant sequence of entries.
 184  	files *[]file
 185  }
 186  
 187  // split splits the name into dir and elem as described in the
 188  // comment in the FS struct above. isDir reports whether the
 189  // final trailing slash was present, indicating that name is a directory.
 190  func split(name string) (dir, elem string, isDir bool) {
 191  	name, isDir = stringslite.CutSuffix(name, "/")
 192  	i := bytealg.LastIndexByteString(name, '/')
 193  	if i < 0 {
 194  		return ".", name, isDir
 195  	}
 196  	return name[:i], name[i+1:], isDir
 197  }
 198  
 199  var (
 200  	_ fs.ReadDirFS  = FS{}
 201  	_ fs.ReadFileFS = FS{}
 202  )
 203  
 204  // A file is a single file in the FS.
 205  // It implements fs.FileInfo and fs.DirEntry.
 206  type file struct {
 207  	// The compiler knows the layout of this struct.
 208  	// See cmd/compile/internal/staticdata's WriteEmbed.
 209  	name string
 210  	data string
 211  	hash [16]byte // truncated SHA256 hash
 212  }
 213  
 214  var (
 215  	_ fs.FileInfo = (*file)(nil)
 216  	_ fs.DirEntry = (*file)(nil)
 217  )
 218  
 219  func (f *file) Name() string               { _, elem, _ := split(f.name); return elem }
 220  func (f *file) Size() int64                { return int64(len(f.data)) }
 221  func (f *file) ModTime() time.Time         { return time.Time{} }
 222  func (f *file) IsDir() bool                { _, _, isDir := split(f.name); return isDir }
 223  func (f *file) Sys() any                   { return nil }
 224  func (f *file) Type() fs.FileMode          { return f.Mode().Type() }
 225  func (f *file) Info() (fs.FileInfo, error) { return f, nil }
 226  
 227  func (f *file) Mode() fs.FileMode {
 228  	if f.IsDir() {
 229  		return fs.ModeDir | 0555
 230  	}
 231  	return 0444
 232  }
 233  
 234  func (f *file) String() string {
 235  	return fs.FormatFileInfo(f)
 236  }
 237  
 238  // dotFile is a file for the root directory,
 239  // which is omitted from the files list in a FS.
 240  var dotFile = &file{name: "./"}
 241  
 242  // lookup returns the named file, or nil if it is not present.
 243  func (f FS) lookup(name string) *file {
 244  	if !fs.ValidPath(name) {
 245  		// The compiler should never emit a file with an invalid name,
 246  		// so this check is not strictly necessary (if name is invalid,
 247  		// we shouldn't find a match below), but it's a good backstop anyway.
 248  		return nil
 249  	}
 250  	if name == "." {
 251  		return dotFile
 252  	}
 253  	if f.files == nil {
 254  		return nil
 255  	}
 256  
 257  	// Binary search to find where name would be in the list,
 258  	// and then check if name is at that position.
 259  	dir, elem, _ := split(name)
 260  	files := *f.files
 261  	i := sortSearch(len(files), func(i int) bool {
 262  		idir, ielem, _ := split(files[i].name)
 263  		return idir > dir || idir == dir && ielem >= elem
 264  	})
 265  	if i < len(files) && stringslite.TrimSuffix(files[i].name, "/") == name {
 266  		return &files[i]
 267  	}
 268  	return nil
 269  }
 270  
 271  // readDir returns the list of files corresponding to the directory dir.
 272  func (f FS) readDir(dir string) []file {
 273  	if f.files == nil {
 274  		return nil
 275  	}
 276  	// Binary search to find where dir starts and ends in the list
 277  	// and then return that slice of the list.
 278  	files := *f.files
 279  	i := sortSearch(len(files), func(i int) bool {
 280  		idir, _, _ := split(files[i].name)
 281  		return idir >= dir
 282  	})
 283  	j := sortSearch(len(files), func(j int) bool {
 284  		jdir, _, _ := split(files[j].name)
 285  		return jdir > dir
 286  	})
 287  	return files[i:j]
 288  }
 289  
 290  // Open opens the named file for reading and returns it as an [fs.File].
 291  //
 292  // The returned file implements [io.Seeker] and [io.ReaderAt] when the file is not a directory.
 293  func (f FS) Open(name string) (fs.File, error) {
 294  	file := f.lookup(name)
 295  	if file == nil {
 296  		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
 297  	}
 298  	if file.IsDir() {
 299  		return &openDir{file, f.readDir(name), 0}, nil
 300  	}
 301  	return &openFile{file, 0}, nil
 302  }
 303  
 304  // ReadDir reads and returns the entire named directory.
 305  func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
 306  	file, err := f.Open(name)
 307  	if err != nil {
 308  		return nil, err
 309  	}
 310  	dir, ok := file.(*openDir)
 311  	if !ok {
 312  		return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("not a directory")}
 313  	}
 314  	list := []fs.DirEntry{:len(dir.files)}
 315  	for i := range list {
 316  		list[i] = &dir.files[i]
 317  	}
 318  	return list, nil
 319  }
 320  
 321  // ReadFile reads and returns the content of the named file.
 322  func (f FS) ReadFile(name string) ([]byte, error) {
 323  	file, err := f.Open(name)
 324  	if err != nil {
 325  		return nil, err
 326  	}
 327  	ofile, ok := file.(*openFile)
 328  	if !ok {
 329  		return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("is a directory")}
 330  	}
 331  	return []byte(ofile.f.data), nil
 332  }
 333  
 334  // An openFile is a regular file open for reading.
 335  type openFile struct {
 336  	f      *file // the file itself
 337  	offset int64 // current read offset
 338  }
 339  
 340  var (
 341  	_ io.Seeker   = (*openFile)(nil)
 342  	_ io.ReaderAt = (*openFile)(nil)
 343  )
 344  
 345  func (f *openFile) Close() error               { return nil }
 346  func (f *openFile) Stat() (fs.FileInfo, error) { return f.f, nil }
 347  
 348  func (f *openFile) Read(b []byte) (int, error) {
 349  	if f.offset >= int64(len(f.f.data)) {
 350  		return 0, io.EOF
 351  	}
 352  	if f.offset < 0 {
 353  		return 0, &fs.PathError{Op: "read", Path: f.f.name, Err: fs.ErrInvalid}
 354  	}
 355  	n := copy(b, f.f.data[f.offset:])
 356  	f.offset += int64(n)
 357  	return n, nil
 358  }
 359  
 360  func (f *openFile) Seek(offset int64, whence int) (int64, error) {
 361  	switch whence {
 362  	case 0:
 363  		// offset += 0
 364  	case 1:
 365  		offset += f.offset
 366  	case 2:
 367  		offset += int64(len(f.f.data))
 368  	}
 369  	if offset < 0 || offset > int64(len(f.f.data)) {
 370  		return 0, &fs.PathError{Op: "seek", Path: f.f.name, Err: fs.ErrInvalid}
 371  	}
 372  	f.offset = offset
 373  	return offset, nil
 374  }
 375  
 376  func (f *openFile) ReadAt(b []byte, offset int64) (int, error) {
 377  	if offset < 0 || offset > int64(len(f.f.data)) {
 378  		return 0, &fs.PathError{Op: "read", Path: f.f.name, Err: fs.ErrInvalid}
 379  	}
 380  	n := copy(b, f.f.data[offset:])
 381  	if n < len(b) {
 382  		return n, io.EOF
 383  	}
 384  	return n, nil
 385  }
 386  
 387  // An openDir is a directory open for reading.
 388  type openDir struct {
 389  	f      *file  // the directory file itself
 390  	files  []file // the directory contents
 391  	offset int    // the read offset, an index into the files slice
 392  }
 393  
 394  func (d *openDir) Close() error               { return nil }
 395  func (d *openDir) Stat() (fs.FileInfo, error) { return d.f, nil }
 396  
 397  func (d *openDir) Read([]byte) (int, error) {
 398  	return 0, &fs.PathError{Op: "read", Path: d.f.name, Err: errors.New("is a directory")}
 399  }
 400  
 401  func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {
 402  	n := len(d.files) - d.offset
 403  	if n == 0 {
 404  		if count <= 0 {
 405  			return nil, nil
 406  		}
 407  		return nil, io.EOF
 408  	}
 409  	if count > 0 && n > count {
 410  		n = count
 411  	}
 412  	list := []fs.DirEntry{:n}
 413  	for i := range list {
 414  		list[i] = &d.files[d.offset+i]
 415  	}
 416  	d.offset += n
 417  	return list, nil
 418  }
 419  
 420  // sortSearch is like sort.Search, avoiding an import.
 421  func sortSearch(n int, f func(int) bool) int {
 422  	// Define f(-1) == false and f(n) == true.
 423  	// Invariant: f(i-1) == false, f(j) == true.
 424  	i, j := 0, n
 425  	for i < j {
 426  		h := int(uint(i+j) >> 1) // avoid overflow when computing h
 427  		// i ≤ h < j
 428  		if !f(h) {
 429  			i = h + 1 // preserves f(i-1) == false
 430  		} else {
 431  			j = h // preserves f(j) == true
 432  		}
 433  	}
 434  	// i == j, f(i-1) == false, and f(j) (= f(i)) == true  =>  answer is i.
 435  	return i
 436  }
 437