├── LICENSE ├── README.md ├── debme.go ├── debme_test.go ├── fixtures ├── test1 │ └── onefile.txt └── test2 │ └── inner │ ├── deeper │ └── three.txt │ ├── one.txt │ └── two.txt ├── go.mod ├── go.sum └── logo.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lea Anthony 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 |

4 | 5 |

6 | embed.FS wrapper providing additional functionality

7 | 8 | 9 | 10 | CodeFactor 11 | CodeFactor 12 | 13 | 14 |

15 | 16 | ## Features 17 | 18 | * Get an `embed.FS` from an embedded subdirectory 19 | * Handy `Copy(sourcePath, targetPath)` method to copy an embedded file to the filesystem 20 | * 100% `embed.FS` compatible 21 | * 100% code coverage 22 | 23 | ## Example 24 | 25 | ```go 26 | package main 27 | 28 | import ( 29 | "embed" 30 | "github.com/leaanthony/debme" 31 | "io/fs" 32 | ) 33 | 34 | // Example Filesystem: 35 | // 36 | // fixtures/ 37 | // ├── test1 38 | // | └── onefile.txt 39 | // └── test2 40 | // └── inner 41 | // ├── deeper 42 | // | └── three.txt 43 | // ├── one.txt 44 | // └── two.txt 45 | 46 | //go:embed fixtures 47 | var fixtures embed.FS 48 | 49 | func main() { 50 | root, _ := debme.FS(fixtures, "fixtures") 51 | 52 | // Anchor to "fixtures/test1" 53 | test1, _ := root.FS("test1") 54 | files1, _ := test1.ReadDir(".") 55 | 56 | println(len(files1)) // 1 57 | println(files1[0].Name()) // "onefile.txt" 58 | 59 | // Anchor to "fixtures/test2/inner" 60 | inner, _ := root.FS("test2/inner") 61 | one, _ := inner.ReadFile("one.txt") 62 | 63 | println(string(one)) // "1" 64 | 65 | // Fully compatible FS 66 | fs.WalkDir(inner, ".", func(path string, d fs.DirEntry, err error) error { 67 | if err != nil { 68 | return err 69 | } 70 | println("Path:", path, " Name:", d.Name()) 71 | return nil 72 | }) 73 | 74 | /* 75 | Path: . Name: inner 76 | Path: deeper Name: deeper 77 | Path: deeper/three.txt Name: three.txt 78 | Path: one.txt Name: one.txt 79 | Path: two.txt Name: two.txt 80 | */ 81 | 82 | // Go deeper 83 | deeper, _ := inner.FS("deeper") 84 | deeperFiles, _ := deeper.ReadDir(".") 85 | 86 | println(len(deeperFiles)) // 1 87 | println(files1[0].Name()) // "three.txt" 88 | 89 | // Copy files 90 | err := deeper.Copy("three.txt", "/path/to/target.txt") 91 | } 92 | ``` 93 | 94 | ## Why 95 | 96 | Go's new embed functionality is awesome! The only thing I found a little frustrating was the need to manage base paths. 97 | This module was created out of the need to embed multiple templates in the [Wails](https://github.com/wailsapp/wails) CLI. 98 | 99 | -------------------------------------------------------------------------------- /debme.go: -------------------------------------------------------------------------------- 1 | package debme 2 | 3 | import ( 4 | "embed" 5 | "io" 6 | "io/fs" 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | type embedFS = embed.FS 12 | 13 | // Debme is an embed.FS compatible wrapper, providing Sub() functionality 14 | type Debme struct { 15 | basedir string 16 | embedFS 17 | } 18 | 19 | // FS creates an embed.FS compatible struct, anchored to the given basedir. 20 | func FS(fs embed.FS, basedir string) (Debme, error) { 21 | result := Debme{embedFS: fs, basedir: basedir} 22 | _, err := result.ReadDir(".") 23 | if err != nil { 24 | return Debme{}, err 25 | } 26 | return result, nil 27 | } 28 | 29 | func (d Debme) calculatePath(path string) string { 30 | base := filepath.Join(d.basedir, path) 31 | return filepath.ToSlash(base) 32 | } 33 | 34 | // Open opens the named file for reading and returns it as an fs.File. 35 | func (d Debme) Open(name string) (fs.File, error) { 36 | path := d.calculatePath(name) 37 | return d.embedFS.Open(path) 38 | } 39 | 40 | // ReadDir reads and returns the entire named directory. 41 | func (d Debme) ReadDir(name string) ([]fs.DirEntry, error) { 42 | path := d.calculatePath(name) 43 | return d.embedFS.ReadDir(path) 44 | } 45 | 46 | // ReadFile reads and returns the content of the named file. 47 | func (d Debme) ReadFile(name string) ([]byte, error) { 48 | path := d.calculatePath(name) 49 | return d.embedFS.ReadFile(path) 50 | } 51 | 52 | // FS returns a new Debme anchored at the given subdirectory. 53 | func (d Debme) FS(subDir string) (Debme, error) { 54 | path := d.calculatePath(subDir) 55 | return FS(d.embedFS, path) 56 | } 57 | 58 | func (d Debme) CopyFile(sourcePath string, target string, perm os.FileMode) error { 59 | sourceFile, err := d.Open(sourcePath) 60 | if err != nil { 61 | return err 62 | } 63 | targetFile, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, perm) 64 | if err != nil { 65 | return err 66 | } 67 | _, err = io.Copy(targetFile, sourceFile) 68 | return err 69 | } 70 | -------------------------------------------------------------------------------- /debme_test.go: -------------------------------------------------------------------------------- 1 | package debme 2 | 3 | import ( 4 | "embed" 5 | "github.com/leaanthony/slicer" 6 | "github.com/matryer/is" 7 | "io" 8 | "io/fs" 9 | "os" 10 | "testing" 11 | ) 12 | 13 | //go:embed fixtures 14 | var testfixtures embed.FS 15 | 16 | func TestReadFile(t *testing.T) { 17 | is2 := is.New(t) 18 | d, err := FS(testfixtures, "fixtures") 19 | is2.NoErr(err) 20 | file, err := d.ReadFile("test1/onefile.txt") 21 | is2.NoErr(err) 22 | is2.Equal(string(file), "test") 23 | test2, err := d.FS("test2") 24 | is2.NoErr(err) 25 | file, err = test2.ReadFile("inner/deeper/three.txt") 26 | is2.NoErr(err) 27 | is2.Equal(string(file), "3") 28 | 29 | _, err = FS(testfixtures, "badfixture") 30 | is2.True(err != nil) 31 | 32 | _, err = d.ReadFile("badfile") 33 | is2.True(err != nil) 34 | } 35 | 36 | func TestReadDir(t *testing.T) { 37 | is2 := is.New(t) 38 | d, err := FS(testfixtures, "fixtures/test2") 39 | is2.NoErr(err) 40 | files, err := d.ReadDir("inner") 41 | is2.NoErr(err) 42 | is2.Equal(len(files), 3) 43 | 44 | expectedFiles := slicer.String([]string{ 45 | "deeper", 46 | "one.txt", 47 | "two.txt", 48 | }) 49 | for _, file := range files { 50 | is2.True(expectedFiles.Contains(file.Name())) 51 | } 52 | 53 | _, err = d.ReadDir("baddir") 54 | is2.True(err != nil) 55 | } 56 | 57 | func TestOpen(t *testing.T) { 58 | is2 := is.New(t) 59 | d, err := FS(testfixtures, "fixtures/test1") 60 | is2.NoErr(err) 61 | file, err := d.Open("onefile.txt") 62 | is2.NoErr(err) 63 | data, err := io.ReadAll(file) 64 | is2.NoErr(err) 65 | is2.Equal(string(data), "test") 66 | 67 | _, err = d.Open("badfile") 68 | is2.True(err != nil) 69 | } 70 | 71 | func TestCompatibility(t *testing.T) { 72 | is2 := is.New(t) 73 | inner, err := FS(testfixtures, "fixtures/test2/inner") 74 | is2.NoErr(err) 75 | expectedFiles := slicer.String([]string{ 76 | ".", 77 | "deeper", 78 | "deeper/three.txt", 79 | "one.txt", 80 | "two.txt", 81 | }) 82 | err = fs.WalkDir(inner, ".", func(path string, d fs.DirEntry, err error) error { 83 | if err != nil { 84 | return err 85 | } 86 | is2.True(expectedFiles.Contains(path)) 87 | return nil 88 | }) 89 | is2.NoErr(err) 90 | } 91 | 92 | func TestFS(t *testing.T) { 93 | is2 := is.New(t) 94 | _, err := FS(testfixtures, "baddir") 95 | is2.True(err != nil) 96 | } 97 | 98 | func TestCopy(t *testing.T) { 99 | is2 := is.New(t) 100 | inner, err := FS(testfixtures, "fixtures/test2/inner") 101 | is2.NoErr(err) 102 | err = inner.CopyFile("one.txt", "one.txt", 0644) 103 | is2.NoErr(err) 104 | sourceData, err := inner.ReadFile("one.txt") 105 | is2.NoErr(err) 106 | targetData, err := os.ReadFile("one.txt") 107 | is2.NoErr(err) 108 | is2.Equal(sourceData, targetData) 109 | 110 | // Bad source file 111 | err = inner.CopyFile("one.txtsd", "one.txt", 0644) 112 | is2.True(err != nil) 113 | 114 | // Bad target file 115 | err = inner.CopyFile("one.txt", "/:09/one.txt", 0644) 116 | is2.True(err != nil) 117 | 118 | } 119 | -------------------------------------------------------------------------------- /fixtures/test1/onefile.txt: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /fixtures/test2/inner/deeper/three.txt: -------------------------------------------------------------------------------- 1 | 3 -------------------------------------------------------------------------------- /fixtures/test2/inner/one.txt: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /fixtures/test2/inner/two.txt: -------------------------------------------------------------------------------- 1 | 2 -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/leaanthony/debme 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/leaanthony/slicer v1.5.0 7 | github.com/matryer/is v1.4.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY= 2 | github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= 3 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 4 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 5 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leaanthony/debme/ccc0401f87e923d5c89a45f71e51dae3c735d9cf/logo.png --------------------------------------------------------------------------------