This repository has been archived by the owner on Aug 22, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
formatted.go
173 lines (159 loc) · 4.6 KB
/
formatted.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package parser
import (
"bytes"
"strings"
"unicode"
"github.com/bouncepaw/mycomarkup/v3/blocks"
"github.com/bouncepaw/mycomarkup/v3/globals"
"github.com/bouncepaw/mycomarkup/v3/links"
"github.com/bouncepaw/mycomarkup/v3/mycocontext"
"github.com/bouncepaw/mycomarkup/v3/util"
)
func nextParagraph(ctx mycocontext.Context) (p blocks.Paragraph, done bool) {
line, done := mycocontext.NextLine(ctx)
p = blocks.Paragraph{MakeFormatted(line, ctx.HyphaName())}
if nextLineIsSomething(ctx) {
return
}
for {
line, done = mycocontext.NextLine(ctx)
if done && line == "" {
break
}
spans := spansFromLine(p.HyphaName, line)
p.AddLine(spans)
if nextLineIsSomething(ctx) {
break
}
}
return
}
// nextInlineLink returns an HTML representation of the next link in the input. Set isBracketedLink if the input starts with [[.
func nextInlineLink(input *bytes.Buffer, hyphaName string, isBracketedLink bool) blocks.InlineLink {
if isBracketedLink {
input.Next(2) // drop those [[
}
var (
escaping = false
addrBuf = bytes.Buffer{}
displayBuf = bytes.Buffer{}
currBuf = &addrBuf
)
for input.Len() != 0 {
b, _ := input.ReadByte()
if escaping {
currBuf.WriteByte(b)
escaping = false
} else if isBracketedLink && b == '|' && currBuf == &addrBuf {
currBuf = &displayBuf
} else if isBracketedLink && b == ']' && bytes.HasPrefix(input.Bytes(), []byte{']'}) {
input.Next(1)
break
} else if !isBracketedLink && (unicode.IsSpace(rune(b)) || strings.ContainsRune("<>{}|\\^[]`,()", rune(b))) {
_ = input.UnreadByte()
break
} else {
currBuf.WriteByte(b)
}
}
link := links.From(addrBuf.String(), displayBuf.String(), hyphaName)
if globals.HyphaExists(util.CanonicalName(link.TargetHypha())) {
link = link.CopyMarkedAsExisting()
}
return blocks.InlineLink{Link: link}
}
// MakeFormatted parses the formatted text in the input and returns it. Does it?
func MakeFormatted(firstLine, hyphaName string) blocks.Formatted {
return blocks.Formatted{
HyphaName: hyphaName,
Lines: [][]blocks.Span{spansFromLine(hyphaName, firstLine)},
}
}
func spansFromLine(hyphaName, line string) []blocks.Span {
var (
input = bytes.NewBufferString(line)
spans = make([]blocks.Span, 0)
tagState = blocks.CleanStyleState()
startsWith = func(t string) bool {
return bytes.HasPrefix(input.Bytes(), []byte(t))
}
noTagsActive = func() bool {
// This function used to be one boolean expression. I changed it to a loop so it is harder to forger 💀 any span kinds.
for _, entry := range blocks.SpanTable {
if tagState[entry.Kind()] { // If span is open
return false
}
}
// All other spans are closed, let's check for link finally.
return !tagState[blocks.SpanLink]
}
)
runeWalker:
for input.Len() != 0 {
for _, entry := range blocks.SpanTable {
if startsWith(entry.Token) {
spans = append(spans, entry)
input.Next(len(entry.Token))
continue runeWalker
}
}
switch {
case startsWith("[["):
spans = append(spans, nextInlineLink(input, hyphaName, true))
case (startsWith("https://") || startsWith("http://") || startsWith("gemini://") || startsWith("gopher://") || startsWith("ftp://")) && noTagsActive():
spans = append(spans, nextInlineLink(input, hyphaName, false))
default:
spans = append(spans, nextInlineText(input))
}
}
return spans
}
var protocols [][]byte
func init() {
protocols = [][]byte{
[]byte("https://"),
[]byte("http://"),
[]byte("gemini://"),
[]byte("gopher://"),
[]byte("ftp://")}
// There was a demand for a way to customize the protocols ^. Do we need that?
}
func bytesStartWithProtocol(b []byte) bool {
for _, protocol := range protocols {
if bytes.HasPrefix(b, protocol) {
return true
}
}
return false
}
// nextInlineText returns the next blocks.InlineText there is in input.
func nextInlineText(input *bytes.Buffer) blocks.InlineText {
var (
ret = bytes.Buffer{}
escaping = false
)
// Always read the first byte in advance to avoid endless loops that kill computers (sad experience)
if input.Len() != 0 {
b, _ := input.ReadByte()
_ = ret.WriteByte(b)
}
for input.Len() != 0 {
// We check for length, this should never fail:
ch, _ := input.ReadByte()
if escaping {
ret.WriteByte(ch)
escaping = false
} else if ch == '\\' {
escaping = true
} else if strings.IndexByte("/*`^,+[~_", ch) >= 0 { // TODO: generate that string there dynamically
input.UnreadByte() // sorry, wrong door >_<
break
} else if bytesStartWithProtocol(input.Bytes()) {
ret.WriteByte(ch)
break
} else {
ret.WriteByte(ch)
}
}
return blocks.InlineText{Contents: ret.String()}
}