rouge-carve 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +2 -0
- data/lib/rouge/lexers/carve.rb +799 -218
- data/lib/rouge-carve/version.rb +7 -0
- data/lib/rouge-carve.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b5dd23d4f12415b4c19ed7333a5bd1555ae21e4f547b4f0a8f52b7829f8ad94
|
|
4
|
+
data.tar.gz: 1f9a6066c9c55b5457eda908909ff6b865d84fedaebbe18a37fdbe4f9e62a76d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bdc875e2a50141600798ff0efcd8741e751516dbb3de632f55039a62e881f4edbe4561942e8b39228e6603c1e6cef428d5320323b2617213d5f8ceed1053e550
|
|
7
|
+
data.tar.gz: 8d1983500c85ecef17ac25bac9aad4ebf76b477bf75ee721d1343ef09a82cf424c86c4bf9cb65b948361183bf97cce2bffe90630f20fa519368d5253fed1f13c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.1.3] - 2026-10-14
|
|
6
|
+
|
|
7
|
+
0.1.2 was tagged without its `RougeCarve::VERSION` bump, so it never reached RubyGems. 0.1.3 is the first published release with its change.
|
|
8
|
+
|
|
9
|
+
- An include selector whose id starts with a digit is lexed as a selector. `{{ ch.crv #2024-plan }}` used to fall through to plain text (#15, #16).
|
|
10
|
+
- A footnote or abbreviation definition ends with its line. It used to turn the rest of the file into plain text, so no later heading, list or fence was recognized (#22).
|
|
11
|
+
- An inline footnote `^[` or extension `:name[` with no closing `]` in its block stays text instead of swallowing the blocks after it. A `]` inside a code span does not count, and a heading, caption or table cell keeps the body on its line (#22, #24).
|
|
12
|
+
- A blockquote line can open a block: `> # Heading`, `> - item`, and a fence whose lines carry the `>` prefix (#22).
|
|
13
|
+
- A `>` marks a quote only when a space or the line end follows it, so `>> x` is paragraph text and `> > x` nests, as in the engine (#22).
|
|
14
|
+
- A comment fence closes only on a `%` run of its own length, which may be followed by text. An opener with no such closer is a one-line comment, as in the engine (#22).
|
|
15
|
+
- A standalone attribute block no longer runs across a blank line (#22).
|
|
16
|
+
- Bare delimiters follow Carve's flanking rules, so `snake_case_name`, `/usr/local/bin`, `x=1 and y=2`, `2*3*4` and the text between `==>` arrows stay text (#23).
|
|
17
|
+
- Emphasis content is lexed inline: an escape, a code span, a link or another kind of emphasis inside a run keeps its own token, and `*a \* b*` closes where the engine closes it (#23). A run opened inside another must close before it, so `*a /b* c/` is strong `a /b` (#24).
|
|
18
|
+
- A list marker or a caption under a paragraph line stays text, as in the engine; headings, quotes, tables, containers and closed fences still interrupt it. A wrapped definition term keeps its definition (#23).
|
|
19
|
+
- A table row needs its closing pipe, `+` is no longer read as a bullet, and an alphabetic ordered marker is one letter or a roman numeral, so `Body.` is a word (#23).
|
|
20
|
+
- Lexing a long line of unclosed openers (`*a *a *a ...`, `{{ a {{ a ...`) is linear in its length; it used to be quadratic (#23).
|
|
21
|
+
- The gem now requires Rouge 3.30 or later. Rouge 3.0, which the old constraint admitted, cannot load the lexer (#23).
|
|
22
|
+
|
|
23
|
+
## [0.1.2] - 2026-10-08
|
|
24
|
+
|
|
25
|
+
- An include selector whose id starts with a digit is lexed as a selector. `{{ ch.crv #2024-plan }}` used to fall through to plain text, because the lexer required a letter or underscore first while the spec's `explicit_identifier` has admitted a leading digit since carve 0.1.8 (#15).
|
|
26
|
+
|
|
3
27
|
## [0.1.1] - 2026-09-21
|
|
4
28
|
|
|
5
29
|
- The reserved include directive `{{ path #section @key:value }}` (PART 9
|
data/README.md
CHANGED
|
@@ -14,6 +14,8 @@ Rouge::Lexer.find('carve') # => Rouge::Lexers::Carve
|
|
|
14
14
|
Rouge::Lexer.guess(filename: 'notes.crv') # => Rouge::Lexers::Carve
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
It needs Rouge 3.30 or later and is tested against Rouge 3, 4 and 5.
|
|
18
|
+
|
|
17
19
|
Requiring the gem registers the lexer. Anything that already highlights with
|
|
18
20
|
Rouge - GitLab, Jekyll, Redcarpet, most Ruby static site generators - then
|
|
19
21
|
handles `.crv` and `.carve` files, and ` ```carve ` fenced blocks, without
|
data/lib/rouge/lexers/carve.rb
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
module Rouge
|
|
5
5
|
module Lexers
|
|
6
|
-
# Carve is a
|
|
6
|
+
# Carve is a lightweight markup language for documents and the web. Its
|
|
7
|
+
# inline
|
|
7
8
|
# delimiters deliberately differ from Markdown's, which is why lexing a
|
|
8
9
|
# Carve document as Markdown produces actively WRONG output rather than
|
|
9
10
|
# merely plain text:
|
|
@@ -48,7 +49,7 @@ module Rouge
|
|
|
48
49
|
# Spec: https://markup-carve.github.io/carve/
|
|
49
50
|
class Carve < RegexLexer
|
|
50
51
|
title 'Carve'
|
|
51
|
-
desc 'Carve, a
|
|
52
|
+
desc 'Carve, a lightweight markup language for documents and the web'
|
|
52
53
|
|
|
53
54
|
tag 'carve'
|
|
54
55
|
aliases 'crv'
|
|
@@ -62,28 +63,37 @@ module Rouge
|
|
|
62
63
|
# One attribute block, brace to brace. Quoted values may contain a brace
|
|
63
64
|
# and an escaped quote, so the value alternatives come before the
|
|
64
65
|
# bare-character one; a single nested brace pair is allowed for a braced
|
|
65
|
-
# span written inside.
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
# span written inside. The tail is the block after its `{`.
|
|
67
|
+
ATTRS_TAIL = %r/
|
|
68
|
+
(?=[.:}'"]|\#[\w-]+[\s}]|[A-Za-z][\w-]*(?:[=\s}]|$))(?:
|
|
68
69
|
"(?:[^"\\\n]|\\.)*"
|
|
69
70
|
|'(?:[^'\\\n]|\\.)*'
|
|
70
71
|
|\{[^{}\n]*\}
|
|
71
72
|
|[^{}\n]
|
|
72
73
|
)*\}
|
|
73
74
|
/x.freeze
|
|
75
|
+
ATTRS = /\{#{ATTRS_TAIL}/.freeze
|
|
74
76
|
|
|
75
|
-
# The same block as a STANDALONE ATTRIBUTE LINE, which may span lines
|
|
76
|
-
#
|
|
77
|
-
#
|
|
77
|
+
# The same block as a STANDALONE ATTRIBUTE LINE, which may span lines
|
|
78
|
+
# but not a blank one: a blank line ends the block it would attach to.
|
|
79
|
+
# The inline form cannot span lines at all, because an unclosed inline
|
|
80
|
+
# `{` would otherwise swallow the rest of the document.
|
|
81
|
+
LINE_BREAK = '\n(?![ \t]*(?:\n|\z))'
|
|
78
82
|
ATTRS_LINE = %r/
|
|
79
83
|
\{(?=[.:}'"]|\#[\w-]+[\s}]|[A-Za-z][\w-]*(?:[=\s}]|$))(?:
|
|
80
|
-
"(?:[^"
|
|
81
|
-
|'(?:[^'
|
|
82
|
-
|\{[^{}]*\}
|
|
83
|
-
|[^{}]
|
|
84
|
+
"(?:[^"\\\n]|\\.|#{LINE_BREAK})*"
|
|
85
|
+
|'(?:[^'\\\n]|\\.|#{LINE_BREAK})*'
|
|
86
|
+
|\{(?:[^{}\n]|#{LINE_BREAK})*\}
|
|
87
|
+
|[^{}\n]
|
|
88
|
+
|#{LINE_BREAK}
|
|
84
89
|
)*\}
|
|
85
90
|
/x.freeze
|
|
86
91
|
|
|
92
|
+
# The prefix of a line inside a blockquote, nested markers included.
|
|
93
|
+
# Each `>` needs a space or the line end after it: `>foo` and `>> x`
|
|
94
|
+
# are paragraph text.
|
|
95
|
+
QUOTE_PREFIX = /(?:[ \t]*>(?=[ \t]|$))+[ \t]?/.freeze
|
|
96
|
+
|
|
87
97
|
# A bracketed label that may itself contain three levels of brackets. A
|
|
88
98
|
# regex cannot match arbitrarily balanced brackets, and a link label in
|
|
89
99
|
# practice nests a level or two (`[t[z]](/u)`). Bounding the nesting keeps
|
|
@@ -96,6 +106,69 @@ module Rouge
|
|
|
96
106
|
Regexp.new("\\[#{pattern}\\]").freeze
|
|
97
107
|
end
|
|
98
108
|
|
|
109
|
+
# An ordered marker: a number, one letter, or a roman numeral. `Body.`
|
|
110
|
+
# is a word, not a marker.
|
|
111
|
+
ORDERED = '(?:\\d+|[A-Za-z]|[ivxlcdm]+|[IVXLCDM]+)[.)]'
|
|
112
|
+
|
|
113
|
+
# A list marker at the start of a line, as the list rules below read it.
|
|
114
|
+
LIST_MARKER = "(?:[-*]|#{ORDERED}|\\.)(?:#{ATTRS})?(?:[ \\t]|$)"
|
|
115
|
+
|
|
116
|
+
# A table row needs its closing pipe: `| not a table` is a paragraph.
|
|
117
|
+
ROW_END = /(?=(?:\\.|[^\\\n])*\|[ \t]*(?:#{ATTRS})?[ \t]*$)/.freeze
|
|
118
|
+
|
|
119
|
+
# The block openers that interrupt a paragraph. A list marker and a
|
|
120
|
+
# caption do not, and a fence does only when it is closed - that one is
|
|
121
|
+
# checked in code, see the :paraline state.
|
|
122
|
+
INTERRUPT = %r/
|
|
123
|
+
\#{1,6}[ \t] | >(?:[ \t]|$) | ::: | ::[ \t] | %%
|
|
124
|
+
| (?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$
|
|
125
|
+
| \|=?[<>^v~]*#{ROW_END}
|
|
126
|
+
| (?:\[\^|\*\[|\[)[^\]\n]+\]:[ ]
|
|
127
|
+
| #{ATTRS_LINE}[ \t]*$
|
|
128
|
+
/x.freeze
|
|
129
|
+
|
|
130
|
+
# Where a bracketed inline body must close, by the block it sits in: a
|
|
131
|
+
# paragraph ends at a blank line or an interrupting block, a list item
|
|
132
|
+
# also at the next marker, and a heading or table row at its line end.
|
|
133
|
+
# A fence ends either only when it is closed, which is checked in code.
|
|
134
|
+
BODY_ENDS = {
|
|
135
|
+
para: /\n(?=[ \t]*(?:\n|\z)|#{MARGIN}(?:#{INTERRUPT}))/,
|
|
136
|
+
item: /\n(?=[ \t]*(?:\n|\z)|#{MARGIN}(?:#{INTERRUPT}|#{LIST_MARKER}|:[ \t]))/,
|
|
137
|
+
line: /\n/
|
|
138
|
+
}.freeze
|
|
139
|
+
FENCE_NEXT = /\n(?=#{MARGIN}(`{3,}|~{3,}))/.freeze
|
|
140
|
+
|
|
141
|
+
# The block states that decide which of those ends applies.
|
|
142
|
+
BODY_SCOPE = {
|
|
143
|
+
paragraph: :para, listitem: :item, dlterm: :item, heading: :line,
|
|
144
|
+
caption: :line, quoteline: :line, tablerow: :line, defline: :line
|
|
145
|
+
}.freeze
|
|
146
|
+
|
|
147
|
+
# A paragraph that is a lone image or display math: a caption under it
|
|
148
|
+
# is that block's.
|
|
149
|
+
IMAGE_LINE = %r/
|
|
150
|
+
#{MARGIN}(?:!#{LABEL}(?:\([^)\n]*\)|#{LABEL})?|\$\$(`+)[^\n]*?\1)(?:#{ATTRS})?[ \t]*$
|
|
151
|
+
/x.freeze
|
|
152
|
+
|
|
153
|
+
# The braced inline families: delimiter, content token, state.
|
|
154
|
+
BRACED = {
|
|
155
|
+
'*' => [Generic::Strong, :forcedstrong], '/' => [Generic::Emph, :forcedemph],
|
|
156
|
+
'_' => [Generic::Emph, :forcedunderline], '^' => [Generic::Emph, :superscript],
|
|
157
|
+
',' => [Generic::Emph, :subscript], '=' => [Generic::Inserted, :bracedmark],
|
|
158
|
+
'+' => [Generic::Inserted, :insertion], '-' => [Generic::Deleted, :deletion]
|
|
159
|
+
}.freeze
|
|
160
|
+
|
|
161
|
+
# The bare emphasis delimiters: state, content token, and the characters
|
|
162
|
+
# that may not stand before an opener or after it. A closer needs a
|
|
163
|
+
# non-space before it and no letter or digit after it.
|
|
164
|
+
BARE = {
|
|
165
|
+
'*' => [:strong, Generic::Strong, /[\w*]/, /[\s*]/],
|
|
166
|
+
'/' => [:emph, Generic::Emph, /[\w\/]/, /[\s\/]/],
|
|
167
|
+
'_' => [:underline, Generic::Emph, /[\w_\/]/, /[\s_]/],
|
|
168
|
+
'~' => [:strike, Generic::Deleted, /[\w~]/, /[\s~]/],
|
|
169
|
+
'=' => [:mark, Generic::Inserted, /[\w=<>!]/, /[\s>=]/]
|
|
170
|
+
}.freeze
|
|
171
|
+
|
|
99
172
|
# Characters that can begin an inline construct. A run of anything else is
|
|
100
173
|
# ordinary content and is emitted as ONE token - without this every
|
|
101
174
|
# content character becomes its own token, which is both noisy and
|
|
@@ -118,6 +191,147 @@ module Rouge
|
|
|
118
191
|
# ------------------------------------------------------------------
|
|
119
192
|
# Block level
|
|
120
193
|
# ------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
# The openers a blockquote marker can introduce as well as a line start.
|
|
196
|
+
# They are written once and instantiated twice: anchored at a line start
|
|
197
|
+
# for the document, and unanchored for the rest of a line after `>`,
|
|
198
|
+
# where the scanner already sits where the block would begin. Each one
|
|
199
|
+
# enters its state through #open_block, which knows which of the two it
|
|
200
|
+
# is running in.
|
|
201
|
+
def self.block_openers(bol)
|
|
202
|
+
proc do
|
|
203
|
+
# A raw block: the `=FORMAT` info string routes the payload to that
|
|
204
|
+
# output format verbatim. Emitted as one token including the `=`,
|
|
205
|
+
# because the format word without its sigil is not the construct.
|
|
206
|
+
# The payload is never a language, so it is never delegated.
|
|
207
|
+
rule %r/#{bol}(#{MARGIN})(`{3,}|~{3,})([ \t]*)(=[a-zA-Z][\w+.-]*)([^\n]*)/ do |m|
|
|
208
|
+
groups Text, Punctuation, Text, Keyword::Type
|
|
209
|
+
token Text, m[5]
|
|
210
|
+
push_fence(m[2], nil)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# A code fence. The first word of the info string names a language,
|
|
214
|
+
# and a lexer for it highlights the payload - the one place this
|
|
215
|
+
# lexer looks past Carve, because a reader of a `.crv` file expects
|
|
216
|
+
# an embedded PHP sample to look like PHP.
|
|
217
|
+
rule %r/#{bol}(#{MARGIN})(`{3,}|~{3,})([ \t]*)([a-zA-Z][\w+#.-]*)?([^\n]*)/ do |m|
|
|
218
|
+
groups Text, Punctuation, Text, Name::Builtin
|
|
219
|
+
lex_info_string(m[5])
|
|
220
|
+
push_fence(m[2], m[4])
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Container divs. A reserved kind word (note, tip, figure, ...)
|
|
224
|
+
# names a known container; `:::` followed by `|` is the layout form.
|
|
225
|
+
rule %r/#{bol}(#{MARGIN})(:{3,})([ \t]*)(\|)/ do
|
|
226
|
+
groups Text, Punctuation, Text, Operator
|
|
227
|
+
open_block
|
|
228
|
+
end
|
|
229
|
+
rule %r/#{bol}(#{MARGIN})(:{3,})([ \t]*)([a-zA-Z][\w-]*)?([^\n]*)/ do |m|
|
|
230
|
+
groups Text, Punctuation, Text, Keyword::Namespace
|
|
231
|
+
lex_info_string(m[5])
|
|
232
|
+
open_block
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# A caption line attaches to the block above or below it.
|
|
236
|
+
rule %r/#{bol}(#{MARGIN})(\^)([ \t]+)/ do
|
|
237
|
+
groups Text, Punctuation, Text
|
|
238
|
+
open_block :caption
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Headings. Carve has no setext form, so a `#` run is the only
|
|
242
|
+
# spelling and a trailing `{...}` is NOT an attribute block here.
|
|
243
|
+
rule %r/#{bol}(#{MARGIN})(\#{1,6})([ \t]+)/ do
|
|
244
|
+
groups Text, Punctuation, Text
|
|
245
|
+
open_block :heading
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Thematic breaks, before the list rules so `---` is not a bullet.
|
|
249
|
+
rule %r/#{bol}#{MARGIN}(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$/ do
|
|
250
|
+
token Punctuation
|
|
251
|
+
open_block
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Definition markers: footnote, link reference, abbreviation. The
|
|
255
|
+
# separator after the colon must START WITH A LITERAL SPACE - a
|
|
256
|
+
# tab-first separator makes the line an ordinary paragraph. Each
|
|
257
|
+
# definition ends with its line.
|
|
258
|
+
rule %r/#{bol}(#{MARGIN})(\[\^)([^\]\n]+)(\]:)( )/ do
|
|
259
|
+
groups Text, Punctuation, Name::Label, Punctuation, Text
|
|
260
|
+
open_block :defline
|
|
261
|
+
end
|
|
262
|
+
rule %r/#{bol}(#{MARGIN})(\*\[)([^\]\n]+)(\]:)( )/ do
|
|
263
|
+
groups Text, Punctuation, Name::Entity, Punctuation, Text
|
|
264
|
+
open_block :defline
|
|
265
|
+
end
|
|
266
|
+
rule %r/#{bol}(#{MARGIN})(\[)([^\]\n]+)(\]:)( )/ do
|
|
267
|
+
groups Text, Punctuation, Name::Label, Punctuation, Text
|
|
268
|
+
open_block :linkdest
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# A definition-list term (`::`) and its definition (`:`). A term
|
|
272
|
+
# may wrap onto lazy lines, and a definition's text is an item's.
|
|
273
|
+
rule %r/#{bol}(#{MARGIN})(::)([ \t]+)/ do
|
|
274
|
+
groups Text, Punctuation, Text
|
|
275
|
+
open_block :dlterm, :heading
|
|
276
|
+
end
|
|
277
|
+
rule %r/#{bol}(#{MARGIN})(:)(?=[ \t])/ do
|
|
278
|
+
groups Text, Punctuation
|
|
279
|
+
open_block :listitem, :quoteline
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# A blockquote marker must be followed by a space or end the line;
|
|
283
|
+
# `>foo` and `>> x` are paragraphs, and `> > x` nests. What follows
|
|
284
|
+
# a marker may open a block of its own, so the openers are tried
|
|
285
|
+
# again there.
|
|
286
|
+
rule %r/#{bol}(#{MARGIN})(>)(?=[ \t]|$)/ do
|
|
287
|
+
groups Text, Punctuation
|
|
288
|
+
state?(:quotestart) ? goto(:quotestart) : push(:quotestart)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Task items before plain bullets, so the state marker is its own
|
|
292
|
+
# token. The state is any single character, not only a space or an
|
|
293
|
+
# x: `[>]` is deferred and `[-]` is dropped. A `+` is not a bullet in
|
|
294
|
+
# Carve; it continues a container.
|
|
295
|
+
rule %r/#{bol}(#{MARGIN})((?:[-*]|#{ORDERED})(?:#{ATTRS})?)([ \t]+)(\[[^\]\n]\])/ do
|
|
296
|
+
groups Text, Punctuation, Text, Name::Constant
|
|
297
|
+
open_block :listitem, :quoteline
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Bullets. A run of markers on one line opens nested lists at once
|
|
301
|
+
# (`- - A`), and attributes may be glued straight onto the marker.
|
|
302
|
+
rule %r/#{bol}(#{MARGIN})((?:[-*][ \t]+)*[-*](?:#{ATTRS})?)(?=[ \t]|$)/ do
|
|
303
|
+
groups Text, Punctuation
|
|
304
|
+
open_block :listitem, :quoteline
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Ordered markers: numeric, alphabetic, roman, and the bare `.` that
|
|
308
|
+
# continues the enclosing sequence.
|
|
309
|
+
rule %r/#{bol}(#{MARGIN})(#{ORDERED}(?:#{ATTRS})?)(?=[ \t]|$)/ do
|
|
310
|
+
groups Text, Num::Integer
|
|
311
|
+
open_block :listitem, :quoteline
|
|
312
|
+
end
|
|
313
|
+
rule %r/#{bol}(#{MARGIN})(\.(?:#{ATTRS})?)(?=[ \t]|$)/ do
|
|
314
|
+
groups Text, Num::Integer
|
|
315
|
+
open_block :listitem, :quoteline
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Tables. The header marker, the alignment run and the separator row
|
|
319
|
+
# are their own tokens; cell content is lexed inline. A row needs its
|
|
320
|
+
# closing pipe.
|
|
321
|
+
rule %r/#{bol}(#{MARGIN})(\|=[<>^v~]*)#{ROW_END}/ do
|
|
322
|
+
groups Text, Operator
|
|
323
|
+
open_block :tablerow
|
|
324
|
+
end
|
|
325
|
+
rule %r/#{bol}(#{MARGIN})(\|)#{ROW_END}/ do
|
|
326
|
+
groups Text, Punctuation
|
|
327
|
+
open_block :tablerow
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
state(:blockopeners, &block_openers('^'))
|
|
333
|
+
state(:quoteopeners, &block_openers(''))
|
|
334
|
+
|
|
121
335
|
state :block do
|
|
122
336
|
# Front matter, only at the very start of the document. \A is what
|
|
123
337
|
# keeps a `---yaml` line mid-document from opening one.
|
|
@@ -127,134 +341,97 @@ module Rouge
|
|
|
127
341
|
end
|
|
128
342
|
|
|
129
343
|
# A comment fence (%%% or longer) versus a one-line comment. The fence
|
|
130
|
-
# has to be tried first: %%% also matches the one-line form.
|
|
131
|
-
|
|
344
|
+
# has to be tried first: %%% also matches the one-line form. Only a
|
|
345
|
+
# run of the SAME length closes it, so the width is closed over the
|
|
346
|
+
# way #push_fence does it, and an opener with no such closer below it
|
|
347
|
+
# is the one-line form. Text after either run is part of the comment.
|
|
348
|
+
rule %r/^(#{MARGIN})(%%%+)([^\n]*)(\n)/ do |m|
|
|
349
|
+
closer = %r/^([ \t]*)(%{#{m[2].length}})(?!%)([^\n]*)$/
|
|
350
|
+
unless closer_below?(closer, m[2].length)
|
|
351
|
+
# The tokens of the one-line rule below. Rouge 3 has no
|
|
352
|
+
# fallthrough! to hand the line on to it.
|
|
353
|
+
token Text, m[1]
|
|
354
|
+
token Comment::Preproc, '%%'
|
|
355
|
+
token Comment, m[2][2..] + m[3]
|
|
356
|
+
token Text, m[4]
|
|
357
|
+
next
|
|
358
|
+
end
|
|
359
|
+
|
|
132
360
|
groups Text, Comment::Preproc, Comment, Text
|
|
133
|
-
push
|
|
361
|
+
push do
|
|
362
|
+
rule closer do
|
|
363
|
+
groups Text, Comment::Preproc, Comment
|
|
364
|
+
pop!
|
|
365
|
+
end
|
|
366
|
+
rule %r/\n/, Comment
|
|
367
|
+
rule %r/[^\n]+\n?/, Comment
|
|
368
|
+
end
|
|
134
369
|
end
|
|
135
370
|
rule %r/^(#{MARGIN})(%%)([^\n]*)$/ do
|
|
136
371
|
groups Text, Comment::Preproc, Comment
|
|
137
372
|
end
|
|
138
373
|
|
|
139
|
-
|
|
140
|
-
# output format verbatim. Emitted as one token including the `=`,
|
|
141
|
-
# because the format word without its sigil is not the construct. The
|
|
142
|
-
# payload is never a language, so it is never delegated.
|
|
143
|
-
rule %r/^(#{MARGIN})(`{3,}|~{3,})([ \t]*)(=[a-zA-Z][\w+.-]*)([^\n]*)/ do |m|
|
|
144
|
-
groups Text, Punctuation, Text, Keyword::Type
|
|
145
|
-
token Text, m[5]
|
|
146
|
-
push_fence(m[2], nil)
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# A code fence. The first word of the info string names a language, and
|
|
150
|
-
# a lexer for it highlights the payload - the one place this lexer
|
|
151
|
-
# looks past Carve, because a reader of a `.crv` file expects an
|
|
152
|
-
# embedded PHP sample to look like PHP.
|
|
153
|
-
rule %r/^(#{MARGIN})(`{3,}|~{3,})([ \t]*)([a-zA-Z][\w+#.-]*)?([^\n]*)/ do |m|
|
|
154
|
-
groups Text, Punctuation, Text, Name::Builtin
|
|
155
|
-
lex_info_string(m[5])
|
|
156
|
-
push_fence(m[2], m[4])
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# Container divs. A reserved kind word (note, tip, figure, ...) names a
|
|
160
|
-
# known container; `:::` followed by `|` is the layout form.
|
|
161
|
-
rule %r/^(#{MARGIN})(:{3,})([ \t]*)(\|)/ do
|
|
162
|
-
groups Text, Punctuation, Text, Operator
|
|
163
|
-
end
|
|
164
|
-
rule %r/^(#{MARGIN})(:{3,})([ \t]*)([a-zA-Z][\w-]*)?([^\n]*)/ do |m|
|
|
165
|
-
groups Text, Punctuation, Text, Keyword::Namespace
|
|
166
|
-
lex_info_string(m[5])
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
# A caption line attaches to the block above or below it.
|
|
170
|
-
rule %r/^(#{MARGIN})(\^)([ \t]+)/ do
|
|
171
|
-
groups Text, Punctuation, Text
|
|
172
|
-
push :caption
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# Headings. Carve has no setext form, so a `#` run is the only spelling
|
|
176
|
-
# and a trailing `{...}` is NOT an attribute block here.
|
|
177
|
-
rule %r/^(#{MARGIN})(\#{1,6})([ \t]+)/ do
|
|
178
|
-
groups Text, Punctuation, Text
|
|
179
|
-
push :heading
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# Thematic breaks, before the list rules so `---` is not a bullet.
|
|
183
|
-
rule %r/^#{MARGIN}(?:\*[ \t]*){3,}$/, Punctuation
|
|
184
|
-
rule %r/^#{MARGIN}(?:-[ \t]*){3,}$/, Punctuation
|
|
185
|
-
rule %r/^#{MARGIN}(?:_[ \t]*){3,}$/, Punctuation
|
|
374
|
+
mixin :blockopeners
|
|
186
375
|
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
|
|
190
|
-
rule %r/^(#{MARGIN})(\[\^)([^\]\n]+)(\]:)( )/ do
|
|
191
|
-
groups Text, Punctuation, Name::Label, Punctuation, Text
|
|
192
|
-
push :inline
|
|
193
|
-
end
|
|
194
|
-
rule %r/^(#{MARGIN})(\*\[)([^\]\n]+)(\]:)( )/ do
|
|
195
|
-
groups Text, Punctuation, Name::Entity, Punctuation, Text
|
|
196
|
-
push :inline
|
|
197
|
-
end
|
|
198
|
-
rule %r/^(#{MARGIN})(\[)([^\]\n]+)(\]:)( )/ do
|
|
199
|
-
groups Text, Punctuation, Name::Label, Punctuation, Text
|
|
200
|
-
push :linkdest
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
# A definition-list term (`::`) and its definition (`:`).
|
|
204
|
-
rule %r/^(#{MARGIN})(::)([ \t]+)/ do
|
|
205
|
-
groups Text, Punctuation, Text
|
|
206
|
-
push :heading
|
|
207
|
-
end
|
|
208
|
-
rule %r/^(#{MARGIN})(:)(?=[ \t])/ do
|
|
209
|
-
groups Text, Punctuation
|
|
376
|
+
# A standalone attribute block, which may span lines.
|
|
377
|
+
rule %r/^(#{MARGIN})(#{ATTRS_LINE})/ do
|
|
378
|
+
groups Text, Name::Attribute
|
|
210
379
|
end
|
|
211
380
|
|
|
212
|
-
#
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
push :
|
|
381
|
+
# Any other line with content starts a paragraph.
|
|
382
|
+
rule %r/^(?=#{MARGIN}\S)/ do
|
|
383
|
+
@lone_image = !@current_stream.check(IMAGE_LINE).nil?
|
|
384
|
+
@indented = @current_stream.check(/[ \t]/) ? true : false
|
|
385
|
+
push :paragraph
|
|
217
386
|
end
|
|
218
387
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
# `[>]` is deferred and `[-]` is dropped.
|
|
222
|
-
rule %r/^(#{MARGIN})((?:[-*+]|\d+[.)]|[A-Za-z]+[.)])(?:#{ATTRS})?)([ \t]+)(\[[^\]\n]\])/ do
|
|
223
|
-
groups Text, Punctuation, Text, Name::Constant
|
|
224
|
-
end
|
|
388
|
+
mixin :inline
|
|
389
|
+
end
|
|
225
390
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
391
|
+
# A paragraph, and the start of each line after its first: a line that
|
|
392
|
+
# opens a block interrupting it ends it, anything else continues it, so
|
|
393
|
+
# a list marker or a caption under prose is prose.
|
|
394
|
+
state :paragraph do
|
|
395
|
+
rule(/\n/) { token Text; goto :paraline }
|
|
396
|
+
mixin :inlinecontent
|
|
397
|
+
rule PLAIN, Text
|
|
398
|
+
rule %r/./, Text
|
|
399
|
+
end
|
|
231
400
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
rule
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
401
|
+
state :paraline do
|
|
402
|
+
rule(%r/^(?=[ \t]*(?:\n|\z))/) { pop! }
|
|
403
|
+
rule(%r/^(?=#{MARGIN}(?:#{INTERRUPT}))/) { pop! }
|
|
404
|
+
rule(%r/^(?=#{MARGIN}(`{3,}|~{3,}))/) { |m| fence_closed_below?(m[1]) ? pop! : continue_paragraph(:paragraph) }
|
|
405
|
+
rule(%r/^(?=#{MARGIN}\^[ \t])/) { @lone_image ? pop! : continue_paragraph(:paragraph) }
|
|
406
|
+
# An indented paragraph sits in a container, so a list marker below it
|
|
407
|
+
# opens a sublist or the next item rather than continuing it.
|
|
408
|
+
rule(%r/^(?=#{MARGIN}#{LIST_MARKER})/) { @indented ? pop! : continue_paragraph(:paragraph) }
|
|
409
|
+
rule(//) { continue_paragraph(:paragraph) }
|
|
410
|
+
end
|
|
240
411
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
412
|
+
# The text of a list item, a definition or a definition term. The line
|
|
413
|
+
# after it may also start the next item or definition.
|
|
414
|
+
{ listitem: Text, dlterm: Generic::Heading }.each do |name, tok|
|
|
415
|
+
line = :"#{name}line"
|
|
416
|
+
state name do
|
|
417
|
+
rule(/\n/) { token Text; goto line }
|
|
418
|
+
mixin :inlinecontent
|
|
419
|
+
rule PLAIN, tok
|
|
420
|
+
rule(/./) { token plain_token }
|
|
250
421
|
end
|
|
251
422
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
423
|
+
state line do
|
|
424
|
+
rule(%r/^(?=[ \t]*(?:\n|\z))/) { pop! }
|
|
425
|
+
rule(%r/^(?=#{MARGIN}(?:#{INTERRUPT}|#{LIST_MARKER}|:[ \t]))/) { pop! }
|
|
426
|
+
rule(%r/^(?=#{MARGIN}(`{3,}|~{3,}))/) { |m| fence_closed_below?(m[1]) ? pop! : goto(name) }
|
|
427
|
+
rule(//) { goto name }
|
|
255
428
|
end
|
|
429
|
+
end
|
|
256
430
|
|
|
257
|
-
|
|
431
|
+
# Right after a blockquote marker: a block opener, or quoted text.
|
|
432
|
+
state :quotestart do
|
|
433
|
+
mixin :quoteopeners
|
|
434
|
+
rule(//) { goto :quoteline }
|
|
258
435
|
end
|
|
259
436
|
|
|
260
437
|
state :frontmatter do
|
|
@@ -266,17 +443,16 @@ module Rouge
|
|
|
266
443
|
rule %r/[^\n]+\n?/, Comment::Special
|
|
267
444
|
end
|
|
268
445
|
|
|
269
|
-
state :
|
|
270
|
-
rule %r
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
rule %r/\n/, Comment
|
|
275
|
-
rule %r/[^\n]+\n?/, Comment
|
|
446
|
+
state :defline do
|
|
447
|
+
rule %r/$/, Text, :pop!
|
|
448
|
+
mixin :inlinecontent
|
|
449
|
+
rule PLAIN, Text
|
|
450
|
+
rule %r/[^\n]/, Text
|
|
276
451
|
end
|
|
277
452
|
|
|
278
453
|
state :heading do
|
|
279
454
|
rule %r/$/, Text, :pop!
|
|
455
|
+
mixin :lineverbatim
|
|
280
456
|
mixin :inlinecontent
|
|
281
457
|
rule PLAIN, Generic::Heading
|
|
282
458
|
rule %r/[^\n]/, Generic::Heading
|
|
@@ -284,6 +460,7 @@ module Rouge
|
|
|
284
460
|
|
|
285
461
|
state :quoteline do
|
|
286
462
|
rule %r/$/, Text, :pop!
|
|
463
|
+
mixin :lineverbatim
|
|
287
464
|
mixin :inlinecontent
|
|
288
465
|
rule PLAIN, Generic::Emph
|
|
289
466
|
rule %r/[^\n]/, Generic::Emph
|
|
@@ -291,6 +468,7 @@ module Rouge
|
|
|
291
468
|
|
|
292
469
|
state :caption do
|
|
293
470
|
rule %r/$/, Text, :pop!
|
|
471
|
+
mixin :lineverbatim
|
|
294
472
|
mixin :inlinecontent
|
|
295
473
|
rule PLAIN, Generic::Subheading
|
|
296
474
|
rule %r/[^\n]/, Generic::Subheading
|
|
@@ -330,6 +508,18 @@ module Rouge
|
|
|
330
508
|
rule %r/./, Text
|
|
331
509
|
end
|
|
332
510
|
|
|
511
|
+
state :lineverbatim do
|
|
512
|
+
rule %r/(!)(`+)(?!`)((?:(?!(?<!`)\2(?!`))[^\n])*)((?<!`)\2(?!`)|$)/ do
|
|
513
|
+
groups Operator, Punctuation, Literal, Punctuation
|
|
514
|
+
end
|
|
515
|
+
rule %r/(\$\$?)(`+)(?!`)((?:(?!(?<!`)\2(?!`))[^\n])*)((?<!`)\2(?!`)|$)/ do
|
|
516
|
+
groups Operator, Punctuation, Str::Other, Punctuation
|
|
517
|
+
end
|
|
518
|
+
rule %r/(`+)(?!`)((?:(?!(?<!`)\1(?!`))[^\n])*)((?<!`)\1(?!`)|$)/ do
|
|
519
|
+
groups Punctuation, Str::Backtick, Punctuation
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
|
|
333
523
|
state :inlinecontent do
|
|
334
524
|
# An escape wins over every delimiter that follows it. A backslash at
|
|
335
525
|
# END OF LINE escapes nothing - it is a hard break, and needs its own
|
|
@@ -370,74 +560,95 @@ module Rouge
|
|
|
370
560
|
# as a hashtag inside a path. After the verbatim family, so a directive
|
|
371
561
|
# in a code span stays literal; before every other brace rule, none of
|
|
372
562
|
# which can spell `{{`.
|
|
373
|
-
#
|
|
374
|
-
#
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
563
|
+
# An unterminated `{{` never opens the state and stays the ordinary
|
|
564
|
+
# text the processor treats it as.
|
|
565
|
+
rule %r/(\{\{)([ \t]+)((?:"(?:\\.|[^"\\])*"|[^#@}\s"][^#@}\s]*))/ do |m|
|
|
566
|
+
if closer_ahead?(:include, /[ \t]\}\}/)
|
|
567
|
+
groups Punctuation, Text, Name::Namespace
|
|
568
|
+
push :includeparts
|
|
569
|
+
else
|
|
570
|
+
token plain_token, m[0]
|
|
571
|
+
end
|
|
380
572
|
end
|
|
381
573
|
|
|
382
|
-
#
|
|
383
|
-
#
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
574
|
+
# The braced families. Each opener consumes only its `{` here, so an
|
|
575
|
+
# unclosed one leaves the character after it to the rules below, and
|
|
576
|
+
# its closer must be on the line - looked up in a per-line cache, so
|
|
577
|
+
# a run of unclosed openers stays linear.
|
|
578
|
+
#
|
|
579
|
+
# CriticMarkup substitution and forced strike share `{~`; the first of
|
|
580
|
+
# `~>` and `~}` decides which one it is.
|
|
581
|
+
rule %r/\{(?=~(?!~\}))/ do
|
|
582
|
+
next token(plain_token) unless closer_ahead?(:tilde, /~\}/)
|
|
390
583
|
|
|
391
|
-
|
|
392
|
-
# where the bare form would not - `my{_path_}name` is underline inside
|
|
393
|
-
# a word - so the content is emphasis and the braces are its
|
|
394
|
-
# delimiters, not an attribute block.
|
|
395
|
-
rule %r/(\{\*)([^\n]+?)(\*\})/ do
|
|
396
|
-
groups Punctuation, Generic::Strong, Punctuation
|
|
397
|
-
end
|
|
398
|
-
rule %r/(\{\/)([^\n]+?)(\/\})/ do
|
|
399
|
-
groups Punctuation, Generic::Emph, Punctuation
|
|
400
|
-
end
|
|
401
|
-
rule %r/(\{_)([^\n]+?)(_\})/ do
|
|
402
|
-
groups Punctuation, Generic::Emph, Punctuation
|
|
403
|
-
end
|
|
404
|
-
rule %r/(\{~)([^\n]+?)(~\})/ do
|
|
405
|
-
groups Punctuation, Generic::Deleted, Punctuation
|
|
584
|
+
enter_braced(:tildebrace)
|
|
406
585
|
end
|
|
407
586
|
|
|
408
|
-
#
|
|
409
|
-
#
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
587
|
+
# A CriticMarkup comment. Without its closer, `{#id}` is an attribute
|
|
588
|
+
# block.
|
|
589
|
+
# The scanner's match is read before #check replaces it.
|
|
590
|
+
rule %r/\{(?=\#)/ do |m|
|
|
591
|
+
brace = m[0]
|
|
592
|
+
if closer_ahead?(:criticcomment, /\#\}/, @current_stream.charpos + 1)
|
|
593
|
+
enter_braced(:criticcomment)
|
|
594
|
+
elsif (tail = @current_stream.check(ATTRS_TAIL))
|
|
595
|
+
token Name::Attribute, brace
|
|
596
|
+
consume_as(Name::Attribute, tail)
|
|
597
|
+
else
|
|
598
|
+
token plain_token, brace
|
|
599
|
+
end
|
|
421
600
|
end
|
|
422
|
-
|
|
423
|
-
|
|
601
|
+
|
|
602
|
+
# The FORCED emphasis family and the other braced inlines. Braces make
|
|
603
|
+
# a delimiter apply where the bare form would not - `my{_path_}name`
|
|
604
|
+
# is underline inside a word - so the content is emphasis and the
|
|
605
|
+
# braces are its delimiters, not an attribute block. Sup and sub are
|
|
606
|
+
# braced-only in Carve: a bare `^x^` or `,x,` is literal text.
|
|
607
|
+
BRACED.each do |char, (_, name)|
|
|
608
|
+
delimiter = Regexp.escape(char)
|
|
609
|
+
rule %r/\{(?=#{delimiter}(?!#{delimiter}\}))/ do
|
|
610
|
+
if closer_ahead?(name, /#{delimiter}\}/, @current_stream.charpos + 2)
|
|
611
|
+
enter_braced(name)
|
|
612
|
+
else
|
|
613
|
+
token plain_token
|
|
614
|
+
end
|
|
615
|
+
end
|
|
424
616
|
end
|
|
425
|
-
rule %r
|
|
426
|
-
|
|
617
|
+
rule %r/\{(?=>>)/ do
|
|
618
|
+
if closer_ahead?(:criticnote, /<<\}/, @current_stream.charpos + 3)
|
|
619
|
+
enter_braced(:criticnote, 2)
|
|
620
|
+
else
|
|
621
|
+
token plain_token
|
|
622
|
+
end
|
|
427
623
|
end
|
|
428
|
-
rule %r
|
|
429
|
-
|
|
624
|
+
rule %r/\{(?=%)/ do
|
|
625
|
+
if closer_ahead?(:bracedcomment, /%\}/, @current_stream.charpos + 1)
|
|
626
|
+
token Comment::Preproc
|
|
627
|
+
push :bracedcomment
|
|
628
|
+
push :preprocchar
|
|
629
|
+
else
|
|
630
|
+
token plain_token
|
|
631
|
+
end
|
|
430
632
|
end
|
|
431
633
|
|
|
432
634
|
# An inline footnote carries content; a reference carries a label.
|
|
433
|
-
|
|
434
|
-
|
|
635
|
+
# Both bracketed bodies need a closing `]` before the paragraph ends;
|
|
636
|
+
# an opener without one is text, as it is in the engine.
|
|
637
|
+
rule %r/\^\[/ do
|
|
638
|
+
if (scope = bracket_ahead?)
|
|
639
|
+
token Punctuation
|
|
640
|
+
push :"inlinefootnote_#{scope}"
|
|
641
|
+
else
|
|
642
|
+
token plain_token
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
rule %r/(\[\^)([^\[\]\n]+)(\])/ do
|
|
435
646
|
groups Punctuation, Name::Label, Punctuation
|
|
436
647
|
end
|
|
437
648
|
|
|
438
649
|
# A citation before a link: both open with `[`, and `[@key]` or
|
|
439
650
|
# `[+@key]` would otherwise be read as a link label.
|
|
440
|
-
rule %r/(\[)(\+?@[^\]\n]+)(\])/ do
|
|
651
|
+
rule %r/(\[)(\+?@[^\[\]\n]+)(\])/ do
|
|
441
652
|
groups Punctuation, Name::Variable, Punctuation
|
|
442
653
|
end
|
|
443
654
|
|
|
@@ -468,8 +679,12 @@ module Rouge
|
|
|
468
679
|
|
|
469
680
|
# The `:name[...]` extension form.
|
|
470
681
|
rule %r/(:)([a-zA-Z][\w-]*)(\[)/ do
|
|
471
|
-
|
|
472
|
-
|
|
682
|
+
if (scope = bracket_ahead?)
|
|
683
|
+
groups Punctuation, Name::Function, Punctuation
|
|
684
|
+
push :"rolebody_#{scope}"
|
|
685
|
+
else
|
|
686
|
+
token plain_token
|
|
687
|
+
end
|
|
473
688
|
end
|
|
474
689
|
|
|
475
690
|
# A code callout marker, and a symbol shortcode - whose name may start
|
|
@@ -480,28 +695,12 @@ module Rouge
|
|
|
480
695
|
# An attribute block attached to the construct before it.
|
|
481
696
|
rule ATTRS, Name::Attribute
|
|
482
697
|
|
|
483
|
-
# Bare emphasis delimiters
|
|
484
|
-
#
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
groups Punctuation, Generic::Strong, Punctuation
|
|
490
|
-
end
|
|
491
|
-
rule %r/(\*)(\S(?:[^\n]*?\S)?)(\*)/ do
|
|
492
|
-
groups Punctuation, Generic::Strong, Punctuation
|
|
493
|
-
end
|
|
494
|
-
rule %r/(\/)(\S(?:[^\n]*?\S)?)(\/)/ do
|
|
495
|
-
groups Punctuation, Generic::Emph, Punctuation
|
|
496
|
-
end
|
|
497
|
-
rule %r/(_)(\S(?:[^\n]*?\S)?)(_)/ do
|
|
498
|
-
groups Punctuation, Generic::Emph, Punctuation
|
|
499
|
-
end
|
|
500
|
-
rule %r/(~)(\S(?:[^\n]*?\S)?)(~)/ do
|
|
501
|
-
groups Punctuation, Generic::Deleted, Punctuation
|
|
502
|
-
end
|
|
503
|
-
rule %r/(=)(\S(?:[^\n]*?\S)?)(=)/ do
|
|
504
|
-
groups Punctuation, Generic::Inserted, Punctuation
|
|
698
|
+
# Bare emphasis delimiters, Carve's / * _ ~ = set. Whether one opens is
|
|
699
|
+
# decided in #open_bare, from the characters on both sides and from
|
|
700
|
+
# whether a closer follows on the line; what it encloses is lexed as
|
|
701
|
+
# inline content. `==>` is left whole for the typographic rule.
|
|
702
|
+
rule %r/[*\/_~]|=(?!=>)/ do |m|
|
|
703
|
+
open_bare(m[0])
|
|
505
704
|
end
|
|
506
705
|
|
|
507
706
|
# A mention and a tag, each one token: the sigil is part of the name
|
|
@@ -528,7 +727,7 @@ module Rouge
|
|
|
528
727
|
state :includeparts do
|
|
529
728
|
rule %r/[ \t]+/, Text
|
|
530
729
|
rule %r/\}\}/, Punctuation, :pop!
|
|
531
|
-
rule %r/#[A-Za-
|
|
730
|
+
rule %r/#[A-Za-z0-9_][\w-]*/, Name::Label
|
|
532
731
|
rule %r/(@[A-Za-z_][\w-]*)(:)([^\s}]+)/ do
|
|
533
732
|
groups Name::Attribute, Punctuation, Literal
|
|
534
733
|
end
|
|
@@ -537,24 +736,375 @@ module Rouge
|
|
|
537
736
|
rule %r/\n/, Text, :pop!
|
|
538
737
|
end
|
|
539
738
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
739
|
+
# The body of a bare emphasis run. A same-kind delimiter that cannot
|
|
740
|
+
# close is content (`*a *b* c*` closes at `b*`), and the run ends with
|
|
741
|
+
# its line in case a construct inside took the closer the opener saw.
|
|
742
|
+
#
|
|
743
|
+
# NO LOOKBEHIND IN A RULE. Rouge before 5 scans without a fixed anchor,
|
|
744
|
+
# so a lookbehind cannot see what precedes the scan position; the
|
|
745
|
+
# character before a closer is read in #close_bare instead.
|
|
746
|
+
BARE.each do |char, (name, _, _, _)|
|
|
747
|
+
state name do
|
|
748
|
+
rule(%r/#{Regexp.escape(char)}(?![\p{L}\p{N}])/) { close_bare(/\s/) }
|
|
749
|
+
rule(/$/) { pop! }
|
|
750
|
+
rule(/#{Regexp.escape(char)}/) { token plain_token }
|
|
751
|
+
mixin :inlinecontent
|
|
752
|
+
rule(PLAIN) { token plain_token }
|
|
753
|
+
rule(/./) { token plain_token }
|
|
754
|
+
end
|
|
546
755
|
end
|
|
547
756
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
757
|
+
# Bold italic in one delimiter pair, in both nesting orders. Its own
|
|
758
|
+
# delimiters inside it are content unless they close it.
|
|
759
|
+
# A comment inside it ends at its closer.
|
|
760
|
+
{
|
|
761
|
+
bolditalic: ['\*\/', /\s/, '(?<=\S)\*\/'],
|
|
762
|
+
italicbold: ['\/\*(?!\w)', /[\s*]/, '(?<![\s*])\/\*(?!\w)']
|
|
763
|
+
}.each do |name, (closer, refused_before, closer_in_line)|
|
|
764
|
+
state name do
|
|
765
|
+
rule(/#{closer}/) { close_bare(refused_before) }
|
|
766
|
+
rule(/$/) { pop! }
|
|
767
|
+
rule %r/(%%)((?:(?!#{closer_in_line})[^\n])*)/ do
|
|
768
|
+
groups Comment::Preproc, Comment
|
|
769
|
+
end
|
|
770
|
+
rule(%r/[*\/]/) { token plain_token }
|
|
771
|
+
mixin :inlinecontent
|
|
772
|
+
rule(PLAIN) { token plain_token }
|
|
773
|
+
rule(/./) { token plain_token }
|
|
774
|
+
end
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
# The rest of an opener whose `{` was already emitted.
|
|
778
|
+
state :punctchar do
|
|
779
|
+
rule(/[^\n]/) { token Punctuation; pop! }
|
|
780
|
+
end
|
|
781
|
+
state :preprocchar do
|
|
782
|
+
rule(/[^\n]/) { token Comment::Preproc; pop! }
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
# Braced bodies, flat up to their closer.
|
|
786
|
+
BRACED.each do |char, (tok, name)|
|
|
787
|
+
closer = /#{Regexp.escape(char)}\}/
|
|
788
|
+
state name do
|
|
789
|
+
rule closer, Punctuation, :pop!
|
|
790
|
+
rule(/$/) { pop! }
|
|
791
|
+
rule %r/(?:(?!#{closer})[^\n])+/, tok
|
|
792
|
+
end
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
state :tildebrace do
|
|
796
|
+
rule %r/~>/, Operator, :tildenew
|
|
797
|
+
rule %r/~\}/, Punctuation, :pop!
|
|
798
|
+
rule(/$/) { pop! }
|
|
799
|
+
rule %r/(?:(?!~[>}])[^\n])+/, Generic::Deleted
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
# The new side of a substitution. Its closer ends the whole construct.
|
|
803
|
+
state :tildenew do
|
|
804
|
+
rule(/~\}/) { token Punctuation; pop!(2) }
|
|
805
|
+
rule(/$/) { pop!(2) }
|
|
806
|
+
rule %r/(?:(?!~\})[^\n])+/, Generic::Inserted
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
state :criticcomment do
|
|
810
|
+
rule %r/\#\}/, Punctuation, :pop!
|
|
811
|
+
rule(/$/) { pop! }
|
|
812
|
+
rule %r/(?:(?!\#\})[^\n])+/, Comment
|
|
813
|
+
end
|
|
814
|
+
|
|
815
|
+
state :criticnote do
|
|
816
|
+
rule %r/<<\}/, Punctuation, :pop!
|
|
817
|
+
rule(/$/) { pop! }
|
|
818
|
+
rule %r/(?:(?!<<\})[^\n])+/, Comment
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
state :bracedcomment do
|
|
822
|
+
rule %r/%\}/, Comment::Preproc, :pop!
|
|
823
|
+
rule(/$/) { pop! }
|
|
824
|
+
rule %r/(?:(?!%\})[^\n])+/, Comment
|
|
825
|
+
end
|
|
826
|
+
|
|
827
|
+
# Both bodies end at their bracket, or where their block ends when a
|
|
828
|
+
# construct inside consumed the bracket the opener saw. The newline is
|
|
829
|
+
# left to the block, which decides what the next line opens.
|
|
830
|
+
{ inlinefootnote: Generic::Emph, rolebody: Name::Function }.each do |body, tok|
|
|
831
|
+
BODY_ENDS.each do |scope, ends|
|
|
832
|
+
state :"#{body}_#{scope}" do
|
|
833
|
+
rule %r/\]/, Punctuation, :pop!
|
|
834
|
+
rule(%r/(?=#{ends.source}|\n?\z)/) { pop! }
|
|
835
|
+
# The newline is handed back so the block reads the fence line.
|
|
836
|
+
rule(FENCE_NEXT) do |m|
|
|
837
|
+
if fence_closed_below?(m[1])
|
|
838
|
+
m.pos -= 1
|
|
839
|
+
pop!
|
|
840
|
+
else
|
|
841
|
+
token Text
|
|
842
|
+
end
|
|
843
|
+
end
|
|
844
|
+
mixin :inlinecontent
|
|
845
|
+
rule PLAIN_NO_BRACKET, tok
|
|
846
|
+
rule %r/[^\]\n]/, tok
|
|
847
|
+
rule %r/\n/, Text
|
|
848
|
+
end
|
|
849
|
+
end
|
|
554
850
|
end
|
|
555
851
|
|
|
556
852
|
private
|
|
557
853
|
|
|
854
|
+
# The token a state gives a character that opens nothing.
|
|
855
|
+
PLAIN_TOKENS = {
|
|
856
|
+
heading: Generic::Heading, quoteline: Generic::Emph,
|
|
857
|
+
caption: Generic::Subheading, dlterm: Generic::Heading, bolditalic: Generic::Strong,
|
|
858
|
+
italicbold: Generic::Strong
|
|
859
|
+
}.merge(BARE.values.to_h { |name, tok, _, _| [name, tok] })
|
|
860
|
+
.merge(BODY_ENDS.keys.flat_map do |scope|
|
|
861
|
+
[[:"inlinefootnote_#{scope}", Generic::Emph], [:"rolebody_#{scope}", Name::Function]]
|
|
862
|
+
end.to_h).freeze
|
|
863
|
+
|
|
864
|
+
# Italic or underline inside strong, or the reverse, reads as strong,
|
|
865
|
+
# the token the combined delimiters have always had.
|
|
866
|
+
def plain_token
|
|
867
|
+
name = state.name
|
|
868
|
+
if %i[emph underline].include?(name) && in_state?(:strong)
|
|
869
|
+
Generic::Strong
|
|
870
|
+
else
|
|
871
|
+
PLAIN_TOKENS.fetch(name, Text)
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
# Decide whether the bare delimiter +char+, just consumed, opens a run.
|
|
876
|
+
# It opens when the characters around it allow it and a closer follows
|
|
877
|
+
# on its line; otherwise it is plain text. `/*` and `*/` open bold
|
|
878
|
+
# italic first, with the engine's guards for each order.
|
|
879
|
+
def open_bare(char)
|
|
880
|
+
text = @current_stream.string
|
|
881
|
+
pos = @current_stream.charpos
|
|
882
|
+
before = pos > 1 ? text[pos - 2] : nil
|
|
883
|
+
before = nil if before == "\n"
|
|
884
|
+
after = text[pos]
|
|
885
|
+
name, _, refused_before, refused_after = BARE[char]
|
|
886
|
+
|
|
887
|
+
limit = enclosing_closer(pos)
|
|
888
|
+
|
|
889
|
+
if char == '/' && after == '*' && text[pos + 1].to_s.match?(/\S/) &&
|
|
890
|
+
closer_ahead?(:bolditalic, COMBINED_CLOSERS[:bolditalic], pos + 2, masked: :combined, before: limit)
|
|
891
|
+
token Punctuation
|
|
892
|
+
push :bolditalic
|
|
893
|
+
return push(:punctchar)
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
if char == '*' && after == '/' && !before.to_s.match?(/[\w*\/]/) &&
|
|
897
|
+
text[pos + 1].to_s.match?(/\S/) && !(text[pos + 1] == '*' && text[pos + 2].to_s.match?(/[\s*]/)) &&
|
|
898
|
+
closer_ahead?(:italicbold, COMBINED_CLOSERS[:italicbold], pos + 2, masked: :combined, before: limit)
|
|
899
|
+
token Punctuation
|
|
900
|
+
push :italicbold
|
|
901
|
+
return push(:punctchar)
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
if after && !after.match?(refused_after) && !(before && before.match?(refused_before)) &&
|
|
905
|
+
closer_ahead?(name, BARE_CLOSERS[name], pos, masked: :bare, before: limit)
|
|
906
|
+
token Punctuation
|
|
907
|
+
return push(name)
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
token plain_token
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
BARE_CLOSERS = BARE.to_h do |char, (name, _, _, _)|
|
|
914
|
+
[name, %r/(?<=\S)#{Regexp.escape(char)}(?![\p{L}\p{N}])/]
|
|
915
|
+
end.freeze
|
|
916
|
+
COMBINED_CLOSERS = {
|
|
917
|
+
bolditalic: %r/(?<=\S)\*\//, italicbold: %r/(?<![\s*])\/\*(?!\w)/
|
|
918
|
+
}.freeze
|
|
919
|
+
|
|
920
|
+
# Where the run the scanner is in closes, if it is in one: a run opened
|
|
921
|
+
# inside it must close first, so `*a /b* c/` is strong `a /b`.
|
|
922
|
+
def enclosing_closer(pos)
|
|
923
|
+
name = state.name
|
|
924
|
+
if BARE_CLOSERS.key?(name)
|
|
925
|
+
closer_at(name, BARE_CLOSERS[name], pos, masked: :bare)
|
|
926
|
+
elsif COMBINED_CLOSERS.key?(name)
|
|
927
|
+
closer_at(name, COMBINED_CLOSERS[name], pos, masked: :combined)
|
|
928
|
+
end
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
# A closer candidate inside a bare run, just consumed: it closes unless
|
|
932
|
+
# the character before it is one +refused_before+ matches.
|
|
933
|
+
def close_bare(refused_before)
|
|
934
|
+
text = @current_stream.string
|
|
935
|
+
at = @current_stream.charpos - @current_stream[0].length
|
|
936
|
+
if at.positive? && !text[at - 1].match?(refused_before)
|
|
937
|
+
token Punctuation
|
|
938
|
+
pop!
|
|
939
|
+
else
|
|
940
|
+
token plain_token
|
|
941
|
+
end
|
|
942
|
+
end
|
|
943
|
+
|
|
944
|
+
# Emit the `{` of a braced opener and enter +name+, whose remaining
|
|
945
|
+
# opener characters come next.
|
|
946
|
+
def enter_braced(name, rest = 1)
|
|
947
|
+
token Punctuation
|
|
948
|
+
push name
|
|
949
|
+
rest.times { push :punctchar }
|
|
950
|
+
end
|
|
951
|
+
|
|
952
|
+
# Emit the next +text+ of the input as one +tok+ token.
|
|
953
|
+
def consume_as(tok, text)
|
|
954
|
+
literal = Regexp.new(Regexp.escape(text))
|
|
955
|
+
push do
|
|
956
|
+
rule(literal) { token tok; pop! }
|
|
957
|
+
end
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
# Whether +pattern+ matches on the current line at or after +from+.
|
|
961
|
+
# The matches of one line are found once per pattern and reused, which
|
|
962
|
+
# keeps a line full of unclosed openers linear instead of quadratic.
|
|
963
|
+
# A masked line hides what a bare closer cannot reach into: escapes,
|
|
964
|
+
# code spans, braced inlines, link destinations and autolinks. A bare
|
|
965
|
+
# run's line also ends at a trailing comment, which takes its closer.
|
|
966
|
+
def closer_ahead?(key, pattern, from = @current_stream.charpos, masked: nil, before: nil)
|
|
967
|
+
at = closer_at(key, pattern, from, masked: masked)
|
|
968
|
+
!at.nil? && (before.nil? || at < before)
|
|
969
|
+
end
|
|
970
|
+
|
|
971
|
+
# The position of the first match of +pattern+ on the current line at or
|
|
972
|
+
# after +from+, or nil.
|
|
973
|
+
def closer_at(key, pattern, from = @current_stream.charpos, masked: nil)
|
|
974
|
+
stream = @current_stream
|
|
975
|
+
unless @closers_for.equal?(stream)
|
|
976
|
+
@closers_for = stream
|
|
977
|
+
@closers = {}
|
|
978
|
+
end
|
|
979
|
+
pos = stream.charpos
|
|
980
|
+
entry = @closers[key]
|
|
981
|
+
unless entry && pos >= entry[0] && pos <= entry[1]
|
|
982
|
+
text = stream.string
|
|
983
|
+
first = pos.zero? ? 0 : (text.rindex("\n", pos - 1) || -1) + 1
|
|
984
|
+
last = text.index("\n", pos) || text.length
|
|
985
|
+
line = text[first...last]
|
|
986
|
+
line = mask_opaque(line) if masked
|
|
987
|
+
line = line[0...line.index('%%')] if masked == :bare && line.include?('%%')
|
|
988
|
+
hits = []
|
|
989
|
+
line.scan(pattern) { hits << first + Regexp.last_match.begin(0) }
|
|
990
|
+
entry = @closers[key] = [first, last, hits]
|
|
991
|
+
end
|
|
992
|
+
entry[2].bsearch { |at| at >= from }
|
|
993
|
+
end
|
|
994
|
+
|
|
995
|
+
OPAQUE = /\\.|(?<!`)(`+)(?!`).*?(?<!`)\1(?!`)|\]\([^)\n]*\)|<[^<>\s]+>/.freeze
|
|
996
|
+
|
|
997
|
+
def mask_opaque(line)
|
|
998
|
+
line = line.gsub(OPAQUE) { |span| "\u0001" * span.length }
|
|
999
|
+
# Braced inlines pair up in code: a regex scanning from every unclosed
|
|
1000
|
+
# `{*` to the end of the line is quadratic. Once a kind has no closer
|
|
1001
|
+
# left, no later opener of that kind can have one either.
|
|
1002
|
+
'*/_^,=+~#%-'.each_char do |kind|
|
|
1003
|
+
at = 0
|
|
1004
|
+
while (open = line.index("{#{kind}", at))
|
|
1005
|
+
close = line.index("#{kind}}", open + 3)
|
|
1006
|
+
break unless close
|
|
1007
|
+
|
|
1008
|
+
line[open...close + 2] = "\u0001" * (close + 2 - open)
|
|
1009
|
+
at = close + 2
|
|
1010
|
+
end
|
|
1011
|
+
end
|
|
1012
|
+
line
|
|
1013
|
+
end
|
|
1014
|
+
|
|
1015
|
+
# The scope of the block a bracketed body opens in, when a `]` outside
|
|
1016
|
+
# escapes and code spans follows before that block ends; nil otherwise.
|
|
1017
|
+
# The brackets of one block are found in one pass and reused, so a run
|
|
1018
|
+
# of unclosed openers does not rescan it once each.
|
|
1019
|
+
def bracket_ahead?
|
|
1020
|
+
stream = @current_stream
|
|
1021
|
+
pos = stream.charpos
|
|
1022
|
+
scope = body_scope
|
|
1023
|
+
@brackets = {} unless @brackets_for.equal?(stream)
|
|
1024
|
+
@brackets_for = stream
|
|
1025
|
+
entry = @brackets[scope]
|
|
1026
|
+
unless entry && pos < entry[0]
|
|
1027
|
+
text = stream.string
|
|
1028
|
+
last = body_end(text, pos, scope)
|
|
1029
|
+
hits = []
|
|
1030
|
+
text[pos...last].scan(BRACKET_SCAN) { hits << pos + $~.begin(0) if $~[0] == ']' }
|
|
1031
|
+
entry = @brackets[scope] = [last, hits]
|
|
1032
|
+
end
|
|
1033
|
+
entry[1].bsearch { |at| at >= pos } ? scope : nil
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
# Where a body opened at +pos+ must end: the first block end, or the
|
|
1037
|
+
# first closed fence, after it.
|
|
1038
|
+
def body_end(text, pos, scope)
|
|
1039
|
+
ends = BODY_ENDS[scope]
|
|
1040
|
+
last = text.index(ends, pos) || text.length
|
|
1041
|
+
return last if scope == :line
|
|
1042
|
+
|
|
1043
|
+
at = pos
|
|
1044
|
+
while (fence = text.index(FENCE_NEXT, at)) && fence < last
|
|
1045
|
+
return fence if fence_closed_below?($~[1], fence + 1)
|
|
1046
|
+
|
|
1047
|
+
at = fence + 1
|
|
1048
|
+
end
|
|
1049
|
+
last
|
|
1050
|
+
end
|
|
1051
|
+
|
|
1052
|
+
BRACKET_SCAN = /\\.|(?<!`)(`+)(?!`).*?(?<!`)\1(?!`)|\]/.freeze
|
|
1053
|
+
|
|
1054
|
+
# The scope of the nearest enclosing block state. An indented paragraph
|
|
1055
|
+
# sits in a container, so the next list marker ends it as it ends an item.
|
|
1056
|
+
def body_scope
|
|
1057
|
+
stack.reverse_each do |s|
|
|
1058
|
+
scope = BODY_SCOPE[s.name]
|
|
1059
|
+
next unless scope
|
|
1060
|
+
return :item if scope == :para && @indented
|
|
1061
|
+
|
|
1062
|
+
return scope
|
|
1063
|
+
end
|
|
1064
|
+
:para
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
# Whether +closer+ matches a line below the current position. A miss
|
|
1068
|
+
# is remembered per +key+, because every later opener of the same kind
|
|
1069
|
+
# would miss too - without that, a run of unclosed openers rescans the
|
|
1070
|
+
# rest of the document once each.
|
|
1071
|
+
def closer_below?(closer, key, from = @current_stream.charpos)
|
|
1072
|
+
stream = @current_stream
|
|
1073
|
+
@closer_misses = {} unless @closer_misses_for.equal?(stream)
|
|
1074
|
+
@closer_misses_for = stream
|
|
1075
|
+
return false if @closer_misses[key]
|
|
1076
|
+
|
|
1077
|
+
found = stream.string.index(closer, from)
|
|
1078
|
+
@closer_misses[key] = true unless found
|
|
1079
|
+
!found.nil?
|
|
1080
|
+
end
|
|
1081
|
+
|
|
1082
|
+
# Enter the state of a block whose opener just matched, or none for an
|
|
1083
|
+
# opener that is a single token. After a blockquote marker the opener
|
|
1084
|
+
# replaces the quote's start state, and a single-token opener hands the
|
|
1085
|
+
# rest of the line to quoted text.
|
|
1086
|
+
def open_block(name = nil, quoted = name || :quoteline)
|
|
1087
|
+
if state?(:quotestart)
|
|
1088
|
+
goto quoted
|
|
1089
|
+
elsif name
|
|
1090
|
+
push name
|
|
1091
|
+
end
|
|
1092
|
+
end
|
|
1093
|
+
|
|
1094
|
+
def continue_paragraph(name)
|
|
1095
|
+
@lone_image = false
|
|
1096
|
+
goto name
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1099
|
+
# Whether a fence opened by +run+ on the current line is closed further
|
|
1100
|
+
# down. Only a closed fence interrupts a paragraph.
|
|
1101
|
+
def fence_closed_below?(run, line_start = @current_stream.charpos)
|
|
1102
|
+
text = @current_stream.string
|
|
1103
|
+
line_end = text.index("\n", line_start) || text.length
|
|
1104
|
+
closer = %r/^[ \t]*#{Regexp.escape(run[0])}{#{run.length},}[ \t]*$/
|
|
1105
|
+
closer_below?(closer, run, line_end)
|
|
1106
|
+
end
|
|
1107
|
+
|
|
558
1108
|
# Lex the tail of a fence or container opener line without leaving the
|
|
559
1109
|
# current state: a quoted title, a bracketed label and an attribute
|
|
560
1110
|
# block, each as its own token.
|
|
@@ -590,6 +1140,8 @@ module Rouge
|
|
|
590
1140
|
sublexer = fence_lexer(language)
|
|
591
1141
|
sublexer&.reset!
|
|
592
1142
|
|
|
1143
|
+
return push_quoted_fence(char, width, sublexer) if state?(:quotestart)
|
|
1144
|
+
|
|
593
1145
|
push do
|
|
594
1146
|
rule closer, Punctuation, :pop!
|
|
595
1147
|
if sublexer
|
|
@@ -611,6 +1163,35 @@ module Rouge
|
|
|
611
1163
|
end
|
|
612
1164
|
end
|
|
613
1165
|
|
|
1166
|
+
# A fence opened after a blockquote marker. Every payload line carries
|
|
1167
|
+
# the quote prefix at the opener's depth, which is markup rather than
|
|
1168
|
+
# payload; a line with fewer markers leaves that quote and so ends the
|
|
1169
|
+
# fence too, while a deeper marker is payload.
|
|
1170
|
+
def push_quoted_fence(char, width, sublexer)
|
|
1171
|
+
pop!
|
|
1172
|
+
text = @current_stream.string
|
|
1173
|
+
pos = @current_stream.charpos
|
|
1174
|
+
line = text[((text.rindex("\n", pos - 1) || -1) + 1)...pos]
|
|
1175
|
+
depth = line[/\A#{QUOTE_PREFIX}/].to_s.count('>')
|
|
1176
|
+
prefix = %r/(?:[ \t]*>(?=[ \t]|$)){#{depth}}[ \t]?/
|
|
1177
|
+
|
|
1178
|
+
push do
|
|
1179
|
+
rule %r/^(#{prefix})([ \t]*#{char}{#{width},})([ \t]*)$/ do
|
|
1180
|
+
groups Punctuation, Punctuation, Text
|
|
1181
|
+
pop!
|
|
1182
|
+
end
|
|
1183
|
+
rule(%r/^(?!#{prefix})/) { pop! }
|
|
1184
|
+
rule %r/^#{prefix}/, Punctuation
|
|
1185
|
+
if sublexer
|
|
1186
|
+
rule %r/\n/, Text
|
|
1187
|
+
rule(%r/[^\n]+\n?/) { |m| delegate sublexer, m[0] }
|
|
1188
|
+
else
|
|
1189
|
+
rule %r/<\d+>/, Name::Constant
|
|
1190
|
+
rule %r/[^\n<]+|<|\n/, Str::Backtick
|
|
1191
|
+
end
|
|
1192
|
+
end
|
|
1193
|
+
end
|
|
1194
|
+
|
|
614
1195
|
# A lexer for the fence's declared language, or nil when there is no
|
|
615
1196
|
# language, none is known, or the name is ambiguous. Guessing is
|
|
616
1197
|
# deliberately not attempted: a wrong guess colours a sample as the wrong
|
data/lib/rouge-carve.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'rouge'
|
|
4
|
+
require_relative 'rouge-carve/version'
|
|
4
5
|
require_relative 'rouge/lexers/carve'
|
|
5
6
|
|
|
6
7
|
# A Rouge lexer for the Carve markup language.
|
|
@@ -9,5 +10,4 @@ require_relative 'rouge/lexers/carve'
|
|
|
9
10
|
# registers it, so `Rouge::Lexer.find('carve')` and
|
|
10
11
|
# `Rouge::Lexer.guess(filename: 'x.crv')` work from that point on.
|
|
11
12
|
module RougeCarve
|
|
12
|
-
VERSION = '0.1.1'
|
|
13
13
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rouge-carve
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mark Scherer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rouge
|
|
@@ -16,7 +16,7 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '3.
|
|
19
|
+
version: '3.30'
|
|
20
20
|
- - "<"
|
|
21
21
|
- !ruby/object:Gem::Version
|
|
22
22
|
version: '6.0'
|
|
@@ -26,7 +26,7 @@ dependencies:
|
|
|
26
26
|
requirements:
|
|
27
27
|
- - ">="
|
|
28
28
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '3.
|
|
29
|
+
version: '3.30'
|
|
30
30
|
- - "<"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
32
|
version: '6.0'
|
|
@@ -44,6 +44,7 @@ files:
|
|
|
44
44
|
- LICENSE
|
|
45
45
|
- README.md
|
|
46
46
|
- lib/rouge-carve.rb
|
|
47
|
+
- lib/rouge-carve/version.rb
|
|
47
48
|
- lib/rouge/demos/carve
|
|
48
49
|
- lib/rouge/lexers/carve.rb
|
|
49
50
|
homepage: https://github.com/markup-carve/rouge-carve
|