-
Notifications
You must be signed in to change notification settings - Fork 0
/
output.go
122 lines (106 loc) · 2.8 KB
/
output.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package mjingo
import (
"fmt"
"io"
"strings"
)
type output struct {
w io.Writer
captureStack []io.Writer
}
var _ = (io.Writer)((*output)(nil))
func newOutput(w io.Writer) *output {
return &output{w: w}
}
func newOutputNull() *output {
// The null writer also has a single entry on the discarding capture
// stack. In fact, `w` is more or less useless here as we always
// shadow it. This is done so that `is_discarding` returns true.
return &output{w: io.Discard, captureStack: []io.Writer{io.Discard}}
}
func (o *output) target() io.Writer {
if len(o.captureStack) > 0 {
return o.captureStack[len(o.captureStack)-1]
}
return o.w
}
func (o *output) isDiscarding() bool {
return len(o.captureStack) > 0 && o.captureStack[len(o.captureStack)-1] == io.Discard
}
func (o *output) Write(p []byte) (n int, err error) {
return o.target().Write(p)
}
// Begins capturing into a string or discard.
func (o *output) beginCapture(mode captureMode) {
var w io.Writer
switch mode {
case captureModeCapture:
w = new(strings.Builder)
case captureModeDiscard:
w = io.Discard
default:
panic("unreachable")
}
o.captureStack = append(o.captureStack, w)
}
// Ends capturing and returns the captured string as
func (o *output) endCapture(escape AutoEscape) Value {
if len(o.captureStack) == 0 {
return Undefined
}
w := o.captureStack[len(o.captureStack)-1]
o.captureStack = o.captureStack[:len(o.captureStack)-1]
if builder, ok := w.(*strings.Builder); ok {
str := builder.String()
if _, ok := escape.(autoEscapeNone); !ok {
return ValueFromSafeString(str)
} else {
return valueFromString(str)
}
}
return Undefined
}
func writeString(o *output, s string) error {
_, err := io.WriteString(o, s)
return err
}
func writeWithHTMLEscaping(o *output, val Value) error {
switch val.Kind() {
case ValueKindUndefined, ValueKindNone, ValueKindBool, ValueKindNumber:
return writeString(o, val.String())
default:
var str string
if !valueAsOptionString(val).UnwrapTo(&str) {
str = val.String()
}
return writeString(o, htmlEscapeString(str))
}
}
func writeEscaped(o *output, autoEscape AutoEscape, val Value) error {
// common case of safe strings or strings without auto escaping
if val.isSafe() || autoEscape.isNone() {
return writeString(o, val.String())
}
switch esc := autoEscape.(type) {
case autoEscapeNone:
return writeString(o, val.String())
case autoEscapeHTML:
return writeWithHTMLEscaping(o, val)
case autoEscapeJSON:
panic("not implemented")
case autoEscapeCustom:
panic(fmt.Sprintf("not implemented for custom auto escape name=%s", esc.name))
}
return nil
}
var htmlEscaper = strings.NewReplacer(
`&`, "&",
`'`, "'",
`/`, "/",
`<`, "<",
`>`, ">",
`"`, """,
)
func htmlEscapeString(s string) string {
return htmlEscaper.Replace(s)
}