├── 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 |
11 |
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
--------------------------------------------------------------------------------