working interpreter for template files
This commit is contained in:
256
interpreter/template_interpreter.go
Normal file
256
interpreter/template_interpreter.go
Normal file
@ -0,0 +1,256 @@
|
||||
package interpreter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"masonry/lang"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
// TemplateInterpreter converts Masonry AST using template files
|
||||
type TemplateInterpreter struct {
|
||||
registry *TemplateRegistry
|
||||
}
|
||||
|
||||
// NewTemplateInterpreter creates a new template interpreter
|
||||
func NewTemplateInterpreter() *TemplateInterpreter {
|
||||
return &TemplateInterpreter{
|
||||
registry: NewTemplateRegistry(),
|
||||
}
|
||||
}
|
||||
|
||||
// InterpretFromFile parses a Masonry file and applies a template file
|
||||
func (ti *TemplateInterpreter) InterpretFromFile(masonryFile, templateFile string) (string, error) {
|
||||
// Read the Masonry file
|
||||
masonryInput, err := os.ReadFile(masonryFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading Masonry file: %w", err)
|
||||
}
|
||||
|
||||
// Read the template file
|
||||
tmplText, err := os.ReadFile(templateFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading template file: %w", err)
|
||||
}
|
||||
|
||||
return ti.Interpret(string(masonryInput), string(tmplText))
|
||||
}
|
||||
|
||||
// InterpretFromDirectory parses a Masonry file and applies templates from a directory
|
||||
func (ti *TemplateInterpreter) InterpretFromDirectory(masonryFile, templateDir, rootTemplate string) (string, error) {
|
||||
// Load all templates from the directory
|
||||
err := ti.registry.LoadFromDirectory(templateDir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error loading templates from directory: %w", err)
|
||||
}
|
||||
|
||||
// Read the Masonry file
|
||||
masonryInput, err := os.ReadFile(masonryFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading Masonry file: %w", err)
|
||||
}
|
||||
|
||||
// Get the root template content
|
||||
rootTemplatePath := filepath.Join(templateDir, rootTemplate)
|
||||
tmplText, err := os.ReadFile(rootTemplatePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading root template file: %w", err)
|
||||
}
|
||||
|
||||
return ti.Interpret(string(masonryInput), string(tmplText))
|
||||
}
|
||||
|
||||
func (ti *TemplateInterpreter) Interpret(masonryInput string, tmplText string) (string, error) {
|
||||
ast, err := lang.ParseInput(masonryInput)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error parsing Masonry input: %w", err)
|
||||
}
|
||||
|
||||
// Create template with helper functions
|
||||
tmpl := template.Must(template.New("rootTemplate").Funcs(template.FuncMap{
|
||||
"registry": func() *TemplateRegistry { return ti.registry },
|
||||
"executeTemplate": func(name string, data interface{}) (string, error) {
|
||||
if tmpl, exists := ti.registry.templates[name]; exists {
|
||||
var buf strings.Builder
|
||||
err := tmpl.Execute(&buf, data)
|
||||
return buf.String(), err
|
||||
}
|
||||
return "", fmt.Errorf("template %s not found", name)
|
||||
},
|
||||
"hasTemplate": func(name string) bool {
|
||||
_, exists := ti.registry.templates[name]
|
||||
return exists
|
||||
},
|
||||
"title": cases.Title(language.English).String,
|
||||
"goType": func(t string) string {
|
||||
typeMap := map[string]string{
|
||||
"string": "string",
|
||||
"int": "int",
|
||||
"uuid": "string",
|
||||
"boolean": "bool",
|
||||
"timestamp": "time.Time",
|
||||
"text": "string",
|
||||
"object": "interface{}",
|
||||
}
|
||||
if goType, ok := typeMap[t]; ok {
|
||||
return goType
|
||||
}
|
||||
return "interface{}"
|
||||
},
|
||||
"pathToHandlerName": func(path string) string {
|
||||
// Convert "/users/{id}" to "Users"
|
||||
re := regexp.MustCompile(`[^a-zA-Z0-9]+`)
|
||||
name := re.ReplaceAllString(path, " ")
|
||||
name = strings.TrimSpace(name)
|
||||
name = strings.Title(name)
|
||||
return strings.ReplaceAll(name, " ", "")
|
||||
},
|
||||
"getHost": func(settings []lang.ServerSetting) string {
|
||||
for _, s := range settings {
|
||||
if s.Host != nil {
|
||||
return *s.Host
|
||||
}
|
||||
}
|
||||
return "localhost"
|
||||
},
|
||||
"getPort": func(settings []lang.ServerSetting) int {
|
||||
for _, s := range settings {
|
||||
if s.Port != nil {
|
||||
return *s.Port
|
||||
}
|
||||
}
|
||||
return 8080
|
||||
},
|
||||
"slice": func() []interface{} {
|
||||
return []interface{}{}
|
||||
},
|
||||
"append": func(slice []interface{}, item interface{}) []interface{} {
|
||||
return append(slice, item)
|
||||
},
|
||||
}).Parse(tmplText))
|
||||
|
||||
data := struct {
|
||||
AST lang.AST
|
||||
Registry *TemplateRegistry
|
||||
}{
|
||||
AST: ast,
|
||||
Registry: ti.registry,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Execute template
|
||||
err = tmpl.Execute(&buf, data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error executing template: %w", err)
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
type TemplateRegistry struct {
|
||||
templates map[string]*template.Template
|
||||
funcMap template.FuncMap
|
||||
}
|
||||
|
||||
func NewTemplateRegistry() *TemplateRegistry {
|
||||
tr := &TemplateRegistry{
|
||||
templates: make(map[string]*template.Template),
|
||||
}
|
||||
|
||||
// Create funcMap with helper functions that will be available in all templates
|
||||
tr.funcMap = template.FuncMap{
|
||||
"executeTemplate": func(name string, data interface{}) (string, error) {
|
||||
if tmpl, exists := tr.templates[name]; exists {
|
||||
var buf strings.Builder
|
||||
err := tmpl.Execute(&buf, data)
|
||||
return buf.String(), err
|
||||
}
|
||||
return "", fmt.Errorf("template %s not found", name)
|
||||
},
|
||||
"hasTemplate": func(name string) bool {
|
||||
_, exists := tr.templates[name]
|
||||
return exists
|
||||
},
|
||||
"title": cases.Title(language.English).String,
|
||||
"goType": func(t string) string {
|
||||
typeMap := map[string]string{
|
||||
"string": "string",
|
||||
"int": "int",
|
||||
"uuid": "string",
|
||||
"boolean": "bool",
|
||||
"timestamp": "time.Time",
|
||||
"text": "string",
|
||||
"object": "interface{}",
|
||||
}
|
||||
if goType, ok := typeMap[t]; ok {
|
||||
return goType
|
||||
}
|
||||
return "interface{}"
|
||||
},
|
||||
"pathToHandlerName": func(path string) string {
|
||||
// Convert "/users/{id}" to "Users"
|
||||
re := regexp.MustCompile(`[^a-zA-Z0-9]+`)
|
||||
name := re.ReplaceAllString(path, " ")
|
||||
name = strings.TrimSpace(name)
|
||||
name = strings.Title(name)
|
||||
return strings.ReplaceAll(name, " ", "")
|
||||
},
|
||||
"getHost": func(settings []lang.ServerSetting) string {
|
||||
for _, s := range settings {
|
||||
if s.Host != nil {
|
||||
return *s.Host
|
||||
}
|
||||
}
|
||||
return "localhost"
|
||||
},
|
||||
"getPort": func(settings []lang.ServerSetting) int {
|
||||
for _, s := range settings {
|
||||
if s.Port != nil {
|
||||
return *s.Port
|
||||
}
|
||||
}
|
||||
return 8080
|
||||
},
|
||||
"slice": func() []interface{} {
|
||||
return []interface{}{}
|
||||
},
|
||||
"append": func(slice []interface{}, item interface{}) []interface{} {
|
||||
return append(slice, item)
|
||||
},
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
func (tr *TemplateRegistry) Register(name, content string) error {
|
||||
tmpl, err := template.New(name).Funcs(tr.funcMap).Parse(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr.templates[name] = tmpl
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tr *TemplateRegistry) LoadFromDirectory(dir string) error {
|
||||
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasSuffix(path, ".tmpl") {
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name := strings.TrimSuffix(filepath.Base(path), ".tmpl")
|
||||
return tr.Register(name, string(content))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user