mirror of https://github.com/gofiber/fiber.git
🐛 fix: mounted app views (#1749)
* Fix mounted app views. * Cleaner structure. Co-authored-by: RW <rene@gofiber.io> * remove unnecessary lines. * Add test case for group-with-mount, remove unnecessary lines. Co-authored-by: RW <rene@gofiber.io>pull/1755/head
parent
569511eb78
commit
c450072f4a
|
@ -0,0 +1 @@
|
||||||
|
<h1>Hello {{ .Name }}!</h1>
|
28
app.go
28
app.go
|
@ -111,8 +111,9 @@ type App struct {
|
||||||
getBytes func(s string) (b []byte)
|
getBytes func(s string) (b []byte)
|
||||||
// Converts byte slice to a string
|
// Converts byte slice to a string
|
||||||
getString func(b []byte) string
|
getString func(b []byte) string
|
||||||
// mount prefix -> error handler
|
|
||||||
errorHandlers map[string]ErrorHandler
|
// Mounted and main apps
|
||||||
|
appList map[string]*App
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is a struct holding the server settings.
|
// Config is a struct holding the server settings.
|
||||||
|
@ -463,7 +464,7 @@ func New(config ...Config) *App {
|
||||||
config: Config{},
|
config: Config{},
|
||||||
getBytes: utils.UnsafeBytes,
|
getBytes: utils.UnsafeBytes,
|
||||||
getString: utils.UnsafeString,
|
getString: utils.UnsafeString,
|
||||||
errorHandlers: make(map[string]ErrorHandler),
|
appList: make(map[string]*App),
|
||||||
}
|
}
|
||||||
// Override config if provided
|
// Override config if provided
|
||||||
if len(config) > 0 {
|
if len(config) > 0 {
|
||||||
|
@ -515,6 +516,9 @@ func New(config ...Config) *App {
|
||||||
app.handleTrustedProxy(ipAddress)
|
app.handleTrustedProxy(ipAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init appList
|
||||||
|
app.appList[""] = app
|
||||||
|
|
||||||
// Init app
|
// Init app
|
||||||
app.init()
|
app.init()
|
||||||
|
|
||||||
|
@ -544,6 +548,7 @@ func (app *App) handleTrustedProxy(ipAddress string) {
|
||||||
// to be invoked on errors that happen within the prefix route.
|
// to be invoked on errors that happen within the prefix route.
|
||||||
func (app *App) Mount(prefix string, fiber *App) Router {
|
func (app *App) Mount(prefix string, fiber *App) Router {
|
||||||
stack := fiber.Stack()
|
stack := fiber.Stack()
|
||||||
|
prefix = strings.TrimRight(prefix, "/")
|
||||||
for m := range stack {
|
for m := range stack {
|
||||||
for r := range stack[m] {
|
for r := range stack[m] {
|
||||||
route := app.copyRoute(stack[m][r])
|
route := app.copyRoute(stack[m][r])
|
||||||
|
@ -551,13 +556,10 @@ func (app *App) Mount(prefix string, fiber *App) Router {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the fiber's error handler and its sub apps
|
// Support for configs of mounted-apps and sub-mounted-apps
|
||||||
prefix = strings.TrimRight(prefix, "/")
|
for mountedPrefixes, subApp := range fiber.appList {
|
||||||
if fiber.config.ErrorHandler != nil {
|
app.appList[prefix+mountedPrefixes] = subApp
|
||||||
app.errorHandlers[prefix] = fiber.config.ErrorHandler
|
subApp.init()
|
||||||
}
|
|
||||||
for mountedPrefixes, errHandler := range fiber.errorHandlers {
|
|
||||||
app.errorHandlers[prefix+mountedPrefixes] = errHandler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.AddUint32(&app.handlersCount, fiber.handlersCount)
|
atomic.AddUint32(&app.handlersCount, fiber.handlersCount)
|
||||||
|
@ -1002,11 +1004,11 @@ func (app *App) ErrorHandler(ctx *Ctx, err error) error {
|
||||||
mountedPrefixParts int
|
mountedPrefixParts int
|
||||||
)
|
)
|
||||||
|
|
||||||
for prefix, errHandler := range app.errorHandlers {
|
for prefix, subApp := range app.appList {
|
||||||
if strings.HasPrefix(ctx.path, prefix) {
|
if strings.HasPrefix(ctx.path, prefix) && prefix != "" {
|
||||||
parts := len(strings.Split(prefix, "/"))
|
parts := len(strings.Split(prefix, "/"))
|
||||||
if mountedPrefixParts <= parts {
|
if mountedPrefixParts <= parts {
|
||||||
mountedErrHandler = errHandler
|
mountedErrHandler = subApp.config.ErrorHandler
|
||||||
mountedPrefixParts = parts
|
mountedPrefixParts = parts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
23
ctx.go
23
ctx.go
|
@ -1082,18 +1082,28 @@ func (c *Ctx) Render(name string, bind interface{}, layouts ...string) error {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.app.config.Views != nil {
|
rendered := false
|
||||||
// Render template based on global layout if exists
|
for prefix, app := range c.app.appList {
|
||||||
if len(layouts) == 0 && c.app.config.ViewsLayout != "" {
|
if prefix == "" || strings.Contains(c.OriginalURL(), prefix) {
|
||||||
|
if len(layouts) == 0 && app.config.ViewsLayout != "" {
|
||||||
layouts = []string{
|
layouts = []string{
|
||||||
c.app.config.ViewsLayout,
|
app.config.ViewsLayout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render template from Views
|
// Render template from Views
|
||||||
if err := c.app.config.Views.Render(buf, name, bind, layouts...); err != nil {
|
if app.config.Views != nil {
|
||||||
|
if err := app.config.Views.Render(buf, name, bind, layouts...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
rendered = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !rendered {
|
||||||
// Render raw template using 'name' as filepath if no engine is set
|
// Render raw template using 'name' as filepath if no engine is set
|
||||||
var tmpl *template.Template
|
var tmpl *template.Template
|
||||||
if _, err = readContent(buf, name); err != nil {
|
if _, err = readContent(buf, name); err != nil {
|
||||||
|
@ -1109,6 +1119,7 @@ func (c *Ctx) Render(name string, bind interface{}, layouts ...string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Content-Type to text/html
|
// Set Content-Type to text/html
|
||||||
c.fasthttp.Response.Header.SetContentType(MIMETextHTMLCharsetUTF8)
|
c.fasthttp.Response.Header.SetContentType(MIMETextHTMLCharsetUTF8)
|
||||||
// Set rendered template to body
|
// Set rendered template to body
|
||||||
|
|
54
ctx_test.go
54
ctx_test.go
|
@ -28,6 +28,7 @@ import (
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2/internal/bytebufferpool"
|
"github.com/gofiber/fiber/v2/internal/bytebufferpool"
|
||||||
"github.com/gofiber/fiber/v2/internal/storage/memory"
|
"github.com/gofiber/fiber/v2/internal/storage/memory"
|
||||||
|
"github.com/gofiber/fiber/v2/internal/template/html"
|
||||||
"github.com/gofiber/fiber/v2/utils"
|
"github.com/gofiber/fiber/v2/utils"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
)
|
)
|
||||||
|
@ -2049,6 +2050,59 @@ func Test_Ctx_Render(t *testing.T) {
|
||||||
err = c.Render("./.github/testdata/template-invalid.html", nil)
|
err = c.Render("./.github/testdata/template-invalid.html", nil)
|
||||||
utils.AssertEqual(t, false, err == nil)
|
utils.AssertEqual(t, false, err == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// go test -run Test_Ctx_Render_Mount
|
||||||
|
func Test_Ctx_Render_Mount(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
sub := New(Config{
|
||||||
|
Views: html.New("./.github/testdata/template", ".gohtml"),
|
||||||
|
})
|
||||||
|
|
||||||
|
sub.Get("/:name", func(ctx *Ctx) error {
|
||||||
|
return ctx.Render("hello_world", Map{
|
||||||
|
"Name": ctx.Params("name"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app := New()
|
||||||
|
app.Mount("/hello", sub)
|
||||||
|
|
||||||
|
resp, err := app.Test(httptest.NewRequest(MethodGet, "/hello/a", nil))
|
||||||
|
utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
|
||||||
|
utils.AssertEqual(t, nil, err, "app.Test(req)")
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
utils.AssertEqual(t, nil, err)
|
||||||
|
utils.AssertEqual(t, "<h1>Hello a!</h1>", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Ctx_Render_MountGroup(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
micro := New(Config{
|
||||||
|
Views: html.New("./.github/testdata/template", ".gohtml"),
|
||||||
|
})
|
||||||
|
|
||||||
|
micro.Get("/doe", func(c *Ctx) error {
|
||||||
|
return c.Render("hello_world", Map{
|
||||||
|
"Name": "doe",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app := New()
|
||||||
|
v1 := app.Group("/v1")
|
||||||
|
v1.Mount("/john", micro)
|
||||||
|
|
||||||
|
resp, err := app.Test(httptest.NewRequest(MethodGet, "/v1/john/doe", nil))
|
||||||
|
utils.AssertEqual(t, nil, err, "app.Test(req)")
|
||||||
|
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
utils.AssertEqual(t, nil, err)
|
||||||
|
utils.AssertEqual(t, "<h1>Hello doe!</h1>", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
func Test_Ctx_RenderWithoutLocals(t *testing.T) {
|
func Test_Ctx_RenderWithoutLocals(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New(Config{
|
app := New(Config{
|
||||||
|
|
10
group.go
10
group.go
|
@ -32,13 +32,11 @@ func (grp *Group) Mount(prefix string, fiber *App) Router {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the fiber's error handler and its sub apps
|
// Support for configs of mounted-apps and sub-mounted-apps
|
||||||
groupPath = strings.TrimRight(groupPath, "/")
|
groupPath = strings.TrimRight(groupPath, "/")
|
||||||
if fiber.config.ErrorHandler != nil {
|
for mountedPrefixes, subApp := range fiber.appList {
|
||||||
grp.app.errorHandlers[groupPath] = fiber.config.ErrorHandler
|
grp.app.appList[groupPath+mountedPrefixes] = subApp
|
||||||
}
|
subApp.init()
|
||||||
for mountedPrefixes, errHandler := range fiber.errorHandlers {
|
|
||||||
grp.app.errorHandlers[groupPath+mountedPrefixes] = errHandler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.AddUint32(&grp.app.handlersCount, fiber.handlersCount)
|
atomic.AddUint32(&grp.app.handlersCount, fiber.handlersCount)
|
||||||
|
|
|
@ -0,0 +1,214 @@
|
||||||
|
package html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2/internal/template/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Engine struct
|
||||||
|
type Engine struct {
|
||||||
|
// delimiters
|
||||||
|
left string
|
||||||
|
right string
|
||||||
|
// views folder
|
||||||
|
directory string
|
||||||
|
// http.FileSystem supports embedded files
|
||||||
|
fileSystem http.FileSystem
|
||||||
|
// views extension
|
||||||
|
extension string
|
||||||
|
// layout variable name that incapsulates the template
|
||||||
|
layout string
|
||||||
|
// determines if the engine parsed all templates
|
||||||
|
loaded bool
|
||||||
|
// reload on each render
|
||||||
|
reload bool
|
||||||
|
// debug prints the parsed templates
|
||||||
|
debug bool
|
||||||
|
// lock for funcmap and templates
|
||||||
|
mutex sync.RWMutex
|
||||||
|
// template funcmap
|
||||||
|
funcmap map[string]interface{}
|
||||||
|
// templates
|
||||||
|
Templates *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a HTML render engine for Fiber
|
||||||
|
func New(directory, extension string) *Engine {
|
||||||
|
engine := &Engine{
|
||||||
|
left: "{{",
|
||||||
|
right: "}}",
|
||||||
|
directory: directory,
|
||||||
|
extension: extension,
|
||||||
|
layout: "embed",
|
||||||
|
funcmap: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
engine.AddFunc(engine.layout, func() error {
|
||||||
|
return fmt.Errorf("layout called unexpectedly.")
|
||||||
|
})
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewFileSystem ...
|
||||||
|
func NewFileSystem(fs http.FileSystem, extension string) *Engine {
|
||||||
|
engine := &Engine{
|
||||||
|
left: "{{",
|
||||||
|
right: "}}",
|
||||||
|
directory: "/",
|
||||||
|
fileSystem: fs,
|
||||||
|
extension: extension,
|
||||||
|
layout: "embed",
|
||||||
|
funcmap: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
engine.AddFunc(engine.layout, func() error {
|
||||||
|
return fmt.Errorf("layout called unexpectedly.")
|
||||||
|
})
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layout defines the variable name that will incapsulate the template
|
||||||
|
func (e *Engine) Layout(key string) *Engine {
|
||||||
|
e.layout = key
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delims sets the action delimiters to the specified strings, to be used in
|
||||||
|
// templates. An empty delimiter stands for the
|
||||||
|
// corresponding default: {{ or }}.
|
||||||
|
func (e *Engine) Delims(left, right string) *Engine {
|
||||||
|
e.left, e.right = left, right
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFunc adds the function to the template's function map.
|
||||||
|
// It is legal to overwrite elements of the default actions
|
||||||
|
func (e *Engine) AddFunc(name string, fn interface{}) *Engine {
|
||||||
|
e.mutex.Lock()
|
||||||
|
e.funcmap[name] = fn
|
||||||
|
e.mutex.Unlock()
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload if set to true the templates are reloading on each render,
|
||||||
|
// use it when you're in development and you don't want to restart
|
||||||
|
// the application when you edit a template file.
|
||||||
|
func (e *Engine) Reload(enabled bool) *Engine {
|
||||||
|
e.reload = enabled
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug will print the parsed templates when Load is triggered.
|
||||||
|
func (e *Engine) Debug(enabled bool) *Engine {
|
||||||
|
e.debug = enabled
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse is deprecated, please use Load() instead
|
||||||
|
func (e *Engine) Parse() error {
|
||||||
|
fmt.Println("Parse() is deprecated, please use Load() instead.")
|
||||||
|
return e.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load parses the templates to the engine.
|
||||||
|
func (e *Engine) Load() error {
|
||||||
|
if e.loaded {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// race safe
|
||||||
|
e.mutex.Lock()
|
||||||
|
defer e.mutex.Unlock()
|
||||||
|
e.Templates = template.New(e.directory)
|
||||||
|
|
||||||
|
// Set template settings
|
||||||
|
e.Templates.Delims(e.left, e.right)
|
||||||
|
e.Templates.Funcs(e.funcmap)
|
||||||
|
|
||||||
|
walkFn := func(path string, info os.FileInfo, err error) error {
|
||||||
|
// Return error if exist
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Skip file if it's a directory or has no file info
|
||||||
|
if info == nil || info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Skip file if it does not equal the given template extension
|
||||||
|
if len(e.extension) >= len(path) || path[len(path)-len(e.extension):] != e.extension {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Get the relative file path
|
||||||
|
// ./views/html/index.tmpl -> index.tmpl
|
||||||
|
rel, err := filepath.Rel(e.directory, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Reverse slashes '\' -> '/' and
|
||||||
|
// partials\footer.tmpl -> partials/footer.tmpl
|
||||||
|
name := filepath.ToSlash(rel)
|
||||||
|
// Remove ext from name 'index.tmpl' -> 'index'
|
||||||
|
name = strings.TrimSuffix(name, e.extension)
|
||||||
|
// name = strings.Replace(name, e.extension, "", -1)
|
||||||
|
// Read the file
|
||||||
|
// #gosec G304
|
||||||
|
buf, err := utils.ReadFile(path, e.fileSystem)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Create new template associated with the current one
|
||||||
|
// This enable use to invoke other templates {{ template .. }}
|
||||||
|
_, err = e.Templates.New(name).Parse(string(buf))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Debugging
|
||||||
|
if e.debug {
|
||||||
|
fmt.Printf("views: parsed template: %s\n", name)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// notify engine that we parsed all templates
|
||||||
|
e.loaded = true
|
||||||
|
if e.fileSystem != nil {
|
||||||
|
return utils.Walk(e.fileSystem, e.directory, walkFn)
|
||||||
|
}
|
||||||
|
return filepath.Walk(e.directory, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render will execute the template name along with the given values.
|
||||||
|
func (e *Engine) Render(out io.Writer, template string, binding interface{}, layout ...string) error {
|
||||||
|
if !e.loaded || e.reload {
|
||||||
|
if e.reload {
|
||||||
|
e.loaded = false
|
||||||
|
}
|
||||||
|
if err := e.Load(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl := e.Templates.Lookup(template)
|
||||||
|
if tmpl == nil {
|
||||||
|
return fmt.Errorf("render: template %s does not exist", template)
|
||||||
|
}
|
||||||
|
if len(layout) > 0 && layout[0] != "" {
|
||||||
|
lay := e.Templates.Lookup(layout[0])
|
||||||
|
if lay == nil {
|
||||||
|
return fmt.Errorf("render: layout %s does not exist", layout[0])
|
||||||
|
}
|
||||||
|
e.mutex.Lock()
|
||||||
|
defer e.mutex.Unlock()
|
||||||
|
lay.Funcs(map[string]interface{}{
|
||||||
|
e.layout: func() error {
|
||||||
|
return tmpl.Execute(out, binding)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return lay.Execute(out, binding)
|
||||||
|
}
|
||||||
|
return tmpl.Execute(out, binding)
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
pathpkg "path"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Walk walks the filesystem rooted at root, calling walkFn for each file or
|
||||||
|
// directory in the filesystem, including root. All errors that arise visiting files
|
||||||
|
// and directories are filtered by walkFn. The files are walked in lexical
|
||||||
|
// order.
|
||||||
|
func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error {
|
||||||
|
info, err := stat(fs, root)
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(root, nil, err)
|
||||||
|
}
|
||||||
|
return walk(fs, root, info, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// #nosec G304
|
||||||
|
// ReadFile returns the raw content of a file
|
||||||
|
func ReadFile(path string, fs http.FileSystem) ([]byte, error) {
|
||||||
|
if fs != nil {
|
||||||
|
file, err := fs.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
return ioutil.ReadAll(file)
|
||||||
|
}
|
||||||
|
return ioutil.ReadFile(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDirNames reads the directory named by dirname and returns
|
||||||
|
// a sorted list of directory entries.
|
||||||
|
func readDirNames(fs http.FileSystem, dirname string) ([]string, error) {
|
||||||
|
fis, err := readDir(fs, dirname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
names := make([]string, len(fis))
|
||||||
|
for i := range fis {
|
||||||
|
names[i] = fis[i].Name()
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk recursively descends path, calling walkFn.
|
||||||
|
func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
||||||
|
err := walkFn(path, info, nil)
|
||||||
|
if err != nil {
|
||||||
|
if info.IsDir() && err == filepath.SkipDir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := readDirNames(fs, path)
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(path, info, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
filename := pathpkg.Join(path, name)
|
||||||
|
fileInfo, err := stat(fs, filename)
|
||||||
|
if err != nil {
|
||||||
|
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = walk(fs, filename, fileInfo, walkFn)
|
||||||
|
if err != nil {
|
||||||
|
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDir reads the contents of the directory associated with file and
|
||||||
|
// returns a slice of FileInfo values in directory order.
|
||||||
|
func readDir(fs http.FileSystem, name string) ([]os.FileInfo, error) {
|
||||||
|
f, err := fs.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return f.Readdir(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stat returns the FileInfo structure describing file.
|
||||||
|
func stat(fs http.FileSystem, name string) (os.FileInfo, error) {
|
||||||
|
f, err := fs.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return f.Stat()
|
||||||
|
}
|
Loading…
Reference in New Issue