From 5db9429bd896603807565f6ea2903dfdeaba4e7f Mon Sep 17 00:00:00 2001 From: surdeus Date: Sun, 9 Jun 2024 17:16:38 +0500 Subject: [PATCH] init --- .gitignore | 3 + build.sh | 4 + cmd/tht/main.go | 10 +++ go.mod | 11 +++ go.sum | 10 +++ install.sh | 3 + mdx/main.go | 83 +++++++++++++++++ mod/post.tengo | 26 ++++++ mod/pre.tengo | 40 +++++++++ server/handler.go | 189 +++++++++++++++++++++++++++++++++++++++ server/tool.go | 53 +++++++++++ src/check.htm.tpp | 5 ++ src/check/index.htm.tpp | 12 +++ src/contact/edit.htm.tpp | 20 +++++ src/contact/view.htm.tpp | 13 +++ src/incorrect.htm.tpp | 11 +++ src/index.htm.tpp | 78 ++++++++++++++++ src/main.htm.tpp | 37 ++++++++ src/page.htm.tpp | 35 ++++++++ src/req.json.tpp | 8 ++ src/request.json | 3 + src/script.js | 3 + 22 files changed, 657 insertions(+) create mode 100644 .gitignore create mode 100755 build.sh create mode 100644 cmd/tht/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100755 install.sh create mode 100644 mdx/main.go create mode 100644 mod/post.tengo create mode 100644 mod/pre.tengo create mode 100644 server/handler.go create mode 100644 server/tool.go create mode 100644 src/check.htm.tpp create mode 100644 src/check/index.htm.tpp create mode 100644 src/contact/edit.htm.tpp create mode 100644 src/contact/view.htm.tpp create mode 100644 src/incorrect.htm.tpp create mode 100644 src/index.htm.tpp create mode 100644 src/main.htm.tpp create mode 100644 src/page.htm.tpp create mode 100644 src/req.json.tpp create mode 100644 src/request.json create mode 100644 src/script.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d128777 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/tpp +/exe +Session.vim diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..4c66820 --- /dev/null +++ b/build.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# +go build -o ./exe/ ./cmd/tht + diff --git a/cmd/tht/main.go b/cmd/tht/main.go new file mode 100644 index 0000000..a8c70b7 --- /dev/null +++ b/cmd/tht/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "surdeus.su/util/tht/server" + "os" +) + +func main() { + server.Tool.Run(os.Args[1:]) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a99fda1 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module surdeus.su/util/tht + +go 1.22.3 + +require ( + github.com/d5/tengo/v2 v2.17.0 + github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2 + surdeus.su/core/cli v0.1.2 + surdeus.su/core/xgo v0.5.0 + surdeus.su/util/tpp v0.2.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2745b13 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/d5/tengo/v2 v2.17.0 h1:BWUN9NoJzw48jZKiYDXDIF3QrIVZRm1uV1gTzeZ2lqM= +github.com/d5/tengo/v2 v2.17.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8= +github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2 h1:yEt5djSYb4iNtmV9iJGVday+i4e9u6Mrn5iP64HH5QM= +github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +surdeus.su/core/cli v0.1.2 h1:qPzjawqPyZsO4Z5SaA1u141recVE65yioA83Qs7Jecs= +surdeus.su/core/cli v0.1.2/go.mod h1:r9JtQz3aEJzpYzMaNUNQHJoYkoWKNPi047qhd5uGlmA= +surdeus.su/core/xgo v0.5.0 h1:/Rk3scfFkoSb0qjHRlkUNOp9sr/fd7wAvCiT4fBRo+U= +surdeus.su/core/xgo v0.5.0/go.mod h1:6C/AHbjfvAMvt3TOzLB4eIZ40eU3ahJXtdY+kr4yXoc= +surdeus.su/util/tpp v0.2.0 h1:9N78JPEUovDWdKnrdfrf05Jkz5xMA5hcBEWCIh4RWW0= +surdeus.su/util/tpp v0.2.0/go.mod h1:3bcSjGNd3vAevr6gPMI1/qJofxL7x53v0yB+Dr18CRM= diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..e184488 --- /dev/null +++ b/install.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +go install ./cmd/tht/ diff --git a/mdx/main.go b/mdx/main.go new file mode 100644 index 0000000..a5bcd63 --- /dev/null +++ b/mdx/main.go @@ -0,0 +1,83 @@ +package mdx +import "github.com/gomarkdown/markdown" +import "github.com/gomarkdown/markdown/html" +import "github.com/gomarkdown/markdown/parser" +import "github.com/d5/tengo/v2" +import "bytes" + +type Markdown struct { + tengo.ObjectImpl + MakeParser func() *parser.Parser + MakeRenderer func() *html.Renderer +} + +func MakeDefaultMarkdown() *Markdown { + return &Markdown{ + MakeParser: MakeDefaultParser, + MakeRenderer: MakeDefaultRenderer, + } +} + +func (md *Markdown) TypeName() string { + return "*Markdown" +} + +func (md *Markdown) String() string { + if md == nil { + return "" + } + return "&Markdown{...}" +} + +func (md *Markdown) Render( + data []byte, +) ([]byte, error) { + doc := md.MakeParser().Parse(data) + rendered := markdown.Render(doc, md.MakeRenderer()) + return rendered, nil +} + +func (md *Markdown) CanCall() bool { + return md != nil +} + +func (md *Markdown) Call( + args ...tengo.Object, +) (tengo.Object, error) { + var b bytes.Buffer + for _, arg := range args { + str, ok := tengo.ToString(arg) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "v", + Expected: "stringer", + Found: arg.TypeName(), + } + } + b.Write([]byte(str)) + } + + rendered, err := md.Render(b.Bytes()) + if err != nil { + return nil, err + } + + return tengo.FromInterface(rendered) +} + +func MakeDefaultParser() *parser.Parser { + return parser.NewWithExtensions( + parser.CommonExtensions | parser.AutoHeadingIDs | + parser.NoEmptyLineBeforeBlock | parser.Attributes | + parser.Tables, + ) +} + +func MakeDefaultRenderer() *html.Renderer { + return html.NewRenderer( + html.RendererOptions{ + Flags: html.CommonFlags | html.HrefTargetBlank, + }, + ) +} + diff --git a/mod/post.tengo b/mod/post.tengo new file mode 100644 index 0000000..c4a439e --- /dev/null +++ b/mod/post.tengo @@ -0,0 +1,26 @@ +fmt := import("fmt") +paths := import("paths") +htmExt := func(c){ + if c.is_compo { + return + } + + c.pp.print(``) +} + +exts := { + ".htm": htmExt, + ".html": htmExt +} + +export func(c){ + ext := paths.ext(c.pp.filepath) + ext = paths.ext( + c.pp.filepath[:len(c.pp.filepath) - len(ext)] + ) + + cl := exts[ext] + if !is_undefined(cl) { + cl(c) + } +} diff --git a/mod/pre.tengo b/mod/pre.tengo new file mode 100644 index 0000000..b550388 --- /dev/null +++ b/mod/pre.tengo @@ -0,0 +1,40 @@ +fmt := import("fmt") +paths := import("paths") +html := import("html").new_render() + +htmExt := func(c){ + if c.is_compo { + return + } + c.pp.print(``) + + c.pp.print(html.head().body( + func(){ + if c.title { + return html.title().body(c.title) + } + }(), + html.script({ + src: `https://unpkg.com/htmx.org@1.9.2` + }) + )) + + c.pp.print(``) +} + +exts := { + ".htm": htmExt, + ".html": htmExt +} + +export func(c){ + ext := paths.ext(c.pp.filepath) + ext = paths.ext( + c.pp.filepath[:len(c.pp.filepath) - len(ext)] + ) + + cl := exts[ext] + if cl { + cl(c) + } +} diff --git a/server/handler.go b/server/handler.go new file mode 100644 index 0000000..daa2408 --- /dev/null +++ b/server/handler.go @@ -0,0 +1,189 @@ +package server + +import ( + //"github.com/d5/tengo/v2" + "surdeus.su/util/tpp/mdx" + //"surdeus.su/core/xgo/xmodules/htmlx" + "surdeus.su/core/xgo/xmodules/httpx" + "surdeus.su/core/xgo/xmodules" + "surdeus.su/util/tpp" + "path/filepath" + "net/http" + "context" + "path" + "mime" + "log" + "os" + "io" +) + +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 + // Preprocessor must be set by user + // to be able to bring custom features in. + pp *tpp.Preprocessor + // Aditional extension. ".tpp" by default. + // For example "file.html.tpp" will be + // first preprocessed TPP and sent back as simple HTML. + ext string + + // The global map accessable from all the + // request so you can keep the states + // between requests. + global any + md *mdx.Markdown + indexSuffix string +} + +// Returns the new Handler with +// specified preprocessor, source path, +// preprocessor extension and the global value. +func NewHandler( + pp *tpp.Preprocessor, + src, ext, index string, + global any, +) *Handler { + ret := &Handler{} + ret.sourcePath = src + ret.ext = ext + ret.pp = pp + ret.global = global + ret.indexSuffix = index + return ret +} + +// Returns the default tpp.Preprocessor suitable for +// the most needs. +func DefaultPP(mod string) *tpp.Preprocessor { + t := tpp.NewTengo().SetPreCompile(func( + ctx context.Context, + s *tpp.Script, + ) { + s.SetImportDir(mod) + s.SetImports(xmodules.Modules) + s.EnableFileImport(true) + + s.Add("__http_request__", ctx.Value(KeyRequest)) + s.Add("__global__", ctx.Value(KeyGlobal)) + s.Add("__markdown__", ctx.Value(KeyMarkdown)) + }).SetPreCode(func(ctx context.Context) []byte { + return []byte(` + markdown := func(...args) { + pp.write_raw(__markdown__(args...)) + } + __http__ := immutable({ + request : __http_request__ + }) + context.http = __http__ + context.pp = pp + context.global = __global__ + import("./pre")(context) + `) + }).SetPostCode(func(ctx context.Context) []byte { + return []byte(` + import("./post")(context) + `) + }) + + return tpp.New(t) +} + +func (h *Handler) SetMD(md *mdx.Markdown) *Handler { + h.md = md + return h +} + +func (h *Handler) ServeHTTP( + w http.ResponseWriter, + r *http.Request, +) { + shouldProcess := true + urlPath := r.URL.Path + // Cleaning URL path to prevent injections. + urlPath = path.Clean(urlPath) + urlExt := path.Ext(urlPath) + + filePath := filepath.Join( + filepath.FromSlash(h.sourcePath), + filepath.FromSlash(urlPath), + ) + fileStat, err := os.Stat(filePath) + if err == nil { + if fileStat.IsDir() { + filePath = filepath.Join( + filePath, + h.indexSuffix, + ) + } + } + + filePathTpp := filePath + h.ext + //log.Println("pth:", urlPath, filePathTpp) + file, err := os.Open(filePathTpp) + if err != nil { + shouldProcess = false + file, err = os.Open(filePath) + if err != nil { + http.NotFound(w, r) + return + } + } + + //process := true + fileData, err := io.ReadAll(file) + if err != nil { + http.NotFound(w, r) + return + } + + ctx := context.WithValue( + r.Context(), + KeyRequest, + &httpx.Request{ + Request: r, + }, + ) + + ctx = context.WithValue( + ctx, + KeyGlobal, + h.global, + ) + + ctx = context.WithValue( + ctx, + KeyMarkdown, + h.md, + ) + + // Setting before the code to let it change own + // content type? + contentType := mime.TypeByExtension(urlExt) + w.Header().Set("Content-Type", contentType) + processedData := fileData + if shouldProcess { + processedData, err = h.pp.Process( + ctx, + true, + filePathTpp, + fileData, + ) + if err != nil { + http.NotFound(w, r) + log.Printf( + "Error: pp.Process(...): %s\n", + err, + ) + return + } + } + + w.Write(processedData) +} diff --git a/server/tool.go b/server/tool.go new file mode 100644 index 0000000..24cebfc --- /dev/null +++ b/server/tool.go @@ -0,0 +1,53 @@ +package server + +import ( + + "github.com/d5/tengo/v2" + //"github.com/d5/tengo/v2/stdlib" + "surdeus.su/util/tpp/mdx" + "surdeus.su/core/cli/mtool" + "net/http" + "log" + //"context" +) + +// Context key type for internal usage. +type CKey string + +const ( + KeyRequest CKey = "http-request" + KeyGlobal = "global" + KeyMarkdown = "markdown" + KeyHTML = "html" +) + +// Simple PHP-like server implementation. +var Tool = mtool.T("tht").Func(func(flags *mtool.Flags) { + var ( + addr, ext, src, mod, index string + ) + + flags.StringVar(&addr, "addr", ":3000", "address to serve at") + flags.StringVar(&mod, "mod", "./mod", "path to store Tengo modules") + flags.StringVar(&src, "src", "./src", "directory with source files") + flags.StringVar(&ext, "ext", ".tpp", "extension for TPP files") + flags.StringVar(&index, "index", "index.htm", "index file name") + + flags.Parse() + + + srv := &http.Server{ + Addr: addr, + Handler: NewHandler( + DefaultPP(mod), + src, ext, index, + map[string] tengo.Object{}, + ).SetMD(mdx.MakeDefaultMarkdown()), + } + + log.Printf("Listening on %q\n", addr) + err := srv.ListenAndServe() + if err != nil { + log.Printf("Error: srv.ListenAndServe(...): %s\n", err) + } +}) diff --git a/src/check.htm.tpp b/src/check.htm.tpp new file mode 100644 index 0000000..6674cd9 --- /dev/null +++ b/src/check.htm.tpp @@ -0,0 +1,5 @@ +
shit
+{{ + pp.print("cock") +}} +
later
diff --git a/src/check/index.htm.tpp b/src/check/index.htm.tpp new file mode 100644 index 0000000..ee4255e --- /dev/null +++ b/src/check/index.htm.tpp @@ -0,0 +1,12 @@ +{{# + html := import("html").new_render() +}} +{{ +h := html.main().body( + html.p().body( + "some paragraph shit" + ) +) + +pp.print(h) +}} diff --git a/src/contact/edit.htm.tpp b/src/contact/edit.htm.tpp new file mode 100644 index 0000000..b762aac --- /dev/null +++ b/src/contact/edit.htm.tpp @@ -0,0 +1,20 @@ +{{# + context.is_compo = true +}} + +
+
+ + +
+
+ + +
+
+ + +
+ + +
diff --git a/src/contact/view.htm.tpp b/src/contact/view.htm.tpp new file mode 100644 index 0000000..04fff93 --- /dev/null +++ b/src/contact/view.htm.tpp @@ -0,0 +1,13 @@ +{{# + //context.is_compo = true +}} + +
+
: Joe
+
: Blow
+
: joe@blow.com
+ +
+ diff --git a/src/incorrect.htm.tpp b/src/incorrect.htm.tpp new file mode 100644 index 0000000..0f67522 --- /dev/null +++ b/src/incorrect.htm.tpp @@ -0,0 +1,11 @@ +html := import("html").new_render() + +pp.print(html.html().body( + html.head().body( + html.title().body( + "some title" + ) + ), + html.body().body( + ) +)) diff --git a/src/index.htm.tpp b/src/index.htm.tpp new file mode 100644 index 0000000..2e4c875 --- /dev/null +++ b/src/index.htm.tpp @@ -0,0 +1,78 @@ +{{# + context.title = "The example.site main page" + + fmt := import("fmt") + html := import("html").new_render() +}} + +

This is the example page for the THT

+{{ + /*if !context.global.int { + context.global.int = 0 + } else { + context.global.int++ + }*/ + + if !context.global.val { + context.global.val = 1 + } else { + context.global.val += 1 + } + fmt.println("global: ", context.global) + req := context.http.request + q := req.url.query + // List checking. + list := [] + rng := int(q.range[0]) + fmt.println("range:", q.range) + if rng { + pp.print(`") + } + if q.name { + pp.print("
", q.name[0], "
") + } +}} +
+ {{ pp.print(context.global.val) }} +
+
+ Hello, Cock! +
+
+ {{ + pp.print(req.url.path) + }} +
+ + +{{ + vals := ["die", "with", "them", "as", "you", 1, "could", "do"] + pp.print(html.div({ + id: "the-uniq-shit" + }).body( + html.ul( + ).body( + func(){ + ret := [] + for i:=0 ; i + +
diff --git a/src/main.htm.tpp b/src/main.htm.tpp new file mode 100644 index 0000000..53a9907 --- /dev/null +++ b/src/main.htm.tpp @@ -0,0 +1,37 @@ +{{# + context.title = "The example.site main page" + + fmt := import("fmt") + html := import("html").new_render() +}} + +{{ + pp.print( + html.h1().body("This is the example page for THT") + ) + + if !context.global.val { + context.global.val = 1 + } else { + context.global.val += 1 + } + +}} + +{{ + req := context.http.request + q := req.url.query + // List checking. + rng := int(q.range[0]) + fmt.println("range:", q.range) + if rng { + pp.print(`
    `) + for i:=0 ; i < rng ; i++ { + pp.printf("
  • %d
  • ", i) + } + pp.print("
") + } + if q.name { + pp.print("
", q.name[0], "
") + } +}} diff --git a/src/page.htm.tpp b/src/page.htm.tpp new file mode 100644 index 0000000..f9fb15f --- /dev/null +++ b/src/page.htm.tpp @@ -0,0 +1,35 @@ +{{# + context.title = "Let's celebrate and suck, SOME DICK" +}} + +{{markdown(` + +# Hello, World! + +This is the markdown example shit. + + func main() { + print("Fuck you") + } + +## Yet another level of header + +And even more text + +> Cheap is talk, show me the code. + +(c) Linus Torvalds + +`)}} + +
+ Even more HTML +
+ +{{markdown(` +### Third level header + +Some shit + +#### `, 135, ` +`)}} diff --git a/src/req.json.tpp b/src/req.json.tpp new file mode 100644 index 0000000..16bb6de --- /dev/null +++ b/src/req.json.tpp @@ -0,0 +1,8 @@ +{ + "error": null, + "data": { + "name": "Andrey", + "surname": "Parhomenko", + "age": 22 + } +} diff --git a/src/request.json b/src/request.json new file mode 100644 index 0000000..3b0cd87 --- /dev/null +++ b/src/request.json @@ -0,0 +1,3 @@ +{ + "name":"Andrew" +} diff --git a/src/script.js b/src/script.js new file mode 100644 index 0000000..f0d1a35 --- /dev/null +++ b/src/script.js @@ -0,0 +1,3 @@ + +console.log("Hello, World!") +