feat: implemented the structured way to render HTML in .tpp files.
This commit is contained in:
parent
485235996f
commit
5a82d721bd
7 changed files with 225 additions and 2 deletions
97
htmlx/element.go
Normal file
97
htmlx/element.go
Normal 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
86
htmlx/html.go
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package httpx
|
||||||
import (
|
import (
|
||||||
//"github.com/d5/tengo/v2"
|
//"github.com/d5/tengo/v2"
|
||||||
"surdeus.su/util/tpp/mdx"
|
"surdeus.su/util/tpp/mdx"
|
||||||
|
"surdeus.su/util/tpp/htmlx"
|
||||||
"surdeus.su/util/tpp"
|
"surdeus.su/util/tpp"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -36,6 +37,7 @@ type Handler struct {
|
||||||
// between requests.
|
// between requests.
|
||||||
global any
|
global any
|
||||||
md *mdx.Markdown
|
md *mdx.Markdown
|
||||||
|
html *htmlx.HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the new Handler with
|
// Returns the new Handler with
|
||||||
|
@ -68,6 +70,7 @@ func DefaultPP(mod string) *tpp.Preprocessor {
|
||||||
s.Add("__http_request__", ctx.Value(KeyRequest))
|
s.Add("__http_request__", ctx.Value(KeyRequest))
|
||||||
s.Add("__global__", ctx.Value(KeyGlobal))
|
s.Add("__global__", ctx.Value(KeyGlobal))
|
||||||
s.Add("__markdown__", ctx.Value(KeyMarkdown))
|
s.Add("__markdown__", ctx.Value(KeyMarkdown))
|
||||||
|
s.Add("__html__", ctx.Value(KeyHTML))
|
||||||
}).SetPreCode(func(ctx context.Context) []byte {
|
}).SetPreCode(func(ctx context.Context) []byte {
|
||||||
return []byte(`
|
return []byte(`
|
||||||
markdown := func(...args) {
|
markdown := func(...args) {
|
||||||
|
@ -76,9 +79,11 @@ func DefaultPP(mod string) *tpp.Preprocessor {
|
||||||
http := immutable({
|
http := immutable({
|
||||||
request : __http_request__
|
request : __http_request__
|
||||||
})
|
})
|
||||||
|
html := __html__
|
||||||
context.http = http
|
context.http = http
|
||||||
context.pp = pp
|
context.pp = pp
|
||||||
context.global = __global__
|
context.global = __global__
|
||||||
|
context.html = html
|
||||||
import("./pre")(context)
|
import("./pre")(context)
|
||||||
`)
|
`)
|
||||||
}).SetPostCode(func(ctx context.Context) []byte {
|
}).SetPostCode(func(ctx context.Context) []byte {
|
||||||
|
@ -94,6 +99,10 @@ func (h *Handler) SetMD(md *mdx.Markdown) *Handler {
|
||||||
h.md = md
|
h.md = md
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
func (h *Handler) SetHTML(html *htmlx.HTML) *Handler {
|
||||||
|
h.html = html
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) ServeHTTP(
|
func (h *Handler) ServeHTTP(
|
||||||
w http.ResponseWriter,
|
w http.ResponseWriter,
|
||||||
|
@ -149,6 +158,12 @@ func (h *Handler) ServeHTTP(
|
||||||
h.md,
|
h.md,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ctx = context.WithValue(
|
||||||
|
ctx,
|
||||||
|
KeyHTML,
|
||||||
|
h.html,
|
||||||
|
)
|
||||||
|
|
||||||
// Setting before the code to let it change own
|
// Setting before the code to let it change own
|
||||||
// content type.
|
// content type.
|
||||||
contentType := mime.TypeByExtension(urlExt)
|
contentType := mime.TypeByExtension(urlExt)
|
||||||
|
|
|
@ -18,6 +18,7 @@ const (
|
||||||
KeyRequest CKey = "http-request"
|
KeyRequest CKey = "http-request"
|
||||||
KeyGlobal = "global"
|
KeyGlobal = "global"
|
||||||
KeyMarkdown = "markdown"
|
KeyMarkdown = "markdown"
|
||||||
|
KeyHTML = "html"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Simple PHP-like server implementation.
|
// Simple PHP-like server implementation.
|
||||||
|
|
|
@ -46,12 +46,15 @@ func (md *Markdown) Call(
|
||||||
) (tengo.Object, error) {
|
) (tengo.Object, error) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
bts, ok := tengo.ToByteSlice(arg)
|
str, ok := tengo.ToString(arg)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, tengo.ErrInvalidArgumentType{
|
return nil, tengo.ErrInvalidArgumentType{
|
||||||
|
Name: "v",
|
||||||
|
Expected: "stringer",
|
||||||
|
Found: arg.TypeName(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.Write(bts)
|
b.Write([]byte(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
rendered, err := md.Render(b.Bytes())
|
rendered, err := md.Render(b.Bytes())
|
||||||
|
|
|
@ -52,6 +52,26 @@
|
||||||
}
|
}
|
||||||
}}</ul>
|
}}</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>
|
<script src="/script.js" ></script>
|
||||||
|
|
||||||
<div></div>
|
<div></div>
|
||||||
|
|
|
@ -31,4 +31,5 @@ And even more text
|
||||||
|
|
||||||
Some shit
|
Some shit
|
||||||
|
|
||||||
|
#### `, 135, `
|
||||||
`)}}
|
`)}}
|
||||||
|
|
Loading…
Reference in a new issue