...

Source file src/embed/embed.go

Documentation: embed

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

View as plain text