Skip to content

Commit bff2a42

Browse files
committed
new MemoryFileSystem
1 parent 89d1b3b commit bff2a42

File tree

3 files changed

+125
-3
lines changed

3 files changed

+125
-3
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Blocks is a, simple, Go-idiomatic view engine based on [html/template](https://p
1010
- Reload templates on development stage
1111
- Full Layouts and Blocks support
1212
- Automatic HTML comments removal
13+
- Memory File System
1314
- Markdown Content
1415
- Global [FuncMap](https://pkg.go.dev/html/template?tab=doc#FuncMap)
1516

Diff for: blocks.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -573,10 +573,10 @@ func (v *Blocks) executeTemplate(w io.Writer, tmplName, layoutName string, data
573573
return tmpl.Execute(w, data)
574574
}
575575

576-
// ParseTemplate parses a template based on its "tmplName" name and returns the result.
576+
// TemplateString executes a template based on its "tmplName" name and returns its contents result.
577577
// Note that, this does not reload the templates on each call if Reload was set to true.
578578
// To refresh the templates you have to manually call the `Load` upfront.
579-
func (v *Blocks) ParseTemplate(tmplName, layoutName string, data any) (string, error) {
579+
func (v *Blocks) TemplateString(tmplName, layoutName string, data any) (string, error) {
580580
b := v.bufferPool.Get()
581581
// use the unexported method so it does not re-reload the templates on each partial one
582582
// when Reload was set to true.
@@ -592,7 +592,7 @@ func (v *Blocks) PartialFunc(partialName string, data any) (template.HTML, error
592592
// if err != nil {
593593
// return "", err
594594
// }
595-
contents, err := v.ParseTemplate(partialName, "", data)
595+
contents, err := v.TemplateString(partialName, "", data)
596596
if err != nil {
597597
return "", err
598598
}

Diff for: fs.go

+121
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package blocks
22

33
import (
44
"fmt"
5+
"html/template"
6+
"io"
57
"io/fs"
68
"net/http"
79
"os"
810
"path/filepath"
11+
"time"
912
)
1013

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

111114
return entries, nil
112115
}
116+
117+
// MemoryFileSystem is a custom file system that holds virtual/memory template files in memory.
118+
// It completes the fs.FS interface.
119+
type MemoryFileSystem struct {
120+
files map[string]*memoryTemplateFile
121+
}
122+
123+
// NewMemoryFileSystem creates a new VirtualFileSystem instance.
124+
// It comes with no files, use `ParseTemplate` to add new virtual/memory template files.
125+
// Usage:
126+
//
127+
// vfs := NewVirtualFileSystem()
128+
// err := vfs.ParseTemplate("example.html", []byte("Hello, World!"), nil)
129+
// templates := New(vfs)
130+
// templates.Load()
131+
func NewMemoryFileSystem() *MemoryFileSystem {
132+
return &MemoryFileSystem{
133+
files: make(map[string]*memoryTemplateFile),
134+
}
135+
}
136+
137+
var _ fs.FS = (*MemoryFileSystem)(nil)
138+
139+
// ParseTemplate adds a new memory temlate to the file system.
140+
func (vfs *MemoryFileSystem) ParseTemplate(name string, contents []byte, funcMap template.FuncMap) error {
141+
vfs.files[name] = &memoryTemplateFile{
142+
name: name,
143+
contents: contents,
144+
funcMap: funcMap,
145+
}
146+
return nil
147+
}
148+
149+
// Open implements the fs.FS interface.
150+
func (vfs *MemoryFileSystem) Open(name string) (fs.File, error) {
151+
if file, exists := vfs.files[name]; exists {
152+
file.reset() // Reset read position
153+
return file, nil
154+
}
155+
return nil, fs.ErrNotExist
156+
}
157+
158+
// memoryTemplateFile represents a memory file.
159+
type memoryTemplateFile struct {
160+
name string
161+
contents []byte
162+
funcMap template.FuncMap
163+
offset int64
164+
}
165+
166+
// Ensure memoryTemplateFile implements fs.File interface.
167+
var _ fs.File = (*memoryTemplateFile)(nil)
168+
169+
func (vf *memoryTemplateFile) reset() {
170+
vf.offset = 0
171+
}
172+
173+
// Stat implements the fs.File interface, returning file info.
174+
func (vf *memoryTemplateFile) Stat() (fs.FileInfo, error) {
175+
return &memoryFileInfo{
176+
name: vf.name,
177+
size: int64(len(vf.contents)),
178+
}, nil
179+
}
180+
181+
// Read implements the io.Reader interface.
182+
func (vf *memoryTemplateFile) Read(p []byte) (int, error) {
183+
if vf.offset >= int64(len(vf.contents)) {
184+
return 0, io.EOF
185+
}
186+
n := copy(p, vf.contents[vf.offset:])
187+
vf.offset += int64(n)
188+
return n, nil
189+
}
190+
191+
// Close implements the io.Closer interface.
192+
func (vf *memoryTemplateFile) Close() error {
193+
return nil
194+
}
195+
196+
// memoryFileInfo provides file information for a memory file.
197+
type memoryFileInfo struct {
198+
name string
199+
size int64
200+
}
201+
202+
// Ensure memoryFileInfo implements fs.FileInfo interface.
203+
var _ fs.FileInfo = (*memoryFileInfo)(nil)
204+
205+
// Name returns the base name of the file.
206+
func (fi *memoryFileInfo) Name() string {
207+
return fi.name
208+
}
209+
210+
// Size returns the length in bytes for regular files.
211+
func (fi *memoryFileInfo) Size() int64 {
212+
return fi.size
213+
}
214+
215+
// Mode returns file mode bits.
216+
func (fi *memoryFileInfo) Mode() fs.FileMode {
217+
return 0444 // Read-only
218+
}
219+
220+
// ModTime returns modification time.
221+
func (fi *memoryFileInfo) ModTime() time.Time {
222+
return time.Now()
223+
}
224+
225+
// IsDir reports if the file is a directory.
226+
func (fi *memoryFileInfo) IsDir() bool {
227+
return false
228+
}
229+
230+
// Sys returns underlying data source (can return nil).
231+
func (fi *memoryFileInfo) Sys() interface{} {
232+
return nil
233+
}

0 commit comments

Comments
 (0)