194 lines
4.3 KiB
Go
194 lines
4.3 KiB
Go
package parser
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/a-h/parse"
|
|
)
|
|
|
|
func Parse(fileName string) (TemplateFile, error) {
|
|
fc, err := os.ReadFile(fileName)
|
|
if err != nil {
|
|
return TemplateFile{}, err
|
|
}
|
|
return ParseString(string(fc))
|
|
}
|
|
|
|
func getDefaultPackageName(fileName string) (pkg string) {
|
|
parent := filepath.Base(filepath.Dir(fileName))
|
|
if !isGoIdentifier(parent) {
|
|
return "main"
|
|
}
|
|
return parent
|
|
}
|
|
|
|
func isGoIdentifier(s string) bool {
|
|
if len(s) == 0 {
|
|
return false
|
|
}
|
|
for i, r := range s {
|
|
if unicode.IsLetter(r) || r == '_' {
|
|
continue
|
|
}
|
|
if i > 0 && unicode.IsDigit(r) {
|
|
continue
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func ParseString(template string) (TemplateFile, error) {
|
|
tf, ok, err := NewTemplateFileParser("main").Parse(parse.NewInput(template))
|
|
if err != nil {
|
|
return tf, err
|
|
}
|
|
if !ok {
|
|
err = ErrTemplateNotFound
|
|
}
|
|
return tf, err
|
|
}
|
|
|
|
// NewTemplateFileParser creates a new TemplateFileParser.
|
|
func NewTemplateFileParser(pkg string) TemplateFileParser {
|
|
return TemplateFileParser{
|
|
DefaultPackage: pkg,
|
|
}
|
|
}
|
|
|
|
var ErrLegacyFileFormat = errors.New("legacy file format - run templ migrate")
|
|
var ErrTemplateNotFound = errors.New("template not found")
|
|
|
|
type TemplateFileParser struct {
|
|
DefaultPackage string
|
|
}
|
|
|
|
var legacyPackageParser = parse.String("{% package")
|
|
|
|
func (p TemplateFileParser) Parse(pi *parse.Input) (tf TemplateFile, ok bool, err error) {
|
|
// If we're parsing a legacy file, complain that migration needs to happen.
|
|
_, ok, err = legacyPackageParser.Parse(pi)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if ok {
|
|
return tf, false, ErrLegacyFileFormat
|
|
}
|
|
|
|
// Read until the package.
|
|
for {
|
|
// Package.
|
|
// package name
|
|
from := pi.Position()
|
|
tf.Package, ok, err = pkg.Parse(pi)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if ok {
|
|
break
|
|
}
|
|
|
|
var line string
|
|
line, ok, err = stringUntilNewLine.Parse(pi)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if !ok {
|
|
break
|
|
}
|
|
var newLine string
|
|
newLine, _, _ = parse.NewLine.Parse(pi)
|
|
tf.Header = append(tf.Header, TemplateFileGoExpression{Expression: NewExpression(line+newLine, from, pi.Position()), BeforePackage: true})
|
|
}
|
|
|
|
// Strip any whitespace between the template declaration and the first template.
|
|
_, _, _ = parse.OptionalWhitespace.Parse(pi)
|
|
|
|
outer:
|
|
for {
|
|
// Optional templates, CSS, and script templates.
|
|
// templ Name(p Parameter)
|
|
var tn HTMLTemplate
|
|
tn, ok, err = template.Parse(pi)
|
|
if err != nil {
|
|
return tf, false, err
|
|
}
|
|
if ok {
|
|
tf.Nodes = append(tf.Nodes, tn)
|
|
_, _, _ = parse.OptionalWhitespace.Parse(pi)
|
|
continue
|
|
}
|
|
|
|
// css Name()
|
|
var cn CSSTemplate
|
|
cn, ok, err = cssParser.Parse(pi)
|
|
if err != nil {
|
|
return tf, false, err
|
|
}
|
|
if ok {
|
|
tf.Nodes = append(tf.Nodes, cn)
|
|
_, _, _ = parse.OptionalWhitespace.Parse(pi)
|
|
continue
|
|
}
|
|
|
|
// script Name()
|
|
var sn ScriptTemplate
|
|
sn, ok, err = scriptTemplateParser.Parse(pi)
|
|
if err != nil {
|
|
return tf, false, err
|
|
}
|
|
if ok {
|
|
tf.Nodes = append(tf.Nodes, sn)
|
|
_, _, _ = parse.OptionalWhitespace.Parse(pi)
|
|
continue
|
|
}
|
|
|
|
// Anything that isn't template content is Go code.
|
|
code := new(strings.Builder)
|
|
from := pi.Position()
|
|
inner:
|
|
for {
|
|
// Check to see if this line isn't Go code.
|
|
last := pi.Index()
|
|
var l string
|
|
if l, ok, err = stringUntilNewLineOrEOF.Parse(pi); err != nil {
|
|
return
|
|
}
|
|
hasTemplatePrefix := strings.HasPrefix(l, "templ ") || strings.HasPrefix(l, "css ") || strings.HasPrefix(l, "script ")
|
|
if hasTemplatePrefix && strings.Contains(l, "(") {
|
|
// Unread the line.
|
|
pi.Seek(last)
|
|
// Take the code so far.
|
|
if code.Len() > 0 {
|
|
expr := NewExpression(strings.TrimSpace(code.String()), from, pi.Position())
|
|
tf.Nodes = append(tf.Nodes, TemplateFileGoExpression{Expression: expr})
|
|
}
|
|
// Carry on parsing.
|
|
break inner
|
|
}
|
|
code.WriteString(l)
|
|
|
|
// Eat the newline or EOF that we read until.
|
|
var newLine string
|
|
if newLine, ok, err = parse.NewLine.Parse(pi); err != nil {
|
|
return
|
|
}
|
|
code.WriteString(newLine)
|
|
if _, isEOF, _ := parse.EOF[string]().Parse(pi); isEOF {
|
|
if code.Len() > 0 {
|
|
expr := NewExpression(strings.TrimSpace(code.String()), from, pi.Position())
|
|
tf.Nodes = append(tf.Nodes, TemplateFileGoExpression{Expression: expr})
|
|
}
|
|
// Stop parsing.
|
|
break outer
|
|
}
|
|
}
|
|
}
|
|
|
|
return tf, true, nil
|
|
}
|