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 #!/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 ( import (
//"errors" //"errors"

View file

@ -1,6 +1,6 @@
package pp package tpp
type Evaler interface { type Evaler interface {
Tags() [2]string 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 go 1.21.7

61
main.go
View file

@ -1,43 +1,52 @@
package pp package tpp
import ( import (
"strings" //"fmt"
"fmt" "bytes"
"context"
) )
type Preprocessor struct { type Preprocessor struct {
evaler Evaler tengo *Tengo
tags [2]string tags [2][]byte
} }
// Get the new preprocessor with default options. // Get the new preprocessor with default options.
func NewPp(evaler Evaler) *Preprocessor { func New(tengo *Tengo ) *Preprocessor {
pp := &Preprocessor{ pp := &Preprocessor{}
tags: evaler.Tags(), pp.tengo = tengo
pp.tags = [2][]byte{
[]byte("{{"),
[]byte("}}"),
} }
pp.evaler = evaler
return pp return pp
} }
func (pp *Preprocessor) Process(filePath string, data string) (string, error) { func (pp *Preprocessor) Process(
var b strings.Builder ctx context.Context,
recompile bool,
filePath string,
data []byte,
) ([]byte, error) {
var b bytes.Buffer
last := 0 last := 0
texts := []string{} texts := [][]byte{}
codes := []string{} codes := [][]byte{}
for { for {
idxStart := strings.Index(data[last:], pp.tags[0]) idxStart := bytes.Index(data[last:], pp.tags[0])
idxEnd := strings.Index(data[last:], pp.tags[1]) idxEnd := bytes.Index(data[last:], pp.tags[1])
//fmt.Printf("cock %d %d %d\n", last, idxStart, idxEnd) //fmt.Printf("cock %d %d %d\n", last, idxStart, idxEnd)
if idxStart < 0 { if idxStart < 0 {
if idxEnd >= 0 { if idxEnd >= 0 {
return "", UnexpectedError{ return nil, UnexpectedError{
What: "end tag", What: "end tag",
} }
} }
texts = append(texts, data[last:]) texts = append(texts, data[last:])
break break
} else if idxEnd < 0 { } else if idxEnd < 0 {
return "", UnexpectedError{ return nil, UnexpectedError{
What: "start tag", What: "start tag",
} }
} }
@ -52,17 +61,23 @@ func (pp *Preprocessor) Process(filePath string, data string) (string, error) {
data = data[1:] data = data[1:]
}*/ }*/
} }
codeRets, err := pp.evaler.Eval(filePath, codes) codeRets, err := pp.tengo.Eval(
ctx,
recompile,
filePath,
codes,
)
if err != nil { if err != nil {
return "", err return nil, err
} }
for i, codeRet := range codeRets { for i, codeRet := range codeRets {
fmt.Fprint(&b, texts[i]) b.Write(texts[i])
fmt.Fprintf(&b, codeRet) b.Write(codeRet)
} }
fmt.Fprint(&b, texts[len(codeRets)])
b.Write(texts[len(codeRets)])
return b.String(), nil 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 ( import (
"github.com/d5/tengo/v2" "github.com/d5/tengo/v2"
"github.com/d5/tengo/v2/stdlib"
"fmt" "fmt"
"context" "context"
"bytes"
) )
type Script = tengo.Script
type Compiled = tengo.Compiled
type Tengo struct { type Tengo struct {
head string 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 { func NewTengo() *Tengo {
evaler := &Tengo{} ret := &Tengo{}
evaler.modules = stdlib.GetModuleMap(stdlib.AllModuleNames()...) return ret
return evaler }
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 { func (pp *Tengo) Tags() [2]string {
return [2]string{ return [2]string{
"<?", "{{",
"?>", "}}",
} }
} }
// Simple Evaler implementation for the Tengo language // 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 = ` const retHead = `
__ret_one__ := "" __ret_one__ := ""
printf := func(format, ...vals) { printf := func(format, ...vals) {
@ -43,20 +74,28 @@ func (pp *Tengo) Eval(filePath string, codes []string) ([]string, error) {
` `
const retSeparator = ` const retSeparator = `
__Separate(__ret_one__) __separate(__ret_one__)
__ret_one__ = "" __ret_one__ = ""
` `
rets := []string{} rets := [][]byte{}
fullCode := retHead + "\n" + pp.head fmt.Fprint(&fullCodeBuf, retHead)
for _, code := range codes { if pp.preCode != nil {
fullCode += "\n" + code + retSeparator + "\n" fullCodeBuf.Write(pp.preCode())
} }
script := tengo.NewScript([]byte(fullCode)) for _, code := range codes {
script.SetImports(pp.modules) fmt.Fprintln(&fullCodeBuf, "\n" + string(code) + retSeparator)
script.EnableFileImport(true) }
script.SetImportDir(".") 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{ err := script.Add("sprintf", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){ Value: func(args ...tengo.Object) (tengo.Object, error){
if len(args) < 1 { if len(args) < 1 {
@ -109,12 +148,12 @@ func (pp *Tengo) Eval(filePath string, codes []string) ([]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = script.Add("__Separate", &tengo.UserFunction{ err = script.Add("__separate", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){ Value: func(args ...tengo.Object) (tengo.Object, error){
if len(args) < 1 { if len(args) < 1 {
return nil, tengo.ErrWrongNumArguments return nil, tengo.ErrWrongNumArguments
} }
str, ok := tengo.ToString(args[0]) str, ok := tengo.ToByteSlice(args[0])
if !ok { if !ok {
return nil, tengo.ErrInvalidArgumentType{ return nil, tengo.ErrInvalidArgumentType{
} }
@ -128,7 +167,7 @@ func (pp *Tengo) Eval(filePath string, codes []string) ([]string, error) {
return nil, err return nil, err
} }
_, err = script.RunContext(context.Background()) _, err = script.RunContext(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

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

26
tool.go
View file

@ -1,15 +1,26 @@
package pp package tpp
import ( import (
//"github.com/d5/tengo/v2"
"github.com/d5/tengo/v2/stdlib"
"vultras.su/core/cli/mtool" "vultras.su/core/cli/mtool"
"fmt" //"fmt"
"os" "os"
"log" "log"
"path/filepath" "path/filepath"
"context"
) )
var Tool = mtool.T("pp").Func(func(flags *mtool.Flags){ 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() filePaths := flags.Parse()
for _, filePath := range filePaths { for _, filePath := range filePaths {
pth := filepath.FromSlash(filePath) pth := filepath.FromSlash(filePath)
@ -18,11 +29,16 @@ var Tool = mtool.T("pp").Func(func(flags *mtool.Flags){
log.Println("read error:", err) log.Println("read error:", err)
continue continue
} }
str, err := pp.Process(pth, string(bts)) out, err := pp.Process(
context.Background(),
true,
pth,
bts,
)
if err != nil { if err != nil {
log.Println("pp error:", err) log.Println("pp error:", err)
continue continue
} }
fmt.Print(str) os.Stdout.Write(out)
} }
}) })