Skip to content

Commit 98024f8

Browse files
committed
feat(hugo-engine): implement many transforms for hugo live editing
Now supported: * `markdownify` * `plainify` * `emojify` * `htmlEscape` * `htmlUnescape` `markdownify` uses a different implementation than hugo, so the output is not guaranteed to be identical. `highlight` is not supported, but will no longer error. it will now pass through the input unchanged.
1 parent 920940b commit 98024f8

File tree

5 files changed

+322
-1
lines changed

5 files changed

+322
-1
lines changed

javascript-modules/engines/hugo-engine/hugo-renderer/helpers/bookshop_modified_content.go

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package helpers
1919

2020
import (
21+
"html/template"
2122
"strings"
2223
"unicode"
2324

@@ -62,3 +63,8 @@ func StripHTML(s string) string {
6263
}
6364
return b.String()
6465
}
66+
67+
// BytesToHTML converts bytes to type template.HTML.
68+
func BytesToHTML(b []byte) template.HTML {
69+
return template.HTML(string(b))
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2016 The Hugo Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package helpers
15+
16+
import (
17+
"bytes"
18+
"sync"
19+
20+
"github.com/kyokomi/emoji/v2"
21+
)
22+
23+
var (
24+
emojiInit sync.Once
25+
26+
emojis = make(map[string][]byte)
27+
28+
emojiDelim = []byte(":")
29+
emojiWordDelim = []byte(" ")
30+
emojiMaxSize int
31+
)
32+
33+
// Emoji returns the emojy given a key, e.g. ":smile:", nil if not found.
34+
func Emoji(key string) []byte {
35+
emojiInit.Do(initEmoji)
36+
return emojis[key]
37+
}
38+
39+
// Emojify "emojifies" the input source.
40+
// Note that the input byte slice will be modified if needed.
41+
// See http://www.emoji-cheat-sheet.com/
42+
func Emojify(source []byte) []byte {
43+
emojiInit.Do(initEmoji)
44+
45+
start := 0
46+
k := bytes.Index(source[start:], emojiDelim)
47+
48+
for k != -1 {
49+
50+
j := start + k
51+
52+
upper := j + emojiMaxSize
53+
54+
if upper > len(source) {
55+
upper = len(source)
56+
}
57+
58+
endEmoji := bytes.Index(source[j+1:upper], emojiDelim)
59+
nextWordDelim := bytes.Index(source[j:upper], emojiWordDelim)
60+
61+
if endEmoji < 0 {
62+
start++
63+
} else if endEmoji == 0 || (nextWordDelim != -1 && nextWordDelim < endEmoji) {
64+
start += endEmoji + 1
65+
} else {
66+
endKey := endEmoji + j + 2
67+
emojiKey := source[j:endKey]
68+
69+
if emoji, ok := emojis[string(emojiKey)]; ok {
70+
source = append(source[:j], append(emoji, source[endKey:]...)...)
71+
}
72+
73+
start += endEmoji
74+
}
75+
76+
if start >= len(source) {
77+
break
78+
}
79+
80+
k = bytes.Index(source[start:], emojiDelim)
81+
}
82+
83+
return source
84+
}
85+
86+
func initEmoji() {
87+
emojiMap := emoji.CodeMap()
88+
89+
for k, v := range emojiMap {
90+
emojis[k] = []byte(v)
91+
92+
if len(k) > emojiMaxSize {
93+
emojiMaxSize = len(k)
94+
}
95+
}
96+
}

javascript-modules/engines/hugo-engine/hugo-renderer/tpl/bookshop_engine/bookshop_func_importer.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ import (
2929
_ "hugo-renderer/tpl/strings" // ———————————————— 🥴 TODO: Only the "Go" title formatting is supported
3030
// _ "hugo-renderer/tpl/templates" // ——————————— ❗ TODO: Maybe a rabbit hole
3131
// _ "hugo-renderer/tpl/time" // ———————————————— ❗ TODO: This relies on lang stuff that is thus far excluded
32-
// _ "hugo-renderer/tpl/transform" // ——————————— TODO: Definitely a rabbit hole
32+
_ "hugo-renderer/tpl/transform" // —————————————— 🥴 TODO: Highlight, unmarshal, commonmark compat
3333
// _ "hugo-renderer/tpl/urls" // ———————————————— ❗ TODO: Relies on pathspec that relies on FS impls
3434
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2017 The Hugo Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package transform
15+
16+
import (
17+
"hugo-renderer/deps"
18+
"hugo-renderer/tpl/internal"
19+
)
20+
21+
const name = "transform"
22+
23+
func init() {
24+
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
25+
ctx := New(d)
26+
27+
ns := &internal.TemplateFuncsNamespace{
28+
Name: name,
29+
Context: func(args ...interface{}) (interface{}, error) { return ctx, nil },
30+
}
31+
32+
ns.AddMethodMapping(ctx.Emojify,
33+
[]string{"emojify"},
34+
[][2]string{
35+
{`{{ "I :heart: Hugo" | emojify }}`, `I ❤️ Hugo`},
36+
},
37+
)
38+
39+
ns.AddMethodMapping(ctx.Highlight,
40+
[]string{"highlight"},
41+
[][2]string{},
42+
)
43+
44+
ns.AddMethodMapping(ctx.HTMLEscape,
45+
[]string{"htmlEscape"},
46+
[][2]string{
47+
{
48+
`{{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>" | safeHTML}}`,
49+
`Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;`,
50+
},
51+
{
52+
`{{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>"}}`,
53+
`Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;`,
54+
},
55+
{
56+
`{{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>" | htmlUnescape | safeHTML }}`,
57+
`Cathal Garvey & The Sunshine Band <[email protected]>`,
58+
},
59+
},
60+
)
61+
62+
ns.AddMethodMapping(ctx.HTMLUnescape,
63+
[]string{"htmlUnescape"},
64+
[][2]string{
65+
{
66+
`{{ htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | safeHTML}}`,
67+
`Cathal Garvey & The Sunshine Band <[email protected]>`,
68+
},
69+
{
70+
`{{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;" | htmlUnescape | htmlUnescape | safeHTML}}`,
71+
`Cathal Garvey & The Sunshine Band <[email protected]>`,
72+
},
73+
{
74+
`{{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;" | htmlUnescape | htmlUnescape }}`,
75+
`Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;`,
76+
},
77+
{
78+
`{{ htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | htmlEscape | safeHTML }}`,
79+
`Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;`,
80+
},
81+
},
82+
)
83+
84+
ns.AddMethodMapping(ctx.Markdownify,
85+
[]string{"markdownify"},
86+
[][2]string{
87+
{`{{ .Title | markdownify}}`, `<strong>BatMan</strong>`},
88+
},
89+
)
90+
91+
ns.AddMethodMapping(ctx.Plainify,
92+
[]string{"plainify"},
93+
[][2]string{
94+
{`{{ plainify "Hello <strong>world</strong>, gophers!" }}`, `Hello world, gophers!`},
95+
},
96+
)
97+
98+
// Bookshop: remarshal has been intentionally removed due to instability
99+
100+
// Bookshop: unmarshal has been removed (for now) due to tendrils
101+
102+
return ns
103+
}
104+
105+
internal.AddTemplateFuncsNamespace(f)
106+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2017 The Hugo Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
// Package transform provides template functions for transforming content.
15+
package transform
16+
17+
import (
18+
"html"
19+
"html/template"
20+
21+
"github.com/gomarkdown/markdown"
22+
23+
"hugo-renderer/deps"
24+
25+
"hugo-renderer/helpers"
26+
27+
"github.com/spf13/cast"
28+
)
29+
30+
// New returns a new instance of the transform-namespaced template functions.
31+
func New(deps *deps.Deps) *Namespace {
32+
// Bookshop: Build listeners & cache intentionally removed
33+
34+
return &Namespace{
35+
deps: deps,
36+
}
37+
}
38+
39+
// Namespace provides template functions for the "transform" namespace.
40+
type Namespace struct {
41+
// Bookshop: Build listeners & cache intentionally removed
42+
deps *deps.Deps
43+
}
44+
45+
// Emojify returns a copy of s with all emoji codes replaced with actual emojis.
46+
//
47+
// See http://www.emoji-cheat-sheet.com/
48+
func (ns *Namespace) Emojify(s interface{}) (template.HTML, error) {
49+
ss, err := cast.ToStringE(s)
50+
if err != nil {
51+
return "", err
52+
}
53+
54+
return template.HTML(helpers.Emojify([]byte(ss))), nil
55+
}
56+
57+
// Highlight returns a copy of s as an HTML string with syntax
58+
// highlighting applied.
59+
func (ns *Namespace) Highlight(s interface{}, lang string, opts ...interface{}) (template.HTML, error) {
60+
ss, err := cast.ToStringE(s)
61+
if err != nil {
62+
return "", err
63+
}
64+
65+
// Bookshop: Highlighting logic intentionally removed for bloat
66+
67+
return template.HTML(ss), nil
68+
}
69+
70+
// HTMLEscape returns a copy of s with reserved HTML characters escaped.
71+
func (ns *Namespace) HTMLEscape(s interface{}) (string, error) {
72+
ss, err := cast.ToStringE(s)
73+
if err != nil {
74+
return "", err
75+
}
76+
77+
return html.EscapeString(ss), nil
78+
}
79+
80+
// HTMLUnescape returns a copy of with HTML escape requences converted to plain
81+
// text.
82+
func (ns *Namespace) HTMLUnescape(s interface{}) (string, error) {
83+
ss, err := cast.ToStringE(s)
84+
if err != nil {
85+
return "", err
86+
}
87+
88+
return html.UnescapeString(ss), nil
89+
}
90+
91+
// Markdownify renders a given input from Markdown to HTML.
92+
func (ns *Namespace) Markdownify(s interface{}) (template.HTML, error) {
93+
ss, err := cast.ToStringE(s)
94+
if err != nil {
95+
return "", err
96+
}
97+
98+
// Bookshop: markdownify implementation has been intentionally modified for bloat
99+
// This includes using a different (smaller) markdown implementation.
100+
output := markdown.ToHTML([]byte(ss), nil, nil)
101+
102+
return helpers.BytesToHTML(output), nil
103+
}
104+
105+
// Plainify returns a copy of s with all HTML tags removed.
106+
func (ns *Namespace) Plainify(s interface{}) (string, error) {
107+
ss, err := cast.ToStringE(s)
108+
if err != nil {
109+
return "", err
110+
}
111+
112+
return helpers.StripHTML(ss), nil
113+
}

0 commit comments

Comments
 (0)