feat: use bytes instead of string to be able to generate binary files, started implementing PHP-like server.

This commit is contained in:
Andrey Parhomenko 2024-04-26 00:38:04 +05:00
parent 1eb5e35fa3
commit 34b6f06901
12 changed files with 193 additions and 62 deletions

2
.gitignore vendored
View file

@ -1 +1 @@
pp
/tpp

View file

@ -1,3 +1,3 @@
#!/bin/sh
#
go build ./cmd/pp
go build ./cmd/tpp

25
cmd/tenserve/main.go Normal file
View file

@ -0,0 +1,25 @@
package main
import (
"flag"
"net/http"
"vultras.su/util/tpp/server"
)
func main() {
var (
addr, src string
)
flag.StringVar(&addr, "http", ":8080", "HTTP address to serve")
flag.StringVar(&src, "src", "", "path to the source directory")
srv := http.Server{
Address: addr,
Handler: &server.Handler{
SourcePath: src,
},
}
log.Fatal(srv.ListenAndServe())
}

10
cmd/tpp/main.go Normal file
View file

@ -0,0 +1,10 @@
package main
import (
"vultras.su/util/tpp"
"os"
)
func main() {
tpp.Tool.Run(os.Args[1:])
}

View file

@ -1,4 +1,4 @@
package pp
package tpp
import (
//"errors"

View file

@ -1,6 +1,6 @@
package pp
package tpp
type Evaler interface {
Tags() [2]string
Eval(string, []string) ([]string, error)
Eval(filePath string, pieces []string) ([]string, error)
}

2
go.mod
View file

@ -1,4 +1,4 @@
module vultras.su/util/pp
module vultras.su/util/tpp
go 1.21.7

61
main.go
View file

@ -1,43 +1,52 @@
package pp
package tpp
import (
"strings"
"fmt"
//"fmt"
"bytes"
"context"
)
type Preprocessor struct {
evaler Evaler
tags [2]string
tengo *Tengo
tags [2][]byte
}
// Get the new preprocessor with default options.
func NewPp(evaler Evaler) *Preprocessor {
pp := &Preprocessor{
tags: evaler.Tags(),
func New(tengo *Tengo ) *Preprocessor {
pp := &Preprocessor{}
pp.tengo = tengo
pp.tags = [2][]byte{
[]byte("{{"),
[]byte("}}"),
}
pp.evaler = evaler
return pp
}
func (pp *Preprocessor) Process(filePath string, data string) (string, error) {
var b strings.Builder
func (pp *Preprocessor) Process(
ctx context.Context,
recompile bool,
filePath string,
data []byte,
) ([]byte, error) {
var b bytes.Buffer
last := 0
texts := []string{}
codes := []string{}
texts := [][]byte{}
codes := [][]byte{}
for {
idxStart := strings.Index(data[last:], pp.tags[0])
idxEnd := strings.Index(data[last:], pp.tags[1])
idxStart := bytes.Index(data[last:], pp.tags[0])
idxEnd := bytes.Index(data[last:], pp.tags[1])
//fmt.Printf("cock %d %d %d\n", last, idxStart, idxEnd)
if idxStart < 0 {
if idxEnd >= 0 {
return "", UnexpectedError{
return nil, UnexpectedError{
What: "end tag",
}
}
texts = append(texts, data[last:])
break
} else if idxEnd < 0 {
return "", UnexpectedError{
return nil, UnexpectedError{
What: "start tag",
}
}
@ -52,17 +61,23 @@ func (pp *Preprocessor) Process(filePath string, data string) (string, error) {
data = data[1:]
}*/
}
codeRets, err := pp.evaler.Eval(filePath, codes)
codeRets, err := pp.tengo.Eval(
ctx,
recompile,
filePath,
codes,
)
if err != nil {
return "", err
return nil, err
}
for i, codeRet := range codeRets {
fmt.Fprint(&b, texts[i])
fmt.Fprintf(&b, codeRet)
b.Write(texts[i])
b.Write(codeRet)
}
fmt.Fprint(&b, texts[len(codeRets)])
return b.String(), nil
b.Write(texts[len(codeRets)])
return b.Bytes(), nil
}

26
server/handler.go Normal file
View file

@ -0,0 +1,26 @@
package server
import (
"net/http"
"path"
"os"
)
var _ = http.Handler(&Handler{})
// The type describes behaviour
// of handling stuff like in PHP.
type Handler struct {
// THe field represents
// where we store site's
// source files and request handlers.
SourcePath string
}
func (h *Handler) ServeHTTP(
w http.ResponseWriter,
r *http.Request,
) {
p := r.URL.Path
p = path.Clean(p)
}

View file

@ -1,32 +1,63 @@
package pp
package tpp
import (
"github.com/d5/tengo/v2"
"github.com/d5/tengo/v2/stdlib"
"fmt"
"context"
"bytes"
)
type Script = tengo.Script
type Compiled = tengo.Compiled
type Tengo struct {
head string
modules *tengo.ModuleMap
compiledScripts map[string] *Compiled
// Functions to modify
// preprocessor for
// more specific purposes.
preCode func() []byte
postCode func() []byte
preCompile func(*Script)
}
// Returns the new Tengo preprocessor
// with
func NewTengo() *Tengo {
evaler := &Tengo{}
evaler.modules = stdlib.GetModuleMap(stdlib.AllModuleNames()...)
return evaler
ret := &Tengo{}
return ret
}
func (tengo *Tengo) SetPreCode(fn func() []byte) *Tengo {
tengo.preCode = fn
return tengo
}
func (tengo *Tengo) SetPostCode(fn func() []byte) *Tengo {
tengo.postCode = fn
return tengo
}
func (tengo *Tengo) SetPreCompile(fn func(*Script)) *Tengo {
tengo.preCompile = fn
return tengo
}
func (pp *Tengo) Tags() [2]string {
return [2]string{
"<?",
"?>",
"{{",
"}}",
}
}
// Simple Evaler implementation for the Tengo language
func (pp *Tengo) Eval(filePath string, codes []string) ([]string, error) {
func (pp *Tengo) Eval(
ctx context.Context,
recompile bool,
filePath string,
codes [][]byte,
) ([][]byte, error) {
var fullCodeBuf bytes.Buffer
const retHead = `
__ret_one__ := ""
printf := func(format, ...vals) {
@ -43,20 +74,28 @@ func (pp *Tengo) Eval(filePath string, codes []string) ([]string, error) {
`
const retSeparator = `
__Separate(__ret_one__)
__separate(__ret_one__)
__ret_one__ = ""
`
rets := []string{}
rets := [][]byte{}
fullCode := retHead + "\n" + pp.head
for _, code := range codes {
fullCode += "\n" + code + retSeparator + "\n"
fmt.Fprint(&fullCodeBuf, retHead)
if pp.preCode != nil {
fullCodeBuf.Write(pp.preCode())
}
script := tengo.NewScript([]byte(fullCode))
script.SetImports(pp.modules)
script.EnableFileImport(true)
script.SetImportDir(".")
for _, code := range codes {
fmt.Fprintln(&fullCodeBuf, "\n" + string(code) + retSeparator)
}
if pp.postCode != nil {
fullCodeBuf.Write(pp.postCode())
}
script := tengo.NewScript(fullCodeBuf.Bytes())
if pp.preCompile != nil {
pp.preCompile(script)
}
err := script.Add("sprintf", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){
if len(args) < 1 {
@ -109,12 +148,12 @@ func (pp *Tengo) Eval(filePath string, codes []string) ([]string, error) {
if err != nil {
return nil, err
}
err = script.Add("__Separate", &tengo.UserFunction{
err = script.Add("__separate", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){
if len(args) < 1 {
return nil, tengo.ErrWrongNumArguments
}
str, ok := tengo.ToString(args[0])
str, ok := tengo.ToByteSlice(args[0])
if !ok {
return nil, tengo.ErrInvalidArgumentType{
}
@ -128,7 +167,7 @@ func (pp *Tengo) Eval(filePath string, codes []string) ([]string, error) {
return nil, err
}
_, err = script.RunContext(context.Background())
_, err = script.RunContext(ctx)
if err != nil {
return nil, err
}

View file

@ -1,20 +1,20 @@
<?
{{
os := import("os")
shit := import("./tests/shit.tengo")
newVar := "'this is gen shita'"
println(shit.some_func())
?># The index testing
1 + 1 = <? print(1+1) ?>
cock <? print(newVar, "and cock")?>
}}# The index testing
1 + 1 = {{ print(1+1) }}
cock {{ print(newVar, "and cock") }}
## The shit after
checking <? printf(newVar) ?>
checking {{ printf(newVar) }}
## File contents
<?
{{
bts := os.read_file("tests/somefile")
printf("%v\n%s", bts, bts)
?>
}}

26
tool.go
View file

@ -1,15 +1,26 @@
package pp
package tpp
import (
//"github.com/d5/tengo/v2"
"github.com/d5/tengo/v2/stdlib"
"vultras.su/core/cli/mtool"
"fmt"
//"fmt"
"os"
"log"
"path/filepath"
"context"
)
var Tool = mtool.T("pp").Func(func(flags *mtool.Flags){
pp := NewPp(NewTengo())
t := NewTengo().
SetPreCompile(func(s *Script){
s.SetImports(stdlib.GetModuleMap(
stdlib.AllModuleNames()...,
))
s.EnableFileImport(true)
s.SetImportDir(".")
})
pp := New(t)
filePaths := flags.Parse()
for _, filePath := range filePaths {
pth := filepath.FromSlash(filePath)
@ -18,11 +29,16 @@ var Tool = mtool.T("pp").Func(func(flags *mtool.Flags){
log.Println("read error:", err)
continue
}
str, err := pp.Process(pth, string(bts))
out, err := pp.Process(
context.Background(),
true,
pth,
bts,
)
if err != nil {
log.Println("pp error:", err)
continue
}
fmt.Print(str)
os.Stdout.Write(out)
}
})