feat: implemented the structured way to render HTML in .tpp files.

This commit is contained in:
Andrey Parhomenko 2024-05-24 14:43:55 +05:00
parent 485235996f
commit 5a82d721bd
7 changed files with 225 additions and 2 deletions

97
htmlx/element.go Normal file
View file

@ -0,0 +1,97 @@
package htmlx
import (
"github.com/d5/tengo/v2"
"strings"
"html"
"fmt"
)
const RawTag = "raw"
// The type implements basic
// way to structrize HTML elements.
type Element struct {
tengo.ObjectImpl
Tag string
Attr map[string] string
Children []*Element
// The value makes sense only if
// the tag is the "raw"
Content string
}
func (el *Element) TypeName() string {
return "*HTMLElement"
}
// The method renders the element to it's
// HTML representation.
func (el *Element) String() string {
if el.Tag == RawTag {
return html.EscapeString(el.Content)
}
var b strings.Builder
fmt.Fprintf(&b, "<%s", el.Tag)
for k, v := range el.Attr {
fmt.Fprintf(&b, " %s=%q", k, v)
}
fmt.Fprint(&b, ">")
for _, child := range el.Children {
if child == nil {
continue
}
fmt.Fprint(&b, child.String())
}
fmt.Fprintf(&b, "</%s>", el.Tag)
return b.String()
}
func (el *Element) Body(els ...*Element) *Element {
el.Children = els
return el
}
func (el *Element) IndexGet(
index tengo.Object,
) (tengo.Object, error) {
arg, ok := tengo.ToString(index)
if !ok {
return nil, tengo.ErrInvalidIndexValueType
}
switch arg {
case "body" :
return &tengo.UserFunction{
Name: "Element.Body",
Value: func(
args ...tengo.Object,
) (tengo.Object, error) {
s := []*Element{}
for _, arg := range args {
el, ok := arg.(*Element)
if !ok {
str, ok := tengo.ToString(arg)
if ok {
s = append(s, &Element{
Tag: RawTag,
Content: str,
})
}
continue
}
s = append(s, el)
}
return el.Body(s...), nil
},
}, nil
}
return nil, nil
}

86
htmlx/html.go Normal file
View file

@ -0,0 +1,86 @@
package htmlx
import "github.com/d5/tengo/v2"
type HTML struct{
tengo.ObjectImpl
}
/*
html.div({
id: "some-el-id",
value: "shit value"
}).body(
html.raw("cock "),
html.strong("something")
)
*/
func (html *HTML) IndexGet(
index tengo.Object,
) (tengo.Object, error) {
str, ok := tengo.ToString(index)
if !ok {
return nil, tengo.ErrInvalidIndexValueType
}
fn := func(args ...tengo.Object) (tengo.Object, error) {
if len(args) > 1 {
return nil, tengo.ErrWrongNumArguments
}
var arg tengo.Object
if len(args) == 1 {
arg = args[0]
}
if arg == nil {
return &Element{
Tag: str,
}, nil
}
if can := arg.CanIterate() ; !can {
return nil, tengo.ErrInvalidArgumentType{
Name: "first",
Expected: "iterable",
Found: arg.TypeName(),
}
}
attr := map[string] string{}
iter := arg.Iterate()
for iter.Next() {
key, val := iter.Key(), iter.Value()
skey, ok := tengo.ToString(key)
if !ok {
return nil, tengo.ErrInvalidArgumentType{
Name: "attribute(key)",
Expected: "stringer",
Found: key.TypeName(),
}
}
sval, ok := tengo.ToString(val)
if !ok {
return nil, tengo.ErrInvalidArgumentType{
Name: "attribute(value)",
Expected: "stringer",
Found: val.TypeName(),
}
}
attr[skey] = sval
}
return &Element{
Tag: str,
Attr: attr,
}, nil
}
return &tengo.UserFunction{
Name: str,
Value: fn,
}, nil
}

View file

@ -3,6 +3,7 @@ package httpx
import (
//"github.com/d5/tengo/v2"
"surdeus.su/util/tpp/mdx"
"surdeus.su/util/tpp/htmlx"
"surdeus.su/util/tpp"
"path/filepath"
"net/http"
@ -36,6 +37,7 @@ type Handler struct {
// between requests.
global any
md *mdx.Markdown
html *htmlx.HTML
}
// Returns the new Handler with
@ -68,6 +70,7 @@ func DefaultPP(mod string) *tpp.Preprocessor {
s.Add("__http_request__", ctx.Value(KeyRequest))
s.Add("__global__", ctx.Value(KeyGlobal))
s.Add("__markdown__", ctx.Value(KeyMarkdown))
s.Add("__html__", ctx.Value(KeyHTML))
}).SetPreCode(func(ctx context.Context) []byte {
return []byte(`
markdown := func(...args) {
@ -76,9 +79,11 @@ func DefaultPP(mod string) *tpp.Preprocessor {
http := immutable({
request : __http_request__
})
html := __html__
context.http = http
context.pp = pp
context.global = __global__
context.html = html
import("./pre")(context)
`)
}).SetPostCode(func(ctx context.Context) []byte {
@ -94,6 +99,10 @@ func (h *Handler) SetMD(md *mdx.Markdown) *Handler {
h.md = md
return h
}
func (h *Handler) SetHTML(html *htmlx.HTML) *Handler {
h.html = html
return h
}
func (h *Handler) ServeHTTP(
w http.ResponseWriter,
@ -149,6 +158,12 @@ func (h *Handler) ServeHTTP(
h.md,
)
ctx = context.WithValue(
ctx,
KeyHTML,
h.html,
)
// Setting before the code to let it change own
// content type.
contentType := mime.TypeByExtension(urlExt)

View file

@ -18,6 +18,7 @@ const (
KeyRequest CKey = "http-request"
KeyGlobal = "global"
KeyMarkdown = "markdown"
KeyHTML = "html"
)
// Simple PHP-like server implementation.

View file

@ -46,12 +46,15 @@ func (md *Markdown) Call(
) (tengo.Object, error) {
var b bytes.Buffer
for _, arg := range args {
bts, ok := tengo.ToByteSlice(arg)
str, ok := tengo.ToString(arg)
if !ok {
return nil, tengo.ErrInvalidArgumentType{
Name: "v",
Expected: "stringer",
Found: arg.TypeName(),
}
}
b.Write(bts)
b.Write([]byte(str))
}
rendered, err := md.Render(b.Bytes())

View file

@ -52,6 +52,26 @@
}
}}</ul>
{{
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<len(vals) ; i++{
ret += [html.li({
class: "someclass"
}).body(i, ": ", vals[i])]
}
return ret
}()...
)
))
}}
<script src="/script.js" ></script>
<div></div>

View file

@ -31,4 +31,5 @@ And even more text
Some shit
#### `, 135, `
`)}}