gopp/tengo.go
2024-06-26 20:29:38 +05:00

237 lines
5.1 KiB
Go

package tpp
import (
"github.com/d5/tengo/v2"
"fmt"
"context"
"bytes"
"strconv"
)
type CompiledMap map[string] *Compiled
type Script = tengo.Script
type Compiled = tengo.Compiled
// The type describes
// customizable way to evaluate
// files.
type Tengo struct {
// Functions to modify
// preprocessor for
// more specific purposes.
preCode func(context.Context) []byte
postCode func(context.Context) []byte
preCompile func(context.Context, *Script)
}
// Returns the new Tengo preprocessor
// with
func NewTengo() *Tengo {
ret := &Tengo{}
return ret
}
func (tengo *Tengo) SetPreCode(fn func(context.Context) []byte) *Tengo {
tengo.preCode = fn
return tengo
}
func (tengo *Tengo) SetPostCode(fn func(context.Context) []byte) *Tengo {
tengo.postCode = fn
return tengo
}
func (tengo *Tengo) SetPreCompile(fn func(context.Context, *Script)) *Tengo {
tengo.preCompile = fn
return tengo
}
func (pp *Tengo) Eval(
ctx context.Context,
// File path to give it.
filePath string,
// Static text pieces.
texts [][]byte,
// The code that comes before any
// dynamic code.
preCode []byte,
// General code.
codes [][]byte,
) (*Compiled, []byte, error) {
var fullCodeBuf, retBuf bytes.Buffer
const retHead = `
context := {}
pp := immutable({
fpath: __pp_filepath__,
printf : __pp_printf__,
print : __pp_print__,
println : __pp_println__,
write_raw : __pp_write_raw__,
include: __pp_include__
})
`
const retSeparator = `
__separate__()
`
fmt.Fprint(&fullCodeBuf, retHead)
if preCode != nil {
fmt.Fprintln(
&fullCodeBuf,
string(preCode)+"\n",
)
}
if pp.preCode != nil {
fullCodeBuf.Write(pp.preCode(ctx))
fullCodeBuf.Write([]byte(retSeparator))
}
for i, code := range codes {
fmt.Fprintf(
&fullCodeBuf,
"\n__pp_write_raw__(%q)\n",
texts[i],
)
fmt.Fprintln(
&fullCodeBuf,
"\n" + string(code) + retSeparator,
)
}
fmt.Fprintf(
&fullCodeBuf,
"\n__pp_write_raw__(%q)\n",
texts[len(texts)-1],
)
if pp.postCode != nil {
fullCodeBuf.Write(pp.postCode(ctx))
fullCodeBuf.Write([]byte(retSeparator))
}
script := tengo.NewScript(fullCodeBuf.Bytes())
if pp.preCompile != nil {
pp.preCompile(ctx, script)
}
script.Add("__pp_include__", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){
if len(args) < 1 {
return nil, tengo.ErrWrongNumArguments
}
format, ok := tengo.ToString(args[0])
if !ok {
return nil, tengo.ErrInvalidArgumentType{
Expected: "string",
}
}
gargs := make([]any, len(args) - 1)
for i := range gargs {
gargs[i] = tengo.ToInterface(args[i+1])
//fmt.Printf("shit: %q\n", gargs[i])
}
fmt.Fprintf(&retBuf, format, gargs...)
return nil, nil
//return tengo.FromInterface([]byte(str))
},
})
script.Add("__pp_filepath__", filePath)
script.Add("__pp_printf__", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){
if len(args) < 1 {
return nil, tengo.ErrWrongNumArguments
}
format, ok := tengo.ToString(args[0])
if !ok {
return nil, tengo.ErrInvalidArgumentType{
Expected: "string",
}
}
gargs := make([]any, len(args) - 1)
for i := range gargs {
gargs[i] = tengo.ToInterface(args[i+1])
//fmt.Printf("shit: %q\n", gargs[i])
}
fmt.Fprintf(&retBuf, format, gargs...)
return nil, nil
//return tengo.FromInterface([]byte(str))
},
})
script.Add("__pp_print__", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){
gargs := make([]any, len(args))
for i := range gargs {
gargs[i] = tengo.ToInterface(args[i])
}
fmt.Fprint(&retBuf, gargs...)
return nil, nil
//return tengo.FromInterface([]byte(str))
},
})
script.Add("__pp_println__", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){
gargs := make([]any, len(args))
for i := range gargs {
gargs[i] = tengo.ToInterface(args[i])
}
fmt.Fprintln(&retBuf, gargs...)
return nil, nil
//return tengo.FromInterface([]byte(str))
},
})
script.Add("__pp_write_raw__", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){
bt := make([][]byte, len(args))
for i, o := range args {
bts, ok := tengo.ToByteSlice(o)
if !ok {
return nil, tengo.ErrInvalidArgumentType{
Name: strconv.Itoa(i),
Expected: "string/bytes",
Found: o.TypeName(),
}
}
bt[i] = bts
}
for _, b := range bt {
retBuf.Write(b)
}
return nil, nil
},
})
script.Add("__separate__", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){
return nil, nil
/*if len(args) < 1 {
return nil, tengo.ErrWrongNumArguments
}
bts, ok := tengo.ToByteSlice(args[0])
if !ok {
return nil, tengo.ErrInvalidArgumentType{
}
}*/
//rets = append(rets, retBuf.Bytes())
//retBuf.Reset()
return nil, nil
},
})
compiled, err := script.Compile()
if err != nil {
return nil, nil, err
}
// To keep everything of the changes.
compiled = compiled.Clone()
_, err = script.RunContext(ctx)
if err != nil {
return nil, nil, err
}
return compiled, retBuf.Bytes(), nil
}