diff options
Diffstat (limited to 'src/embed')
17 files changed, 765 insertions, 0 deletions
diff --git a/src/embed/embed.go b/src/embed/embed.go new file mode 100644 index 0000000..c54b961 --- /dev/null +++ b/src/embed/embed.go @@ -0,0 +1,432 @@ +// 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/*’ or symbolic links. +// 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 ( + "errors" + "io" + "io/fs" + "time" +) + +// 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(name string) (dir, elem string, isDir bool) { + if name[len(name)-1] == '/' { + isDir = true + name = name[:len(name)-1] + } + i := len(name) - 1 + for i >= 0 && name[i] != '/' { + i-- + } + if i < 0 { + return ".", name, isDir + } + return name[:i], name[i+1:], isDir +} + +// trimSlash trims a trailing slash from name, if present, +// returning the possibly shortened name. +func trimSlash(name string) string { + if len(name) > 0 && name[len(name)-1] == '/' { + return name[:len(name)-1] + } + return name +} + +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 (f *file) Name() string { _, elem, _ := split(f.name); return elem } +func (f *file) Size() int64 { return int64(len(f.data)) } +func (f *file) ModTime() time.Time { return time.Time{} } +func (f *file) IsDir() bool { _, _, isDir := split(f.name); return isDir } +func (f *file) Sys() any { return nil } +func (f *file) Type() fs.FileMode { return f.Mode().Type() } +func (f *file) Info() (fs.FileInfo, error) { return f, nil } + +func (f *file) Mode() fs.FileMode { + if f.IsDir() { + return fs.ModeDir | 0555 + } + return 0444 +} + +// 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 (f FS) lookup(name string) *file { + if !fs.ValidPath(name) { + // 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 name == "." { + return dotFile + } + if f.files == nil { + return nil + } + + // Binary search to find where name would be in the list, + // and then check if name is at that position. + dir, elem, _ := split(name) + files := *f.files + i := sortSearch(len(files), func(i int) bool { + idir, ielem, _ := split(files[i].name) + return idir > dir || idir == dir && ielem >= elem + }) + if i < len(files) && trimSlash(files[i].name) == name { + return &files[i] + } + return nil +} + +// readDir returns the list of files corresponding to the directory dir. +func (f FS) readDir(dir string) []file { + if f.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 := *f.files + i := sortSearch(len(files), func(i int) bool { + idir, _, _ := split(files[i].name) + return idir >= dir + }) + j := sortSearch(len(files), func(j int) bool { + jdir, _, _ := split(files[j].name) + return jdir > dir + }) + return files[i:j] +} + +// Open opens the named file for reading and returns it as an fs.File. +// +// The returned file implements io.Seeker when the file is not a directory. +func (f FS) Open(name string) (fs.File, error) { + file := f.lookup(name) + if file == nil { + return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} + } + if file.IsDir() { + return &openDir{file, f.readDir(name), 0}, nil + } + return &openFile{file, 0}, nil +} + +// ReadDir reads and returns the entire named directory. +func (f FS) ReadDir(name string) ([]fs.DirEntry, error) { + file, err := f.Open(name) + if err != nil { + return nil, err + } + dir, ok := file.(*openDir) + if !ok { + return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("not a directory")} + } + list := make([]fs.DirEntry, len(dir.files)) + for i := range list { + list[i] = &dir.files[i] + } + return list, nil +} + +// ReadFile reads and returns the content of the named file. +func (f FS) ReadFile(name string) ([]byte, error) { + file, err := f.Open(name) + if err != nil { + return nil, err + } + ofile, ok := file.(*openFile) + if !ok { + return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("is a directory")} + } + return []byte(ofile.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) +) + +func (f *openFile) Close() error { return nil } +func (f *openFile) Stat() (fs.FileInfo, error) { return f.f, nil } + +func (f *openFile) Read(b []byte) (int, error) { + if f.offset >= int64(len(f.f.data)) { + return 0, io.EOF + } + if f.offset < 0 { + return 0, &fs.PathError{Op: "read", Path: f.f.name, Err: fs.ErrInvalid} + } + n := copy(b, f.f.data[f.offset:]) + f.offset += int64(n) + return n, nil +} + +func (f *openFile) Seek(offset int64, whence int) (int64, error) { + switch whence { + case 0: + // offset += 0 + case 1: + offset += f.offset + case 2: + offset += int64(len(f.f.data)) + } + if offset < 0 || offset > int64(len(f.f.data)) { + return 0, &fs.PathError{Op: "seek", Path: f.f.name, Err: fs.ErrInvalid} + } + f.offset = offset + return offset, 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 (d *openDir) Close() error { return nil } +func (d *openDir) Stat() (fs.FileInfo, error) { return d.f, nil } + +func (d *openDir) Read([]byte) (int, error) { + return 0, &fs.PathError{Op: "read", Path: d.f.name, Err: errors.New("is a directory")} +} + +func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) { + n := len(d.files) - d.offset + if n == 0 { + if count <= 0 { + return nil, nil + } + return nil, io.EOF + } + if count > 0 && n > count { + n = count + } + list := make([]fs.DirEntry, n) + for i := range list { + list[i] = &d.files[d.offset+i] + } + d.offset += n + return list, nil +} + +// sortSearch is like sort.Search, avoiding an import. +func sortSearch(n int, f func(int) bool) int { + // Define f(-1) == false and f(n) == true. + // Invariant: f(i-1) == false, f(j) == true. + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) // avoid overflow when computing h + // i ≤ h < j + if !f(h) { + i = h + 1 // preserves f(i-1) == false + } else { + j = h // preserves f(j) == true + } + } + // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i. + return i +} diff --git a/src/embed/example_test.go b/src/embed/example_test.go new file mode 100644 index 0000000..5498c27 --- /dev/null +++ b/src/embed/example_test.go @@ -0,0 +1,23 @@ +// Copyright 2021 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_test + +import ( + "embed" + "log" + "net/http" +) + +//go:embed internal/embedtest/testdata/*.txt +var content embed.FS + +func Example() { + mutex := http.NewServeMux() + mutex.Handle("/", http.FileServer(http.FS(content))) + err := http.ListenAndServe(":8080", mutex) + if err != nil { + log.Fatal(err) + } +} diff --git a/src/embed/internal/embedtest/concurrency.txt b/src/embed/internal/embedtest/concurrency.txt new file mode 100644 index 0000000..0814741 --- /dev/null +++ b/src/embed/internal/embedtest/concurrency.txt @@ -0,0 +1 @@ +Concurrency is not parallelism. diff --git a/src/embed/internal/embedtest/embed_test.go b/src/embed/internal/embedtest/embed_test.go new file mode 100644 index 0000000..cbd58ee --- /dev/null +++ b/src/embed/internal/embedtest/embed_test.go @@ -0,0 +1,178 @@ +// 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 embedtest + +import ( + "embed" + "reflect" + "testing" + "testing/fstest" +) + +//go:embed testdata/h*.txt +//go:embed c*.txt testdata/g*.txt +var global embed.FS + +//go:embed c*txt +var concurrency string + +//go:embed testdata/g*.txt +var glass []byte + +func testFiles(t *testing.T, f embed.FS, name, data string) { + t.Helper() + d, err := f.ReadFile(name) + if err != nil { + t.Error(err) + return + } + if string(d) != data { + t.Errorf("read %v = %q, want %q", name, d, data) + } +} + +func testString(t *testing.T, s, name, data string) { + t.Helper() + if s != data { + t.Errorf("%v = %q, want %q", name, s, data) + } +} + +func testDir(t *testing.T, f embed.FS, name string, expect ...string) { + t.Helper() + dirs, err := f.ReadDir(name) + if err != nil { + t.Error(err) + return + } + var names []string + for _, d := range dirs { + name := d.Name() + if d.IsDir() { + name += "/" + } + names = append(names, name) + } + if !reflect.DeepEqual(names, expect) { + t.Errorf("readdir %v = %v, want %v", name, names, expect) + } +} + +// Tests for issue 49514. +var _ = '"' +var _ = '\'' +var _ = '🦆' + +func TestGlobal(t *testing.T) { + testFiles(t, global, "concurrency.txt", "Concurrency is not parallelism.\n") + testFiles(t, global, "testdata/hello.txt", "hello, world\n") + testFiles(t, global, "testdata/glass.txt", "I can eat glass and it doesn't hurt me.\n") + + if err := fstest.TestFS(global, "concurrency.txt", "testdata/hello.txt"); err != nil { + t.Fatal(err) + } + + testString(t, concurrency, "concurrency", "Concurrency is not parallelism.\n") + testString(t, string(glass), "glass", "I can eat glass and it doesn't hurt me.\n") +} + +//go:embed testdata +var testDirAll embed.FS + +func TestDir(t *testing.T) { + all := testDirAll + testFiles(t, all, "testdata/hello.txt", "hello, world\n") + testFiles(t, all, "testdata/i/i18n.txt", "internationalization\n") + testFiles(t, all, "testdata/i/j/k/k8s.txt", "kubernetes\n") + testFiles(t, all, "testdata/ken.txt", "If a program is too slow, it must have a loop.\n") + + testDir(t, all, ".", "testdata/") + testDir(t, all, "testdata/i", "i18n.txt", "j/") + testDir(t, all, "testdata/i/j", "k/") + testDir(t, all, "testdata/i/j/k", "k8s.txt") +} + +var ( + //go:embed testdata + testHiddenDir embed.FS + + //go:embed testdata/* + testHiddenStar embed.FS +) + +func TestHidden(t *testing.T) { + dir := testHiddenDir + star := testHiddenStar + + t.Logf("//go:embed testdata") + + testDir(t, dir, "testdata", + "-not-hidden/", "ascii.txt", "glass.txt", "hello.txt", "i/", "ken.txt") + + t.Logf("//go:embed testdata/*") + + testDir(t, star, "testdata", + "-not-hidden/", ".hidden/", "_hidden/", "ascii.txt", "glass.txt", "hello.txt", "i/", "ken.txt") + + testDir(t, star, "testdata/.hidden", + "fortune.txt", "more/") // but not .more or _more +} + +func TestUninitialized(t *testing.T) { + var uninitialized embed.FS + testDir(t, uninitialized, ".") + f, err := uninitialized.Open(".") + if err != nil { + t.Fatal(err) + } + defer f.Close() + fi, err := f.Stat() + if err != nil { + t.Fatal(err) + } + if !fi.IsDir() { + t.Errorf("in uninitialized embed.FS, . is not a directory") + } +} + +var ( + //go:embed "testdata/hello.txt" + helloT []T + //go:embed "testdata/hello.txt" + helloUint8 []uint8 + //go:embed "testdata/hello.txt" + helloEUint8 []EmbedUint8 + //go:embed "testdata/hello.txt" + helloBytes EmbedBytes + //go:embed "testdata/hello.txt" + helloString EmbedString +) + +type T byte +type EmbedUint8 uint8 +type EmbedBytes []byte +type EmbedString string + +// golang.org/issue/47735 +func TestAliases(t *testing.T) { + all := testDirAll + want, e := all.ReadFile("testdata/hello.txt") + if e != nil { + t.Fatal("ReadFile:", e) + } + check := func(g any) { + got := reflect.ValueOf(g) + for i := 0; i < got.Len(); i++ { + if byte(got.Index(i).Uint()) != want[i] { + t.Fatalf("got %v want %v", got.Bytes(), want) + } + } + } + check(helloT) + check(helloUint8) + check(helloEUint8) + check(helloBytes) + check(helloString) +} diff --git a/src/embed/internal/embedtest/embedx_test.go b/src/embed/internal/embedtest/embedx_test.go new file mode 100644 index 0000000..27fa116 --- /dev/null +++ b/src/embed/internal/embedtest/embedx_test.go @@ -0,0 +1,92 @@ +// 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 embedtest_test + +import ( + "embed" + "os" + "testing" +) + +var ( + global2 = global + concurrency2 = concurrency + glass2 = glass + sbig2 = sbig + bbig2 = bbig +) + +//go:embed testdata/*.txt +var global embed.FS + +//go:embed c*txt +var concurrency string + +//go:embed testdata/g*.txt +var glass []byte + +//go:embed testdata/ascii.txt +var sbig string + +//go:embed testdata/ascii.txt +var bbig []byte + +func testFiles(t *testing.T, f embed.FS, name, data string) { + t.Helper() + d, err := f.ReadFile(name) + if err != nil { + t.Error(err) + return + } + if string(d) != data { + t.Errorf("read %v = %q, want %q", name, d, data) + } +} + +func testString(t *testing.T, s, name, data string) { + t.Helper() + if s != data { + t.Errorf("%v = %q, want %q", name, s, data) + } +} + +func TestXGlobal(t *testing.T) { + testFiles(t, global, "testdata/hello.txt", "hello, world\n") + testString(t, concurrency, "concurrency", "Concurrency is not parallelism.\n") + testString(t, string(glass), "glass", "I can eat glass and it doesn't hurt me.\n") + testString(t, concurrency2, "concurrency2", "Concurrency is not parallelism.\n") + testString(t, string(glass2), "glass2", "I can eat glass and it doesn't hurt me.\n") + + big, err := os.ReadFile("testdata/ascii.txt") + if err != nil { + t.Fatal(err) + } + testString(t, sbig, "sbig", string(big)) + testString(t, sbig2, "sbig2", string(big)) + testString(t, string(bbig), "bbig", string(big)) + testString(t, string(bbig2), "bbig", string(big)) + + if t.Failed() { + return + } + + // Could check &glass[0] == &glass2[0] but also want to make sure write does not fault + // (data must not be in read-only memory). + old := glass[0] + glass[0]++ + if glass2[0] != glass[0] { + t.Fatalf("glass and glass2 do not share storage") + } + glass[0] = old + + // Could check &bbig[0] == &bbig2[0] but also want to make sure write does not fault + // (data must not be in read-only memory). + old = bbig[0] + bbig[0]++ + if bbig2[0] != bbig[0] { + t.Fatalf("bbig and bbig2 do not share storage") + } + bbig[0] = old +} diff --git a/src/embed/internal/embedtest/testdata/-not-hidden/fortune.txt b/src/embed/internal/embedtest/testdata/-not-hidden/fortune.txt new file mode 100644 index 0000000..31f2013 --- /dev/null +++ b/src/embed/internal/embedtest/testdata/-not-hidden/fortune.txt @@ -0,0 +1,2 @@ +WARNING: terminal is not fully functional + - (press RETURN) diff --git a/src/embed/internal/embedtest/testdata/.hidden/.more/tip.txt b/src/embed/internal/embedtest/testdata/.hidden/.more/tip.txt new file mode 100644 index 0000000..71b9c69 --- /dev/null +++ b/src/embed/internal/embedtest/testdata/.hidden/.more/tip.txt @@ -0,0 +1 @@ +#define struct union /* Great space saver */ diff --git a/src/embed/internal/embedtest/testdata/.hidden/_more/tip.txt b/src/embed/internal/embedtest/testdata/.hidden/_more/tip.txt new file mode 100644 index 0000000..71b9c69 --- /dev/null +++ b/src/embed/internal/embedtest/testdata/.hidden/_more/tip.txt @@ -0,0 +1 @@ +#define struct union /* Great space saver */ diff --git a/src/embed/internal/embedtest/testdata/.hidden/fortune.txt b/src/embed/internal/embedtest/testdata/.hidden/fortune.txt new file mode 100644 index 0000000..31f2013 --- /dev/null +++ b/src/embed/internal/embedtest/testdata/.hidden/fortune.txt @@ -0,0 +1,2 @@ +WARNING: terminal is not fully functional + - (press RETURN) diff --git a/src/embed/internal/embedtest/testdata/.hidden/more/tip.txt b/src/embed/internal/embedtest/testdata/.hidden/more/tip.txt new file mode 100644 index 0000000..71b9c69 --- /dev/null +++ b/src/embed/internal/embedtest/testdata/.hidden/more/tip.txt @@ -0,0 +1 @@ +#define struct union /* Great space saver */ diff --git a/src/embed/internal/embedtest/testdata/_hidden/fortune.txt b/src/embed/internal/embedtest/testdata/_hidden/fortune.txt new file mode 100644 index 0000000..31f2013 --- /dev/null +++ b/src/embed/internal/embedtest/testdata/_hidden/fortune.txt @@ -0,0 +1,2 @@ +WARNING: terminal is not fully functional + - (press RETURN) diff --git a/src/embed/internal/embedtest/testdata/ascii.txt b/src/embed/internal/embedtest/testdata/ascii.txt new file mode 100644 index 0000000..0cfebf6 --- /dev/null +++ b/src/embed/internal/embedtest/testdata/ascii.txt @@ -0,0 +1,25 @@ + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmn +!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno +"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop +#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopq +$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqr +%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrs +&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrst +'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstu +()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuv +)*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvw +*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwx ++,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxy +,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz +-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{ +./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{| +/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} +0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} +123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} ! +23456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !" +3456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !"# +456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !"#$ +56789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !"#$% +6789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !"#$%& +789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !"#$%&' +89:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !"#$%&'( diff --git a/src/embed/internal/embedtest/testdata/glass.txt b/src/embed/internal/embedtest/testdata/glass.txt new file mode 100644 index 0000000..8350baf --- /dev/null +++ b/src/embed/internal/embedtest/testdata/glass.txt @@ -0,0 +1 @@ +I can eat glass and it doesn't hurt me. diff --git a/src/embed/internal/embedtest/testdata/hello.txt b/src/embed/internal/embedtest/testdata/hello.txt new file mode 100644 index 0000000..4b5fa63 --- /dev/null +++ b/src/embed/internal/embedtest/testdata/hello.txt @@ -0,0 +1 @@ +hello, world diff --git a/src/embed/internal/embedtest/testdata/i/i18n.txt b/src/embed/internal/embedtest/testdata/i/i18n.txt new file mode 100644 index 0000000..5ee27c6 --- /dev/null +++ b/src/embed/internal/embedtest/testdata/i/i18n.txt @@ -0,0 +1 @@ +internationalization diff --git a/src/embed/internal/embedtest/testdata/i/j/k/k8s.txt b/src/embed/internal/embedtest/testdata/i/j/k/k8s.txt new file mode 100644 index 0000000..807e21b --- /dev/null +++ b/src/embed/internal/embedtest/testdata/i/j/k/k8s.txt @@ -0,0 +1 @@ +kubernetes diff --git a/src/embed/internal/embedtest/testdata/ken.txt b/src/embed/internal/embedtest/testdata/ken.txt new file mode 100644 index 0000000..bb25981 --- /dev/null +++ b/src/embed/internal/embedtest/testdata/ken.txt @@ -0,0 +1 @@ +If a program is too slow, it must have a loop. |