Skip to content
This repository has been archived by the owner on Feb 24, 2024. It is now read-only.

Commit

Permalink
Merge pull request #2179 from gobuffalo/development
Browse files Browse the repository at this point in the history
v0.18.1
  • Loading branch information
paganotoni authored Nov 28, 2021
2 parents 0a9008a + 4e1771c commit 94b49a3
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 1 deletion.
76 changes: 76 additions & 0 deletions fs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package buffalo

import (
"fmt"
"io/fs"
"os"
)

// FS wraps a directory and an embed FS that are expected to have the same contents.
// it prioritizes the directory FS and falls back to the embedded FS if the file cannot
// be found on disk. This is useful during development or when deploying with
// assets not embedded in the binary.
//
// Additionally FS hiddes any file named embed.go from the FS.
type FS struct {
embed fs.FS
dir fs.FS
}

// NewFS returns a new FS that wraps the given directory and embedded FS.
// the embed.FS is expected to embed the same files as the directory FS.
func NewFS(embed fs.ReadDirFS, dir string) FS {
return FS{
embed: embed,
dir: os.DirFS(dir),
}
}

// Open implements the FS interface.
func (f FS) Open(name string) (fs.File, error) {
if name == "embed.go" {
return nil, fs.ErrNotExist
}
file, err := f.getFile(name)
if name == "." {
return rootFile{file}, err
}
return file, err
}

func (f FS) getFile(name string) (fs.File, error) {
file, err := f.dir.Open(name)
if err == nil {
return file, nil
}

return f.embed.Open(name)
}

// rootFile wraps the "." directory for hidding the embed.go file.
type rootFile struct {
fs.File
}

// ReadDir implements the fs.ReadDirFile interface.
func (f rootFile) ReadDir(n int) (entries []fs.DirEntry, err error) {
dir, ok := f.File.(fs.ReadDirFile)
if !ok {
return nil, fmt.Errorf("%T is not a directory", f.File)
}

entries, err = dir.ReadDir(n)
entries = hideEmbedFile(entries)
return entries, err
}

func hideEmbedFile(entries []fs.DirEntry) []fs.DirEntry {
result := make([]fs.DirEntry, 0, len(entries))

for _, entry := range entries {
if entry.Name() != "embed.go" {
result = append(result, entry)
}
}
return result
}
100 changes: 100 additions & 0 deletions fs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package buffalo

import (
"io"
"io/fs"
"testing"

"github.com/gobuffalo/buffalo/internal/testdata/embedded"
"github.com/stretchr/testify/require"
)

func Test_FS_Disallows_Parent_Folders(t *testing.T) {
r := require.New(t)

fsys := NewFS(embedded.FS(), "internal/testdata/disk")
r.NotNil(fsys)

f, err := fsys.Open("../panic.txt")
r.ErrorIs(err, fs.ErrNotExist)
r.Nil(f)

f, err = fsys.Open("try/../to/../trick/../panic.txt")
r.ErrorIs(err, fs.ErrNotExist)
r.Nil(f)
}

func Test_FS_Hides_embed_go(t *testing.T) {
r := require.New(t)

fsys := NewFS(embedded.FS(), "internal/testdata/disk")
r.NotNil(fsys)

f, err := fsys.Open("embed.go")
r.ErrorIs(err, fs.ErrNotExist)
r.Nil(f)
}

func Test_FS_Prioritizes_Disk(t *testing.T) {
r := require.New(t)

fsys := NewFS(embedded.FS(), "internal/testdata/disk")
r.NotNil(fsys)

f, err := fsys.Open("file.txt")
r.NoError(err)

b, err := io.ReadAll(f)
r.NoError(err)

r.Equal("This file is on disk.", string(b))
}

func Test_FS_Uses_Embed_If_No_Disk(t *testing.T) {
r := require.New(t)

fsys := NewFS(embedded.FS(), "internal/testdata/empty")
r.NotNil(fsys)

f, err := fsys.Open("file.txt")
r.NoError(err)

b, err := io.ReadAll(f)
r.NoError(err)

r.Equal("This file is embedded.", string(b))
}

func Test_FS_ReadDirFile(t *testing.T) {
r := require.New(t)

fsys := NewFS(embedded.FS(), "internal/testdata/disk")
r.NotNil(fsys)

f, err := fsys.Open(".")
r.NoError(err)

dir, ok := f.(fs.ReadDirFile)
r.True(ok, "folder does not implement fs.ReadDirFile interface")

// First read should return at most 1 file
entries, err := dir.ReadDir(1)
r.NoError(err)

// The actual len will be 0 because the first file read is the embed.go file
// this is counter-intuitive, but it's how the fs.ReadDirFile interface is specified;
// if err == nil, just continue to call ReadDir until io.EOF is returned.
r.LessOrEqual(len(entries), 1, "a call to ReadDir must at most return n entries")

// Second read should return at most 2 files
entries, err = dir.ReadDir(2)
r.NoError(err)

// The actual len will be 2 (file.txt & file2.txt)
r.LessOrEqual(len(entries), 2, "a call to ReadDir must at most return n entries")

// trying to read next 2 files (none left)
entries, err = dir.ReadDir(2)
r.ErrorIs(err, io.EOF)
r.Empty(entries)
}
Empty file added internal/testdata/disk/embed.go
Empty file.
1 change: 1 addition & 0 deletions internal/testdata/disk/file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file is on disk.
Empty file.
12 changes: 12 additions & 0 deletions internal/testdata/embedded/embed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package embedded

import (
"embed"
)

//go:embed *
var files embed.FS

func FS() embed.FS {
return files
}
1 change: 1 addition & 0 deletions internal/testdata/embedded/file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file is embedded.
1 change: 1 addition & 0 deletions internal/testdata/panic.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file must not be accessible from buffalo.FS.
9 changes: 9 additions & 0 deletions render/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package render

import (
"html"
"strings"

"github.com/gobuffalo/github_flavored_markdown"
"github.com/gobuffalo/plush/v4"
Expand All @@ -28,6 +29,14 @@ func HTML(names ...string) Renderer {
// in the options, then that layout file will be used
// automatically.
func (e *Engine) HTML(names ...string) Renderer {
// just allow leading slash and remove them here.
// generated actions were various by buffalo versions.
tmp := []string{}
for _, name := range names {
tmp = append(tmp, strings.TrimPrefix(name, "/"))
}
names = tmp

if e.HTMLLayout != "" && len(names) == 1 {
names = append(names, e.HTMLLayout)
}
Expand Down
19 changes: 19 additions & 0 deletions render/html_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,22 @@ func Test_HTML_WithLayout_Override(t *testing.T) {
r.NoError(h.Render(bb, Data{"name": "Mark"}))
r.Equal("<html>Mark</html>", strings.TrimSpace(bb.String()))
}

func Test_HTML_LeadingSlash(t *testing.T) {
r := require.New(t)

rootFS := memfs.New()
r.NoError(rootFS.WriteFile(htmlTemplate, []byte("<%= name %>"), 0644))
r.NoError(rootFS.WriteFile(htmlLayout, []byte("<body><%= yield %></body>"), 0644))

e := NewEngine()
e.TemplatesFS = rootFS
e.HTMLLayout = htmlLayout

h := e.HTML("/my-template.html") // instead of "my-template.html"
r.Equal("text/html; charset=utf-8", h.ContentType())
bb := &bytes.Buffer{}

r.NoError(h.Render(bb, Data{"name": "Mark"}))
r.Equal("<body>Mark</body>", strings.TrimSpace(bb.String()))
}
2 changes: 1 addition & 1 deletion runtime/version.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package runtime

// Version is the current version of the buffalo binary
var Version = "v0.18.0"
var Version = "v0.18.1"

0 comments on commit 94b49a3

Please sign in to comment.