From 34b6f06901a2718c85546fda10e95fb241662165 Mon Sep 17 00:00:00 2001 From: surdeus Date: Fri, 26 Apr 2024 00:38:04 +0500 Subject: [PATCH] feat: use bytes instead of string to be able to generate binary files, started implementing PHP-like server. --- .gitignore | 2 +- build.sh | 2 +- cmd/tenserve/main.go | 25 ++++++++++++++ cmd/tpp/main.go | 10 ++++++ errors.go | 2 +- eval.go | 4 +-- go.mod | 2 +- main.go | 61 ++++++++++++++++++++------------- server/handler.go | 26 ++++++++++++++ tengo.go | 81 ++++++++++++++++++++++++++++++++------------ tests/index.md.pp | 14 ++++---- tool.go | 26 +++++++++++--- 12 files changed, 193 insertions(+), 62 deletions(-) create mode 100644 cmd/tenserve/main.go create mode 100644 cmd/tpp/main.go create mode 100644 server/handler.go diff --git a/.gitignore b/.gitignore index ed4f642..812817f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -pp +/tpp diff --git a/build.sh b/build.sh index 568817a..e74ef3e 100755 --- a/build.sh +++ b/build.sh @@ -1,3 +1,3 @@ #!/bin/sh # -go build ./cmd/pp +go build ./cmd/tpp diff --git a/cmd/tenserve/main.go b/cmd/tenserve/main.go new file mode 100644 index 0000000..0ed8189 --- /dev/null +++ b/cmd/tenserve/main.go @@ -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()) +} + diff --git a/cmd/tpp/main.go b/cmd/tpp/main.go new file mode 100644 index 0000000..a827a8b --- /dev/null +++ b/cmd/tpp/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "vultras.su/util/tpp" + "os" +) + +func main() { + tpp.Tool.Run(os.Args[1:]) +} diff --git a/errors.go b/errors.go index 05059a2..658c65f 100644 --- a/errors.go +++ b/errors.go @@ -1,4 +1,4 @@ -package pp +package tpp import ( //"errors" diff --git a/eval.go b/eval.go index 3421bcf..816b3dc 100644 --- a/eval.go +++ b/eval.go @@ -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) } diff --git a/go.mod b/go.mod index 205ce5e..9623bd2 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module vultras.su/util/pp +module vultras.su/util/tpp go 1.21.7 diff --git a/main.go b/main.go index 8747af4..a4f0c25 100644 --- a/main.go +++ b/main.go @@ -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)]) + + b.Write(texts[len(codeRets)]) - return b.String(), nil + return b.Bytes(), nil } diff --git a/server/handler.go b/server/handler.go new file mode 100644 index 0000000..a49a853 --- /dev/null +++ b/server/handler.go @@ -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) +} diff --git a/tengo.go b/tengo.go index eb4a3ae..b343476 100644 --- a/tengo.go +++ b/tengo.go @@ -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 } diff --git a/tests/index.md.pp b/tests/index.md.pp index 782f5c2..aa1a413 100644 --- a/tests/index.md.pp +++ b/tests/index.md.pp @@ -1,20 +1,20 @@ -# The index testing - 1 + 1 = - cock +}}# The index testing + 1 + 1 = {{ print(1+1) }} + cock {{ print(newVar, "and cock") }} ## The shit after -checking +checking {{ printf(newVar) }} ## File contents - +}} diff --git a/tool.go b/tool.go index 2c9d42e..92e9826 100644 --- a/tool.go +++ b/tool.go @@ -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) } })