-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
/
Copy pathinline.jl
187 lines (160 loc) · 5.89 KB
/
inline.jl
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# This file is a part of Julia. License is MIT: https://julialang.org/license
# ––––––––
# Emphasis
# ––––––––
mutable struct Italic
text
end
@trigger '*' ->
function asterisk_italic(stream::IO, md::MD)
result = parse_inline_wrapper(stream, "*")
return result === nothing ? nothing : Italic(parseinline(result, md))
end
@trigger '_' ->
function underscore_italic(stream::IO, md::MD)
result = parse_inline_wrapper(stream, "_")
return result === nothing ? nothing : Italic(parseinline(result, md))
end
mutable struct Bold
text
end
@trigger '*' ->
function asterisk_bold(stream::IO, md::MD)
result = parse_inline_wrapper(stream, "**")
return result === nothing ? nothing : Bold(parseinline(result, md))
end
@trigger '_' ->
function underscore_bold(stream::IO, md::MD)
result = parse_inline_wrapper(stream, "__")
return result === nothing ? nothing : Bold(parseinline(result, md))
end
# ––––
# Code
# ––––
@trigger '`' ->
function inline_code(stream::IO, md::MD)
withstream(stream) do
ticks = startswith(stream, r"^(`+)")
result = readuntil(stream, ticks)
if result === nothing
nothing
else
result = strip(result)
# An odd number of backticks wrapping the text will produce a `Code` node, while
# an even number will result in a `LaTeX` node. This allows for arbitrary
# backtick combinations to be embedded inside the resulting node, i.e.
#
# `a`, ``a``, `` `a` ``, ``` ``a`` ```, ``` `a` ```, etc.
# ^ ^ ^ ^ ^
# C L L C C with C=Code and L=LaTeX.
#
isodd(length(ticks)) ? Code(result) : LaTeX(result)
end
end
end
# ––––––––––––––
# Images & Links
# ––––––––––––––
mutable struct Image
url::String
alt::String
end
@trigger '!' ->
function image(stream::IO, md::MD)
withstream(stream) do
startswith(stream, "![") || return
alt = readuntil(stream, ']', match = '[')
alt ≡ nothing && return
skipwhitespace(stream)
startswith(stream, '(') || return
url = readuntil(stream, ')', match = '(')
url ≡ nothing && return
return Image(url, alt)
end
end
mutable struct Link
text
url::String
end
@trigger '[' ->
function link(stream::IO, md::MD)
withstream(stream) do
startswith(stream, '[') || return
text = readuntil(stream, ']', match = '[')
text ≡ nothing && return
skipwhitespace(stream)
startswith(stream, '(') || return
url = readuntil(stream, ')', match = '(')
url ≡ nothing && return
return Link(parseinline(text, md), url)
end
end
@trigger '[' ->
function footnote_link(stream::IO, md::MD)
withstream(stream) do
regex = r"^\[\^(\w+)\]"
str = startswith(stream, regex)
if isempty(str)
return
else
ref = match(regex, str).captures[1]
return Footnote(ref, nothing)
end
end
end
@trigger '<' ->
function autolink(stream::IO, md::MD)
withstream(stream) do
startswith(stream, '<') || return
url = readuntil(stream, '>')
url ≡ nothing && return
_is_link(url) && return Link(url, url)
_is_mailto(url) && return Link(url, url)
return
end
end
# This list is taken from the commonmark spec
# http://spec.commonmark.org/0.19/#absolute-uri
const _allowable_schemes = Set(split("coap doi javascript aaa aaas about acap cap cid crid data dav dict dns file ftp geo go gopher h323 http https iax icap im imap info ipp iris iris.beep iris.xpc iris.xpcs iris.lwz ldap mailto mid msrp msrps mtqp mupdate news nfs ni nih nntp opaquelocktoken pop pres rtsp service session shttp sieve sip sips sms snmp,soap.beep soap.beeps tag tel telnet tftp thismessage tn3270 tip tv urn vemmi ws wss xcon xcon-userid xmlrpc.beep xmlrpc.beeps xmpp z39.50r z39.50s
adiumxtra afp afs aim apt,attachment aw beshare bitcoin bolo callto chrome,chrome-extension com-eventbrite-attendee content cvs,dlna-playsingle dlna-playcontainer dtn dvb ed2k facetime feed finger fish gg git gizmoproject gtalk hcp icon ipn irc irc6 ircs itms jar jms keyparc lastfm ldaps magnet maps market,message mms ms-help msnim mumble mvn notes oid palm paparazzi platform proxy psyc query res resource rmi rsync rtmp secondlife sftp sgn skype smb soldat spotify ssh steam svn teamspeak
things udp unreal ut2004 ventrilo view-source webcal wtai wyciwyg xfire xri ymsgr"))
function _is_link(s::AbstractString)
'<' in s && return false
m = match(r"^(.*)://(\S+?)(:\S*)?$", s)
m ≡ nothing && return false
scheme = lowercase(m.captures[1])
return scheme in _allowable_schemes
end
# non-normative regex from the HTML5 spec
const _email_regex = r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
function _is_mailto(s::AbstractString)
length(s) < 6 && return false
# slicing strings is a bit risky, but this equality check is safe
lowercase(s[1:6]) == "mailto:" || return false
return occursin(_email_regex, s[6:end])
end
# –––––––––––
# Punctuation
# –––––––––––
mutable struct LineBreak end
@trigger '\\' ->
function linebreak(stream::IO, md::MD)
if startswith(stream, "\\\n")
return LineBreak()
end
end
@trigger '-' ->
function en_dash(stream::IO, md::MD)
if startswith(stream, "--")
return "–"
end
end
const escape_chars = "\\`*_#+-.!{}[]()\$"
@trigger '\\' ->
function escapes(stream::IO, md::MD)
withstream(stream) do
if startswith(stream, "\\") && !eof(stream) && (c = read(stream, Char)) in escape_chars
return string(c)
end
end
end