Skip to content

Commit

Permalink
new MemoryFileSystem
Browse files Browse the repository at this point in the history
  • Loading branch information
kataras committed Dec 15, 2024
1 parent 89d1b3b commit bff2a42
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Blocks is a, simple, Go-idiomatic view engine based on [html/template](https://p
- Reload templates on development stage
- Full Layouts and Blocks support
- Automatic HTML comments removal
- Memory File System
- Markdown Content
- Global [FuncMap](https://pkg.go.dev/html/template?tab=doc#FuncMap)

Expand Down
6 changes: 3 additions & 3 deletions blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,10 +573,10 @@ func (v *Blocks) executeTemplate(w io.Writer, tmplName, layoutName string, data
return tmpl.Execute(w, data)
}

// ParseTemplate parses a template based on its "tmplName" name and returns the result.
// TemplateString executes a template based on its "tmplName" name and returns its contents result.
// Note that, this does not reload the templates on each call if Reload was set to true.
// To refresh the templates you have to manually call the `Load` upfront.
func (v *Blocks) ParseTemplate(tmplName, layoutName string, data any) (string, error) {
func (v *Blocks) TemplateString(tmplName, layoutName string, data any) (string, error) {
b := v.bufferPool.Get()
// use the unexported method so it does not re-reload the templates on each partial one
// when Reload was set to true.
Expand All @@ -592,7 +592,7 @@ func (v *Blocks) PartialFunc(partialName string, data any) (template.HTML, error
// if err != nil {
// return "", err
// }
contents, err := v.ParseTemplate(partialName, "", data)
contents, err := v.TemplateString(partialName, "", data)
if err != nil {
return "", err
}
Expand Down
121 changes: 121 additions & 0 deletions fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package blocks

import (
"fmt"
"html/template"
"io"
"io/fs"
"net/http"
"os"
"path/filepath"
"time"
)

func getFS(fsOrDir any) fs.FS {
Expand Down Expand Up @@ -110,3 +113,121 @@ func (f *httpFS) ReadDir(name string) ([]fs.DirEntry, error) {

return entries, nil
}

// MemoryFileSystem is a custom file system that holds virtual/memory template files in memory.
// It completes the fs.FS interface.
type MemoryFileSystem struct {
files map[string]*memoryTemplateFile
}

// NewMemoryFileSystem creates a new VirtualFileSystem instance.
// It comes with no files, use `ParseTemplate` to add new virtual/memory template files.
// Usage:
//
// vfs := NewVirtualFileSystem()
// err := vfs.ParseTemplate("example.html", []byte("Hello, World!"), nil)
// templates := New(vfs)
// templates.Load()
func NewMemoryFileSystem() *MemoryFileSystem {
return &MemoryFileSystem{
files: make(map[string]*memoryTemplateFile),
}
}

var _ fs.FS = (*MemoryFileSystem)(nil)

// ParseTemplate adds a new memory temlate to the file system.
func (vfs *MemoryFileSystem) ParseTemplate(name string, contents []byte, funcMap template.FuncMap) error {
vfs.files[name] = &memoryTemplateFile{
name: name,
contents: contents,
funcMap: funcMap,
}
return nil
}

// Open implements the fs.FS interface.
func (vfs *MemoryFileSystem) Open(name string) (fs.File, error) {
if file, exists := vfs.files[name]; exists {
file.reset() // Reset read position
return file, nil
}
return nil, fs.ErrNotExist
}

// memoryTemplateFile represents a memory file.
type memoryTemplateFile struct {
name string
contents []byte
funcMap template.FuncMap
offset int64
}

// Ensure memoryTemplateFile implements fs.File interface.
var _ fs.File = (*memoryTemplateFile)(nil)

func (vf *memoryTemplateFile) reset() {
vf.offset = 0
}

// Stat implements the fs.File interface, returning file info.
func (vf *memoryTemplateFile) Stat() (fs.FileInfo, error) {
return &memoryFileInfo{
name: vf.name,
size: int64(len(vf.contents)),
}, nil
}

// Read implements the io.Reader interface.
func (vf *memoryTemplateFile) Read(p []byte) (int, error) {
if vf.offset >= int64(len(vf.contents)) {
return 0, io.EOF
}
n := copy(p, vf.contents[vf.offset:])
vf.offset += int64(n)
return n, nil
}

// Close implements the io.Closer interface.
func (vf *memoryTemplateFile) Close() error {
return nil
}

// memoryFileInfo provides file information for a memory file.
type memoryFileInfo struct {
name string
size int64
}

// Ensure memoryFileInfo implements fs.FileInfo interface.
var _ fs.FileInfo = (*memoryFileInfo)(nil)

// Name returns the base name of the file.
func (fi *memoryFileInfo) Name() string {
return fi.name
}

// Size returns the length in bytes for regular files.
func (fi *memoryFileInfo) Size() int64 {
return fi.size
}

// Mode returns file mode bits.
func (fi *memoryFileInfo) Mode() fs.FileMode {
return 0444 // Read-only
}

// ModTime returns modification time.
func (fi *memoryFileInfo) ModTime() time.Time {
return time.Now()
}

// IsDir reports if the file is a directory.
func (fi *memoryFileInfo) IsDir() bool {
return false
}

// Sys returns underlying data source (can return nil).
func (fi *memoryFileInfo) Sys() interface{} {
return nil
}

0 comments on commit bff2a42

Please sign in to comment.