coradoc-adoc 2.0.34 → 2.0.36
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/grammar/coradoc-adoc.artifact.json +1 -0
- data/grammar/coradoc-adoc.parg +396 -0
- data/lib/coradoc/asciidoc/formatter.rb +75 -0
- data/lib/coradoc/asciidoc/lint/document_title.rb +26 -0
- data/lib/coradoc/asciidoc/lint/duplicate_ids.rb +36 -0
- data/lib/coradoc/asciidoc/lint/empty_section.rb +45 -0
- data/lib/coradoc/asciidoc/lint/heading_levels.rb +44 -0
- data/lib/coradoc/asciidoc/lint.rb +27 -0
- data/lib/coradoc/asciidoc/model/text_element.rb +18 -5
- data/lib/coradoc/asciidoc/parser/base.rb +28 -11
- data/lib/coradoc/asciidoc/parser/citation.rb +10 -0
- data/lib/coradoc/asciidoc/parser/document_attributes.rb +6 -1
- data/lib/coradoc/asciidoc/parser/inline.rb +26 -19
- data/lib/coradoc/asciidoc/parser/list.rb +25 -8
- data/lib/coradoc/asciidoc/parser/paragraph.rb +4 -2
- data/lib/coradoc/asciidoc/parser/positioned_string.rb +40 -0
- data/lib/coradoc/asciidoc/parser/rule_dispatcher.rb +3 -2
- data/lib/coradoc/asciidoc/parser/tree_materializer.rb +28 -0
- data/lib/coradoc/asciidoc/parser.rb +2 -0
- data/lib/coradoc/asciidoc/serializer/serializers/block/source_code.rb +3 -1
- data/lib/coradoc/asciidoc/serializer/serializers/list/item.rb +4 -0
- data/lib/coradoc/asciidoc/serializer/serializers/table.rb +8 -2
- data/lib/coradoc/asciidoc/serializer/serializers/text_element.rb +3 -1
- data/lib/coradoc/asciidoc/transform/from_core_model/list_building.rb +86 -0
- data/lib/coradoc/asciidoc/transform/from_core_model.rb +100 -29
- data/lib/coradoc/asciidoc/transform/frontmatter_attribute_map.rb +3 -2
- data/lib/coradoc/asciidoc/transform/text_extract_visitor.rb +5 -2
- data/lib/coradoc/asciidoc/transform/to_core_model.rb +12 -2
- data/lib/coradoc/asciidoc/transform.rb +1 -0
- data/lib/coradoc/asciidoc/transformer/source_line_extractor.rb +1 -1
- data/lib/coradoc/asciidoc/transformer/table_cell_builder.rb +18 -7
- data/lib/coradoc/asciidoc/version.rb +1 -1
- data/lib/coradoc/asciidoc.rb +12 -0
- data/lib/coradoc/util/asciidoc.rb +56 -0
- metadata +16 -5
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# Coradoc AsciiDoc grammar — PARG port (parsanol-ruby#129 acceptance).
|
|
2
|
+
#
|
|
3
|
+
# Translation of coradoc-adoc/lib/coradoc/asciidoc/parser (Ruby Parsanol
|
|
4
|
+
# DSL) to .parg. Ported incrementally; each rule family cites its Ruby
|
|
5
|
+
# source. The motivating runtime-state patterns (fence capture + dynamic
|
|
6
|
+
# close, from Parser::Block#block_style / #markdown_code_block) map onto
|
|
7
|
+
# set/state exactly as designed in parsanol-ruby#129.
|
|
8
|
+
#
|
|
9
|
+
# Increment 1 scope: terminals, attribute lists (faithful capture
|
|
10
|
+
# shapes), element ids, block titles, block headers, all delimited
|
|
11
|
+
# block styles (verbatim content), comments/tags/includes, document
|
|
12
|
+
# dispatch subset with the unparsed catch-all.
|
|
13
|
+
|
|
14
|
+
grammar CoradocAdoc version "0.2.0" {
|
|
15
|
+
|
|
16
|
+
# ---- text.rb terminals ----
|
|
17
|
+
any_char = %x01-10FFFF
|
|
18
|
+
space = 1*" "
|
|
19
|
+
sp = [1*" "]
|
|
20
|
+
text = 1*(!"\n" any_char)
|
|
21
|
+
line_ending = ("\n" / "\r\n")
|
|
22
|
+
newline = 1*("\n" / "\r\n")
|
|
23
|
+
newline_single = ("\n" / "\r\n")
|
|
24
|
+
empty_line = "\n"
|
|
25
|
+
keyword = 1*(%x41-5A / %x61-7A / %x30-39 / "_" / "-" / "." / ",")
|
|
26
|
+
file_path = 1*(!"[" any_char)
|
|
27
|
+
|
|
28
|
+
# ---- attribute_list.rb (faithful capture shapes) ----
|
|
29
|
+
named_key = (1*(%x41-5A / %x61-7A / %x30-39 / "_" / "-")) as named_key
|
|
30
|
+
|
|
31
|
+
named_value_sq = %x27 (1*(!(%x27) any_char)) %x27
|
|
32
|
+
named_value_dq = %x22 (1*(!(%x22) any_char)) %x22
|
|
33
|
+
named_value_noquote = 1*(!(%x5D / %x2C) any_char)
|
|
34
|
+
named_value = ((named_value_sq / named_value_dq / named_value_noquote)) as named_value
|
|
35
|
+
|
|
36
|
+
named_attribute = ((named_key [space] "=" [space] named_value [space])) as named
|
|
37
|
+
|
|
38
|
+
positional_value_sq = %x27 (1*(!(%x27) any_char)) %x27
|
|
39
|
+
positional_value_dq = %x22 (1*(!(%x22) any_char)) %x22
|
|
40
|
+
positional_value_unquoted = 1*(!(%x5D / %x2C / %x3D) any_char)
|
|
41
|
+
positional_attribute = (((positional_value_sq / positional_value_dq / positional_value_unquoted) !(%x3D))) as positional
|
|
42
|
+
|
|
43
|
+
named_many = ((1*1(named_attribute)) [*(%x2C [space] named_attribute)])
|
|
44
|
+
positional_one_named_many = ((1*1(positional_attribute)) 1*(%x2C [space] named_attribute))
|
|
45
|
+
positional_many_named_many = ((1*1(positional_attribute)) 1*(%x2C [space] positional_attribute) 1*(%x2C [space] named_attribute))
|
|
46
|
+
positional_many = ((1*1(positional_attribute)) [*(%x2C [space] positional_attribute)])
|
|
47
|
+
positional_zero_or_one = 0*1(positional_attribute)
|
|
48
|
+
|
|
49
|
+
attribute_list = (&(%x5B) %x5B !(%x5B) (((((named_many / positional_one_named_many / positional_many_named_many / positional_many / positional_zero_or_one))) as attribute_array)) as attribute_list %x5D)
|
|
50
|
+
|
|
51
|
+
# ---- content.rb: element_id / block_header.rb ----
|
|
52
|
+
element_id = ((("[[" (keyword) as id "]]") / ("[#" (keyword) as id "]")) newline)
|
|
53
|
+
|
|
54
|
+
attribute_blocks = (1*((attribute_list newline))) as attribute_list
|
|
55
|
+
|
|
56
|
+
# ---- block.rb: delimiters + title ----
|
|
57
|
+
block_delim_run = 4*((%x2A / %x3D / %x5F / %x2B / %x2E / %x2D))
|
|
58
|
+
block_delimiter = ((block_delim_run / (2%x2D) / (3*%x60)) newline)
|
|
59
|
+
block_title = (!(block_delimiter) %x2E !(space) (text) as title newline)
|
|
60
|
+
|
|
61
|
+
block_header = [(block_title)] [(element_id)] [(attribute_blocks)]
|
|
62
|
+
|
|
63
|
+
# ---- text.rb: comment_line / tag / comment_block / include ----
|
|
64
|
+
tag_shape = ("//" !( "/") [1*" "] ("tag" / "end") "::" !( ":") 1*(!"[" any_char))
|
|
65
|
+
|
|
66
|
+
comment_line_content = (!(tag_shape) "//" !("/") [1*" "] *(!"\n" any_char) line_ending)
|
|
67
|
+
|
|
68
|
+
comment_line = (!(tag_shape) ("//" !("/") [1*" "] [(text) as comment_text line_ending as line_break])) as comment_line
|
|
69
|
+
|
|
70
|
+
tag = (("//" !("/") [1*" "] ("tag" / "end") as prefix "::" !(":") ((1*(!"[" any_char))) as name attribute_list ([newline_single] as line_break))) as tag
|
|
71
|
+
|
|
72
|
+
comment_block = (("////" line_ending (*(!((line_ending "////")) any_char)) as comment_text line_ending "////")) as comment_block
|
|
73
|
+
|
|
74
|
+
page_break = ("<<<" line_ending)
|
|
75
|
+
|
|
76
|
+
include_directive = (("include::" file_path as path attribute_list [newline] as line_break)) as include
|
|
77
|
+
|
|
78
|
+
# ---- content.rb text_line(verbatim:) : text + own line ending ----
|
|
79
|
+
# tl >> (line_ending.as(:line_break) | eof?) — the newline rides INSIDE
|
|
80
|
+
# the same iteration hash (parslet sequence merge), matching the DSL.
|
|
81
|
+
verbatim_line = (((1*(!"\n" any_char)) as text) ((line_ending) as line_break / (!(any_char))))
|
|
82
|
+
|
|
83
|
+
# ---- block.rb: delimited blocks (the #129 state patterns) ----
|
|
84
|
+
# markdown_code_block: 3+ backticks, optional language, closes with the
|
|
85
|
+
# SAME fence run (Parser::Block#markdown_code_block).
|
|
86
|
+
state md_fence: string
|
|
87
|
+
md_lang = [*" " (1*(%x41-5A / %x61-7A / %x30-39 / "_" / "+" / "-" / ".")) as language]
|
|
88
|
+
md_close = (state md_fence [space] newline)
|
|
89
|
+
md_content = (!(md_close) verbatim_line)
|
|
90
|
+
md_break = (empty_line as line_break)
|
|
91
|
+
markdown_code_block = (block_header ((set md_fence = (3*%x60)) as delimiter md_lang newline (1*(md_content / md_break)) as lines [space] (state md_fence) [space] newline))
|
|
92
|
+
|
|
93
|
+
# block_style: 4+ identical fence chars, closes with the exact same run.
|
|
94
|
+
state eq_fence: string
|
|
95
|
+
state dash_fence: string
|
|
96
|
+
state us_fence: string
|
|
97
|
+
state ast_fence: string
|
|
98
|
+
state plus_fence: string
|
|
99
|
+
state dot_fence: string
|
|
100
|
+
|
|
101
|
+
eq_content = (!(((state eq_fence) newline)) verbatim_line)
|
|
102
|
+
dash_content = (!(((state dash_fence) newline)) verbatim_line)
|
|
103
|
+
us_content = (!(((state us_fence) newline)) verbatim_line)
|
|
104
|
+
ast_content = (!(((state ast_fence) newline)) verbatim_line)
|
|
105
|
+
plus_content = (!(((state plus_fence) newline)) verbatim_line)
|
|
106
|
+
dot_content = (!(((state dot_fence) newline)) verbatim_line)
|
|
107
|
+
vbreak = (empty_line as line_break)
|
|
108
|
+
|
|
109
|
+
# superseded by depth-unrolled forms: example_block = (block_header ((set eq_fence = (4*%x3D)) as delimiter newline (1*(eq_content / vbreak)) as lines (state eq_fence) newline))
|
|
110
|
+
source_block = (block_header ((set dash_fence = (4*%x2D)) as delimiter newline (1*(dash_content / vbreak)) as lines (state dash_fence) newline))
|
|
111
|
+
# superseded by depth-unrolled forms: quote_block = (block_header ((set us_fence = (4*%x5F)) as delimiter newline (1*(us_content / vbreak)) as lines (state us_fence) newline))
|
|
112
|
+
# superseded by depth-unrolled forms: sidebar_block = (block_header ((set ast_fence = (4*%x2A)) as delimiter newline (1*(ast_content / vbreak)) as lines (state ast_fence) newline))
|
|
113
|
+
pass_block = (block_header ((set plus_fence = (4*%x2B)) as delimiter newline (1*(plus_content / vbreak)) as lines (state plus_fence) newline))
|
|
114
|
+
literal_block = (block_header ((set dot_fence = (4*%x2E)) as delimiter newline (1*(dot_content / vbreak)) as lines (state dot_fence) newline))
|
|
115
|
+
|
|
116
|
+
# block_style_exact: open block — EXACTLY 2 dashes
|
|
117
|
+
state open_fence: string
|
|
118
|
+
open_content = (!(((state open_fence) newline)) verbatim_line)
|
|
119
|
+
# superseded by depth-unrolled forms: open_block = (block_header ((set open_fence = (2%x2D)) as delimiter newline (1*(open_content / vbreak)) as lines (state open_fence) newline))
|
|
120
|
+
|
|
121
|
+
# superseded by depth-unrolled block_3/block_2/block_1
|
|
122
|
+
|
|
123
|
+
# ---- inline.rb (INLINE_RULE_ORDER is load-bearing) ----
|
|
124
|
+
typographic_quote = ((%x22 %x60) / (%x60 %x22) / (%x27 %x60) / (%x60 %x27)) as typographic_quote
|
|
125
|
+
|
|
126
|
+
attribute_reference = (&(%x7B) %x7B ((1*(%x41-5A / %x61-7A / %x30-39 / "_" / "-"))) as attribute_reference %x7D)
|
|
127
|
+
|
|
128
|
+
uri_char = (%x41-5A / %x61-7A / %x30-39 / "_" / "." / "-" / ":" / "/" / "&" / "?" / "=" / "+" / "," / "%" / "#" / "~" / ";")
|
|
129
|
+
|
|
130
|
+
link_tail = (((1*uri_char)) as path [(%x5B ((1*(!(%x5D) any_char))) as text %x5D)])
|
|
131
|
+
link = ((&(("http" / "https" / "ftp")) link_tail) as link / (&("link:") "link:" link_tail) as link)
|
|
132
|
+
|
|
133
|
+
inline_image = (&("image:") "image:" !(":") ((1*uri_char)) as path [attribute_list]) as inline_image
|
|
134
|
+
|
|
135
|
+
inline_passthrough = ((("+++" ((1*(!("+++") !(%x0A) any_char))) as raw "+++")) as inline_passthrough / (&("pass:") "pass:" *(%x41-5A / %x61-7A / %x2B / %x2C) %x5B ((1*(!("]]") !(%x5D / %x0A) any_char))) as raw %x5D) as inline_passthrough)
|
|
136
|
+
|
|
137
|
+
underline = (attribute_list %x5B ".underline" %x5D as role %x23 ((1*(!(%x23 / %x0A) any_char))) as text %x23) as underline
|
|
138
|
+
small = (attribute_list %x5B ".small" %x5D as role %x23 ((1*(!(%x23 / %x0A) any_char))) as text %x23) as small
|
|
139
|
+
|
|
140
|
+
hard_line_break = (((" +" %x0A) / (%x5C %x0A))) as hard_line_break
|
|
141
|
+
|
|
142
|
+
term_type = ("term" / "alt" / "deprecated" / "domain") as term_type
|
|
143
|
+
stem_type = ("stem" / "latexmath" / "asciimath") as stem_type
|
|
144
|
+
|
|
145
|
+
escaped_xref = (%x5C (%x3C %x3C) as text)
|
|
146
|
+
# \X (X punctuation, minus < for escaped_xref and \ itself)
|
|
147
|
+
# yields the literal X without firing any inline rule (#281)
|
|
148
|
+
escape_char = (%x5C ((%x21-2f / %x3a-3b / %x3d-40 / %x5b / %x5d-60 / %x7b-7e)) as text)
|
|
149
|
+
cross_reference = (((%x3C %x3C) ((1*(!((%x2C / %x3E)) any_char)) as href) [((%x2C) ((*(!((%x3E)) any_char)) as text))] (%x3E %x3E)) as cross_reference)
|
|
150
|
+
|
|
151
|
+
term_inline = (term_type ":[" ((1*(!(%x5D) any_char))) as term %x5D)
|
|
152
|
+
term_inline2 = (%x5B term_type %x5D %x23 ((1*(!(%x23) any_char))) as term2 %x23)
|
|
153
|
+
|
|
154
|
+
macro_kw = ("term" / "alt" / "deprecated" / "domain" / "stem" / "latexmath" / "asciimath")
|
|
155
|
+
macro_content = 1*((!(macro_kw) !(%x5D) any_char) / (macro_kw %x3A %x5B macro_content %x5D))
|
|
156
|
+
footnote = ("footnote:" [(keyword) as id] %x5B ((macro_content)) as footnote %x5D)
|
|
157
|
+
|
|
158
|
+
stem = (stem_type ":[" ((1*(!(%x5D) any_char))) as content %x5D) as stem
|
|
159
|
+
|
|
160
|
+
# constrained/unstrained marks — content keeps Ruby's .as(:text).repeat(1,1)
|
|
161
|
+
# array wrap (1*1) except where Ruby uses a bare .as
|
|
162
|
+
bold_constrained = ((!< WORD %x2A !(%x2A) (1*1(((1*(!(%x2A / %x0A) any_char))) as text)) %x2A !(%x2A) !(WORD)) !(%x0A %x0A)) as bold_constrained
|
|
163
|
+
bold_unconstrained = ((%x2A %x2A (1*1(((1*(!(%x2A) any_char))) as text)) %x2A %x2A)) as bold_unconstrained
|
|
164
|
+
span_constrained = (attribute_list %x23 ((1*(!(%x23 / %x0A) any_char))) as text %x23 !(%x23) !(WORD)) as span_constrained
|
|
165
|
+
span_unconstrained = (attribute_list %x23 %x23 ((1*(!(%x23 / %x0A) any_char))) as text %x23 %x23) as span_unconstrained
|
|
166
|
+
italic_constrained = ((!< WORD %x5F !(%x5F) (1*1(((1*(!(%x5F / %x0A) any_char))) as text)) %x5F !(%x5F) !(WORD))) as italic_constrained
|
|
167
|
+
italic_unconstrained = ((%x5F %x5F (1*1(((1*(!(%x5F) any_char))) as text)) %x5F %x5F)) as italic_unconstrained
|
|
168
|
+
highlight_constrained = ((!< WORD %x23 !(%x23) (1*1(((1*(!(%x23 / %x0A) any_char))) as text)) %x23 !(%x23) !(WORD))) as highlight_constrained
|
|
169
|
+
highlight_unconstrained = ((%x23 %x23 (1*1(((1*(!(%x23) any_char))) as text)) %x23 %x23)) as highlight_unconstrained
|
|
170
|
+
monospace_constrained = ((!< WORD %x60 !(%x60) (1*1((1*((!(%x60 / %x0A) any_char) / (%x60 %x60))) as text)) %x60 !(%x60) !(WORD))) as monospace_constrained
|
|
171
|
+
monospace_unconstrained = ((%x60 %x60 (1*1(((1*(!(%x60) any_char))) as text)) %x60 %x60)) as monospace_unconstrained
|
|
172
|
+
superscript = (%x5E (1*1(((1*(!(%x5E / %x0A) any_char))) as text)) %x5E) as superscript
|
|
173
|
+
subscript = (%x7E (1*1(((1*(!(%x7E / %x0A) any_char))) as text)) %x7E) as subscript
|
|
174
|
+
|
|
175
|
+
inline = (typographic_quote / bold_unconstrained / bold_constrained / span_unconstrained / span_constrained / italic_unconstrained / italic_constrained / highlight_unconstrained / highlight_constrained / monospace_unconstrained / monospace_constrained / superscript / subscript / attribute_reference / escaped_xref / escape_char / cross_reference / term_inline / term_inline2 / footnote / stem / link / inline_image / inline_passthrough / underline / small / hard_line_break)
|
|
176
|
+
|
|
177
|
+
# text.rb/inline.rb text layers
|
|
178
|
+
inline_chars = ((%x5B / %x2A / %x23 / %x5F / %x7B / %x3C / %x5E / %x7E / %x60 / %x5C) / typographic_quote / "https" / "http" / "link:" / "image:" / "+++" / "pass:" / term_type / "footnote" / stem_type / (%x5C %x3C %x3C) / hard_line_break)
|
|
179
|
+
text_formatted = (&(inline_chars) inline)
|
|
180
|
+
text_unformatted = 1*(!("\\" %x3C %x3C) !(inline) !(%x0A) any_char)
|
|
181
|
+
text_any = ((2*((text_formatted / (text_unformatted) as text))) / (1*1(text_formatted)) / text_unformatted)
|
|
182
|
+
|
|
183
|
+
# ---- paragraph.rb ----
|
|
184
|
+
# list.absent? from the DSL's line_not_text? — ported with the
|
|
185
|
+
# empty-item arm (#139): a bare marker line must not be eaten as a
|
|
186
|
+
# continuation line of the previous item.
|
|
187
|
+
literal_space = 1*(%x20 / %x09)
|
|
188
|
+
section_prefix = (%x3D *(%x3D) (!(%x0A) any_char))
|
|
189
|
+
list_prefix = (((%x2A 1*5(%x2A)) / (%x2E 1*5(%x2E))) " ")
|
|
190
|
+
# Any-depth list markers stop continuation text: a nested list
|
|
191
|
+
# (`** b` under `* a`) attaches as `nested`, not continuation.
|
|
192
|
+
line_not_text = (!(list_1) !(ulist_item_1) !(list_2) !(list_3) !(list_4) !(list_5) !(list_prefix) !(list_continuation) !((attribute_list newline)) !(block_delimiter) !((%x7C %x3D %x3D %x3D newline)) !(element_id) !(comment_line) !(tag) !(section_prefix))
|
|
193
|
+
asciidoc_char = (%x2A / %x5F / %x3A / %x2B / %x3D / %x2D)
|
|
194
|
+
asciidoc_char_with_id = (asciidoc_char / (%x5B %x23) / (%x5B %x5B))
|
|
195
|
+
element_id_inline = ((%x5B %x5B (keyword) as id %x5D %x5D) / (%x5B %x23 (keyword) as id %x5D))
|
|
196
|
+
# paragraph_text_line(0|false|true): guard, text_any, then the
|
|
197
|
+
# three-state newline tail (0 = eof only; false = single; true = many)
|
|
198
|
+
# Ruby's (a.absent? | element_id_inline | line_not_text?) reduces under
|
|
199
|
+
# the outer line_not_text guard: arms 1/3 always hold there, and
|
|
200
|
+
# element_id_inline implies asciidoc_char_with_id — so it's just an
|
|
201
|
+
# optional inline id (keeps zero-width branches out of the alternation)
|
|
202
|
+
ptl_guard = (line_not_text [(element_id_inline) [*literal_space]])
|
|
203
|
+
paragraph_text_line_0 = (ptl_guard ((text_any)) as text (!(any_char)))
|
|
204
|
+
paragraph_text_line_s = (ptl_guard ((text_any)) as text ((line_ending) as line_break / (!(any_char))))
|
|
205
|
+
paragraph_text_line_m = (ptl_guard ((text_any)) as text ((newline) as line_break / (!(any_char))))
|
|
206
|
+
paragraph = (block_header ((((1*1(paragraph_text_line_0)) ((newline) as line_break / (!(any_char)))) / ((1*(paragraph_text_line_s)) [(1*1(paragraph_text_line_m) ((newline) as line_break / (!(any_char))))]))) as lines *(newline)) as paragraph
|
|
207
|
+
|
|
208
|
+
# ---- admonition.rb ----
|
|
209
|
+
admonition_type = (%i"note" / %i"tip" / %i"warning" / %i"caution" / %i"important" / %i"editor" / %i"todo" / %i"admonition")
|
|
210
|
+
admonition_line = ((admonition_type as admonition_type) ": " ((1*((text_any as text) (line_ending as line_break))) as content))
|
|
211
|
+
|
|
212
|
+
# ---- list.rb (nesting levels unrolled 1..5) ----
|
|
213
|
+
list_continuation = ("+" %x0A)
|
|
214
|
+
list_item_separator = (*(comment_line_content / empty_line))
|
|
215
|
+
ulist_marker_1 = ( ((1%x2A !(%x2A) !("===")) / (%x2D !(%x2D) !(%x30-39))))
|
|
216
|
+
olist_marker_1 = ( 1%x2E !(%x2E) !((*((%x30-39 / %x2E / %x3C / %x3E / %x5E) / %x2B)) "|"))
|
|
217
|
+
list_marker_1 = (olist_marker_1 / ulist_marker_1)
|
|
218
|
+
ulist_marker_2 = ([*literal_space] (2%x2A !(%x2A) !("===")))
|
|
219
|
+
olist_marker_2 = ([*literal_space] 2%x2E !(%x2E) !((*((%x30-39 / %x2E / %x3C / %x3E / %x5E) / %x2B)) "|"))
|
|
220
|
+
list_marker_2 = (olist_marker_2 / ulist_marker_2)
|
|
221
|
+
ulist_marker_3 = ([*literal_space] (3%x2A !(%x2A) !("===")))
|
|
222
|
+
olist_marker_3 = ([*literal_space] 3%x2E !(%x2E) !((*((%x30-39 / %x2E / %x3C / %x3E / %x5E) / %x2B)) "|"))
|
|
223
|
+
list_marker_3 = (olist_marker_3 / ulist_marker_3)
|
|
224
|
+
ulist_marker_4 = ([*literal_space] (4%x2A !(%x2A) !("===")))
|
|
225
|
+
olist_marker_4 = ([*literal_space] 4%x2E !(%x2E) !((*((%x30-39 / %x2E / %x3C / %x3E / %x5E) / %x2B)) "|"))
|
|
226
|
+
list_marker_4 = (olist_marker_4 / ulist_marker_4)
|
|
227
|
+
ulist_marker_5 = ([*literal_space] (5%x2A !(%x2A) !("===")))
|
|
228
|
+
olist_marker_5 = ([*literal_space] 5%x2E !(%x2E) !((*((%x30-39 / %x2E / %x3C / %x3E / %x5E) / %x2B)) "|"))
|
|
229
|
+
list_marker_5 = (olist_marker_5 / ulist_marker_5)
|
|
230
|
+
inline_lb = (typographic_quote / bold_unconstrained / bold_constrained / span_unconstrained / span_constrained / italic_unconstrained / italic_constrained / highlight_unconstrained / highlight_constrained / monospace_unconstrained / monospace_constrained / superscript / subscript / attribute_reference / escaped_xref / escape_char / cross_reference / term_inline / term_inline2 / footnote / stem / link / inline_image / inline_passthrough / underline / small)
|
|
231
|
+
list_body_hard_break = ((" +" %x0A) / (%x5C %x0A))
|
|
232
|
+
list_body_text_unformatted = (1*(!("\\<<") !(inline_lb) !(list_body_hard_break) !(%x0A) any_char))
|
|
233
|
+
list_body_text_any = (1*(inline_lb / (list_body_text_unformatted as text)))
|
|
234
|
+
list_body_line_ending = (((" +" as hard_line_break) (line_ending as line_break)) / (line_ending as line_break) / !(any_char))
|
|
235
|
+
list_body_text_line = ([*literal_space] (list_body_text_any as text) list_body_line_ending)
|
|
236
|
+
list_item_continuation_lines = (*(line_not_text list_body_text_line))
|
|
237
|
+
list_attached_block = (admonition_line / unordered_list_1 / ordered_list_1 / paragraph / block_3)
|
|
238
|
+
list_item_attached = ((*(&(list_continuation) list_continuation list_attached_block)) as attached)
|
|
239
|
+
# a bare marker line is an empty list item — without this
|
|
240
|
+
# arm it falls through to a continuation text line and corrupts
|
|
241
|
+
# the preceding item.
|
|
242
|
+
ulist_item_1 = (empty_ulist_item_1 / ulist_item_1_nonempty)
|
|
243
|
+
|
|
244
|
+
empty_ulist_item_1 = (&(ulist_marker_1) (((ulist_marker_1 as marker) !(" [[[") (%x0A as lines)) as list_item))
|
|
245
|
+
|
|
246
|
+
ulist_item_1_nonempty = (&(ulist_marker_1) (((ulist_marker_1 as marker) !(" [[[") !(%x0A) space ((list_body_text_line list_item_continuation_lines) as lines) [list_item_attached] ((*(&(list_marker_2) list_2)) as nested)) as list_item))
|
|
247
|
+
olist_item_1 = (&(olist_marker_1) (((olist_marker_1 as marker) !(%x0A) space ((list_body_text_line list_item_continuation_lines) as lines) [list_item_attached] ((*(&(list_marker_2) list_2)) as nested)) as list_item))
|
|
248
|
+
ulist_item_2 = (&(ulist_marker_2) (((ulist_marker_2 as marker) !(" [[[") !(%x0A) space ((list_body_text_line list_item_continuation_lines) as lines) [list_item_attached] ((*(&(list_marker_3) list_3)) as nested)) as list_item))
|
|
249
|
+
olist_item_2 = (&(olist_marker_2) (((olist_marker_2 as marker) !(%x0A) space ((list_body_text_line list_item_continuation_lines) as lines) [list_item_attached] ((*(&(list_marker_3) list_3)) as nested)) as list_item))
|
|
250
|
+
ulist_item_3 = (&(ulist_marker_3) (((ulist_marker_3 as marker) !(" [[[") !(%x0A) space ((list_body_text_line list_item_continuation_lines) as lines) [list_item_attached] ((*(&(list_marker_4) list_4)) as nested)) as list_item))
|
|
251
|
+
olist_item_3 = (&(olist_marker_3) (((olist_marker_3 as marker) !(%x0A) space ((list_body_text_line list_item_continuation_lines) as lines) [list_item_attached] ((*(&(list_marker_4) list_4)) as nested)) as list_item))
|
|
252
|
+
ulist_item_4 = (&(ulist_marker_4) (((ulist_marker_4 as marker) !(" [[[") !(%x0A) space ((list_body_text_line list_item_continuation_lines) as lines) [list_item_attached] ((*(&(list_marker_5) list_5)) as nested)) as list_item))
|
|
253
|
+
olist_item_4 = (&(olist_marker_4) (((olist_marker_4 as marker) !(%x0A) space ((list_body_text_line list_item_continuation_lines) as lines) [list_item_attached] ((*(&(list_marker_5) list_5)) as nested)) as list_item))
|
|
254
|
+
ulist_item_5 = (&(ulist_marker_5) (((ulist_marker_5 as marker) !(" [[[") !(%x0A) space ((list_body_text_line list_item_continuation_lines) as lines) [list_item_attached] ) as list_item))
|
|
255
|
+
olist_item_5 = (&(olist_marker_5) (((olist_marker_5 as marker) !(%x0A) space ((list_body_text_line list_item_continuation_lines) as lines) [list_item_attached] ) as list_item))
|
|
256
|
+
unordered_list_1 = ([(attribute_list line_ending)] ((1*(list_item_separator ulist_item_1)) as unordered))
|
|
257
|
+
ordered_list_1 = ([(attribute_list line_ending)] ((1*(list_item_separator olist_item_1)) as ordered))
|
|
258
|
+
list_1 = ((unordered_list_1 / ordered_list_1 / definition_list)) as list
|
|
259
|
+
unordered_list_2 = ([(attribute_list line_ending)] ((1*(list_item_separator ulist_item_2)) as unordered))
|
|
260
|
+
ordered_list_2 = ([(attribute_list line_ending)] ((1*(list_item_separator olist_item_2)) as ordered))
|
|
261
|
+
list_2 = ((unordered_list_2 / ordered_list_2 / definition_list)) as list
|
|
262
|
+
unordered_list_3 = ([(attribute_list line_ending)] ((1*(list_item_separator ulist_item_3)) as unordered))
|
|
263
|
+
ordered_list_3 = ([(attribute_list line_ending)] ((1*(list_item_separator olist_item_3)) as ordered))
|
|
264
|
+
list_3 = ((unordered_list_3 / ordered_list_3 / definition_list)) as list
|
|
265
|
+
unordered_list_4 = ([(attribute_list line_ending)] ((1*(list_item_separator ulist_item_4)) as unordered))
|
|
266
|
+
ordered_list_4 = ([(attribute_list line_ending)] ((1*(list_item_separator olist_item_4)) as ordered))
|
|
267
|
+
list_4 = ((unordered_list_4 / ordered_list_4 / definition_list)) as list
|
|
268
|
+
unordered_list_5 = ([(attribute_list line_ending)] ((1*(list_item_separator ulist_item_5)) as unordered))
|
|
269
|
+
ordered_list_5 = ([(attribute_list line_ending)] ((1*(list_item_separator olist_item_5)) as ordered))
|
|
270
|
+
list_5 = ((unordered_list_5 / ordered_list_5 / definition_list)) as list
|
|
271
|
+
dlist_delimiter = (((":::::" !(%x3A)) / ("::::" !(%x3A)) / (":::" !(%x3A)) / ("::" !(%x3A)) / ";;") as delimiter)
|
|
272
|
+
dlist_term = ((([element_id_inline] ((1*(!(dlist_delimiter) !(%x0A) any_char)) as text)) as dlist_term) dlist_delimiter)
|
|
273
|
+
dlist_definition = ((line_not_text *((%x20 / %x09)) ((list_body_text_line list_item_continuation_lines) as lines)) (*empty_line))
|
|
274
|
+
dlist_item = ((((((1*dlist_term) as terms) line_ending (*empty_line) [dlist_definition]) / (((1*dlist_term) as terms) space dlist_definition)) [list_item_attached] (*(list_continuation &(dlist_item)))) as definition_list_item)
|
|
275
|
+
definition_list = ([(attribute_list line_ending)] ((1*(list_item_separator dlist_item)) as definition_list) !(dlist_item))
|
|
276
|
+
|
|
277
|
+
# ---- header.rb ----
|
|
278
|
+
word = (1*(%x41-5A / %x61-7A / %x30-39 / "_" / "-"))
|
|
279
|
+
words = (word (*([*" "] word)))
|
|
280
|
+
email_r = (word %x40 word %x2E word)
|
|
281
|
+
date_r = (2*4(%x30-39) %x2D 1*2(%x30-39) %x2D 1*2(%x30-39))
|
|
282
|
+
header_title = (%x3D !(%x3D) [*" "] (text as title) newline)
|
|
283
|
+
author_r = ((words as first_name) %x2C [*" "] (words as last_name) [*" "] %x3C (email_r as email) %x3E newline)
|
|
284
|
+
revision_r = (((word [(%x2E word)]) as number) %x2C [*" "] (date_r as date) %x3A [*" "] (words as remark) newline)
|
|
285
|
+
header = (header_title ([author_r] as author) ([revision_r] as revision) *(newline))
|
|
286
|
+
|
|
287
|
+
# ---- document_attributes.rb ----
|
|
288
|
+
doc_attr_name = (1*(%x41-5A / %x61-7A / %x30-39 / "_" / "-"))
|
|
289
|
+
doc_attr_value = (text / !(newline_single))
|
|
290
|
+
document_attribute = (%x3A (doc_attr_name as key) %x3A [*" "] (doc_attr_value as value) (newline_single / !(any_char)))
|
|
291
|
+
document_attributes = ((1*document_attribute) as document_attributes)
|
|
292
|
+
|
|
293
|
+
# ---- bibliography.rb ----
|
|
294
|
+
text_line_g = ([element_id_inline] [*literal_space] (text_any as text) ((newline_single as line_break) / !(any_char)))
|
|
295
|
+
bib_entry = (((!(%x2A) any_char) " [[[" ((1*(! (%x2C / %x5B / %x5D / %x0A) any_char)) as anchor_name) [(%x2C ((1*(! (%x5D / %x0A) any_char)) as document_id))] "]]]" [(((0*1text_line_g) (*text_line_g)) as ref_text)] [((1*newline_single) as line_break)]) as bibliography_entry)
|
|
296
|
+
|
|
297
|
+
# ---- text.rb block_image ----
|
|
298
|
+
attribute_list_macro = (&(%x5B) %x5B !(%x5B) (((named_many / positional_one_named_many / positional_many_named_many / positional_many / positional_zero_or_one) as attribute_array) as attribute_list_macro) %x5D)
|
|
299
|
+
block_image = ((block_header "image::" (file_path as path) attribute_list_macro (newline as line_break)) as block_image)
|
|
300
|
+
|
|
301
|
+
# ---- table.rb (state-driven delimiter) ----
|
|
302
|
+
state tdc: string
|
|
303
|
+
state tdf: string
|
|
304
|
+
dchar = (%x7C / %x21 / %x2C / %x3A / %x3B)
|
|
305
|
+
fspec_char = (%x30-39 / %x2E / %x3C / %x3E / "d" / "s" / "e" / "m" / "a" / "l" / "h" / "v" / %x2B / %x5E)
|
|
306
|
+
cell_format_spec = (1*fspec_char)
|
|
307
|
+
table_start = ((set tdc = (dchar as delim_char)) (set tdf = (2*%x3D)))
|
|
308
|
+
table_close = ((state tdc) (state tdf))
|
|
309
|
+
new_row_signal = (newline_single ((state tdc) / (fspec_char (*fspec_char) (state tdc))))
|
|
310
|
+
# \ + a delim char: state-matched chars drop out of the enclosing
|
|
311
|
+
# capture span (#129 nuance), so match the delim class instead —
|
|
312
|
+
# for the active delim it consumes the same pair, and a \ + other-delim
|
|
313
|
+
# pair consumes the same bytes as two single-char iterations
|
|
314
|
+
escaped_delim = (%x5C dchar)
|
|
315
|
+
cell_content = (*(!table_close !new_row_signal (escaped_delim / (!((state tdc)) any_char))))
|
|
316
|
+
table_cell = ([*literal_space] ([cell_format_spec] as cell_format) (state tdc) (cell_content as text))
|
|
317
|
+
table_row = (((1*(!table_close !new_row_signal (table_cell as cell))) as cells) newline_single)
|
|
318
|
+
table_rows = (1*(!table_close (table_row as row)))
|
|
319
|
+
table = (block_header table_start newline_single (table_rows as rows) table_close (newline_single / !(any_char)))
|
|
320
|
+
|
|
321
|
+
# ---- section.rb (levels 2..8) ----
|
|
322
|
+
endline_r = ((newline as line_break) / ((!(any_char)) as line_break))
|
|
323
|
+
section_header = ([(element_id)] [(attribute_blocks)])
|
|
324
|
+
|
|
325
|
+
section_title_2 = ((((2*8(%x3D))) as level) !(%x3D) [*" "] (text as text) endline_r)
|
|
326
|
+
section_title_3 = ((((3*8(%x3D))) as level) !(%x3D) [*" "] (text as text) endline_r)
|
|
327
|
+
section_title_4 = ((((4*8(%x3D))) as level) !(%x3D) [*" "] (text as text) endline_r)
|
|
328
|
+
section_title_5 = ((((5*8(%x3D))) as level) !(%x3D) [*" "] (text as text) endline_r)
|
|
329
|
+
section_title_6 = ((((6*8(%x3D))) as level) !(%x3D) [*" "] (text as text) endline_r)
|
|
330
|
+
section_title_7 = ((((7*8(%x3D))) as level) !(%x3D) [*" "] (text as text) endline_r)
|
|
331
|
+
section_title_8 = ((((8*8(%x3D))) as level) !(%x3D) [*" "] (text as text) endline_r)
|
|
332
|
+
contents_r = ((1*(bib_entry / block_image / tag / comment_block / comment_line / include_directive / admonition_line / block_3 / (table as table) / (page_break as page_break) / paragraph / list_1 / (empty_line as line_break))) as contents)
|
|
333
|
+
section_block_2 = (section_header (section_title_2 as title) [(contents_r)])
|
|
334
|
+
section_block_3 = (section_header (section_title_3 as title) [(contents_r)])
|
|
335
|
+
section_block_4 = (section_header (section_title_4 as title) [(contents_r)])
|
|
336
|
+
section_block_5 = (section_header (section_title_5 as title) [(contents_r)])
|
|
337
|
+
section_block_6 = (section_header (section_title_6 as title) [(contents_r)])
|
|
338
|
+
section_block_7 = (section_header (section_title_7 as title) [(contents_r)])
|
|
339
|
+
section_block_8 = (section_header (section_title_8 as title) [(contents_r)])
|
|
340
|
+
section_8 = section_block_8
|
|
341
|
+
section_7 = (section_block_7 ((*(section_8 as section)) as sections))
|
|
342
|
+
section_6 = (section_block_6 ((*(section_7 as section)) as sections))
|
|
343
|
+
section_5 = (section_block_5 ((*(section_6 as section)) as sections))
|
|
344
|
+
section_4 = (section_block_4 ((*(section_5 as section)) as sections))
|
|
345
|
+
section_3 = (section_block_3 ((*(section_4 as section)) as sections))
|
|
346
|
+
section_2 = ((section_block_2 ((*(section_3 as section)) as sections)) as section)
|
|
347
|
+
|
|
348
|
+
# ---- block.rb block_container_content (non-verbatim block bodies) ----
|
|
349
|
+
# text_line(false, unguarded: true): no guards, text_any body
|
|
350
|
+
text_line_u = ([*literal_space] (text_any as text) ((newline_single as line_break) / !(any_char)))
|
|
351
|
+
container_3 = ((table as table) / block_image / block_2 / list_1 / text_line_u / (empty_line as line_break))
|
|
352
|
+
container_2 = ((table as table) / block_image / block_1 / list_1 / text_line_u / (empty_line as line_break))
|
|
353
|
+
container_1 = ((table as table) / block_image / list_1 / text_line_u / (empty_line as line_break))
|
|
354
|
+
example_block_3 = (block_header ((set eq_fence = (4*%x3D)) as delimiter) newline ((1*(!((state eq_fence) newline) (container_3 / (empty_line as line_break)))) as lines) (state eq_fence) newline)
|
|
355
|
+
quote_block_3 = (block_header ((set us_fence = (4*%x5F)) as delimiter) newline ((1*(!((state us_fence) newline) (container_3 / (empty_line as line_break)))) as lines) (state us_fence) newline)
|
|
356
|
+
sidebar_block_3 = (block_header ((set ast_fence = (4*%x2A)) as delimiter) newline ((1*(!((state ast_fence) newline) (container_3 / (empty_line as line_break)))) as lines) (state ast_fence) newline)
|
|
357
|
+
open_block_3 = (block_header ((set open_fence = (2%x2D)) as delimiter) newline ((1*(!((state open_fence) newline) (container_3 / (empty_line as line_break)))) as lines) (state open_fence) newline)
|
|
358
|
+
block_3 = ((markdown_code_block / example_block_3 / sidebar_block_3 / source_block / quote_block_3 / pass_block / literal_block / open_cast_3 / open_block_3) as block)
|
|
359
|
+
example_block_2 = (block_header ((set eq_fence = (4*%x3D)) as delimiter) newline ((1*(!((state eq_fence) newline) (container_2 / (empty_line as line_break)))) as lines) (state eq_fence) newline)
|
|
360
|
+
quote_block_2 = (block_header ((set us_fence = (4*%x5F)) as delimiter) newline ((1*(!((state us_fence) newline) (container_2 / (empty_line as line_break)))) as lines) (state us_fence) newline)
|
|
361
|
+
sidebar_block_2 = (block_header ((set ast_fence = (4*%x2A)) as delimiter) newline ((1*(!((state ast_fence) newline) (container_2 / (empty_line as line_break)))) as lines) (state ast_fence) newline)
|
|
362
|
+
open_block_2 = (block_header ((set open_fence = (2%x2D)) as delimiter) newline ((1*(!((state open_fence) newline) (container_2 / (empty_line as line_break)))) as lines) (state open_fence) newline)
|
|
363
|
+
block_2 = ((markdown_code_block / example_block_2 / sidebar_block_2 / source_block / quote_block_2 / pass_block / literal_block / open_cast_2 / open_block_2) as block)
|
|
364
|
+
example_block_1 = (block_header ((set eq_fence = (4*%x3D)) as delimiter) newline ((1*(!((state eq_fence) newline) (container_1 / (empty_line as line_break)))) as lines) (state eq_fence) newline)
|
|
365
|
+
quote_block_1 = (block_header ((set us_fence = (4*%x5F)) as delimiter) newline ((1*(!((state us_fence) newline) (container_1 / (empty_line as line_break)))) as lines) (state us_fence) newline)
|
|
366
|
+
sidebar_block_1 = (block_header ((set ast_fence = (4*%x2A)) as delimiter) newline ((1*(!((state ast_fence) newline) (container_1 / (empty_line as line_break)))) as lines) (state ast_fence) newline)
|
|
367
|
+
open_block_1 = (block_header ((set open_fence = (2%x2D)) as delimiter) newline ((1*(!((state open_fence) newline) (container_1 / (empty_line as line_break)))) as lines) (state open_fence) newline)
|
|
368
|
+
block_1 = ((markdown_code_block / example_block_1 / sidebar_block_1 / source_block / quote_block_1 / pass_block / literal_block / open_cast_1 / open_block_1) as block)
|
|
369
|
+
|
|
370
|
+
# ---- block_style_exact's verbatim cast: a [source]/[listing]/
|
|
371
|
+
# ---- [literal] attribute list turns the open block body verbatim
|
|
372
|
+
source_cast_attr = (&(("[" ("source" / "listing" / "literal"))) attribute_blocks)
|
|
373
|
+
open_cast_3 = ((source_cast_attr) ((set open_fence = (2%x2D)) as delimiter) newline ((1*(!((state open_fence) newline) verbatim_line / (empty_line as line_break))) as lines) (state open_fence) newline)
|
|
374
|
+
open_cast_2 = ((source_cast_attr) ((set open_fence = (2%x2D)) as delimiter) newline ((1*(!((state open_fence) newline) verbatim_line / (empty_line as line_break))) as lines) (state open_fence) newline)
|
|
375
|
+
open_cast_1 = ((source_cast_attr) ((set open_fence = (2%x2D)) as delimiter) newline ((1*(!((state open_fence) newline) verbatim_line / (empty_line as line_break))) as lines) (state open_fence) newline)
|
|
376
|
+
|
|
377
|
+
# ---- base.rb rule(:document) — ported members in Ruby order ----
|
|
378
|
+
# the repetition itself is the capture: parenthesize so `as` binds to
|
|
379
|
+
# the star, not the body (per-iteration capture would fragment the tree)
|
|
380
|
+
document = (*(((header) as header) / document_attributes / section_2 / admonition_line / bib_entry / block_3 / block_image / comment_block / comment_line / include_directive / list_1 / (table) as table / (page_break) as page_break / paragraph / tag / (empty_line as line_break) / (any_char as unparsed))) as document
|
|
381
|
+
|
|
382
|
+
entry document: document
|
|
383
|
+
|
|
384
|
+
test {
|
|
385
|
+
accept "----\ncode\n----"
|
|
386
|
+
accept "----\ncode\n----\n----\nmore\n----"
|
|
387
|
+
accept "------\ncode\n------"
|
|
388
|
+
accept "```ruby\nputs 1\n```"
|
|
389
|
+
accept "````\n```\n````"
|
|
390
|
+
accept ".Title\n----\ncode\n----"
|
|
391
|
+
accept "[source, ruby]\n----\nx = 1\n----"
|
|
392
|
+
accept "////\nblock comment\n////"
|
|
393
|
+
accept "// tag::section-name[]"
|
|
394
|
+
accept "include::file.adoc[leveloffset=1]"
|
|
395
|
+
}
|
|
396
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
# Canonical AsciiDoc formatter (#110): parse to the format model
|
|
6
|
+
# tree, serialize, then normalize inter-block whitespace. Blank
|
|
7
|
+
# lines inside verbatim/example fences are preserved — the pass
|
|
8
|
+
# tracks delimiter pairs and only squeezes outside them.
|
|
9
|
+
module Formatter
|
|
10
|
+
# Delimiter-line fence characters: listings, examples, sidebars,
|
|
11
|
+
# quotes, passthroughs, thematic breaks. A fence is 4+ repetitions
|
|
12
|
+
# of ONE character (CodeQL rb/polynomial-redos: the earlier regex
|
|
13
|
+
# form flagged as polynomial; a same-char scan is linear and also
|
|
14
|
+
# stricter — mixed runs are not fences).
|
|
15
|
+
FENCE_CHARACTERS = %w[- = * _ ' +].freeze
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def call(text)
|
|
20
|
+
canonical_whitespace(Coradoc::AsciiDoc.parse(text).to_adoc)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def canonical_whitespace(adoc)
|
|
24
|
+
out = []
|
|
25
|
+
blanks = 0
|
|
26
|
+
fence = nil
|
|
27
|
+
|
|
28
|
+
adoc.lines.each do |line|
|
|
29
|
+
stripped = line.strip
|
|
30
|
+
|
|
31
|
+
if fence
|
|
32
|
+
out << line
|
|
33
|
+
fence = nil if stripped == fence && fence_delimiter(stripped)
|
|
34
|
+
elsif fence_delimiter(stripped)
|
|
35
|
+
emit_blanks(out, blanks)
|
|
36
|
+
blanks = 0
|
|
37
|
+
out << line
|
|
38
|
+
fence = stripped
|
|
39
|
+
elsif stripped.empty?
|
|
40
|
+
blanks += 1
|
|
41
|
+
else
|
|
42
|
+
emit_blanks(out, blanks)
|
|
43
|
+
blanks = 0
|
|
44
|
+
out << line
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
joined = out.join
|
|
48
|
+
return joined if joined.empty?
|
|
49
|
+
|
|
50
|
+
# Exactly one trailing newline — a reverse scan, not a regex
|
|
51
|
+
# (CodeQL rb/polynomial-redos models /\n+\z/ as polynomial).
|
|
52
|
+
end_index = joined.length
|
|
53
|
+
end_index -= 1 while end_index.positive? && joined[end_index - 1] == "\n"
|
|
54
|
+
"#{joined[0, end_index]}\n"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The delimiter character when +stripped+ is a fence line
|
|
58
|
+
# (4+ repetitions of one fence character); nil otherwise.
|
|
59
|
+
def fence_delimiter(stripped)
|
|
60
|
+
return nil unless stripped.length >= 4
|
|
61
|
+
|
|
62
|
+
first = stripped[0]
|
|
63
|
+
return nil unless FENCE_CHARACTERS.include?(first)
|
|
64
|
+
|
|
65
|
+
stripped.each_char.all?(first) ? first : nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Exactly one blank line between blocks; none before the first
|
|
69
|
+
# content line.
|
|
70
|
+
def emit_blanks(out, blanks)
|
|
71
|
+
out << "\n" if blanks.positive? && !out.empty?
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
module Lint
|
|
6
|
+
# ADOC002 — a document should have exactly one title: the
|
|
7
|
+
# level-0 `=` heading carried by the document header.
|
|
8
|
+
class DocumentTitle < Coradoc::Lint::Rule
|
|
9
|
+
rule_id 'ADOC002'
|
|
10
|
+
applies_to :asciidoc
|
|
11
|
+
|
|
12
|
+
def check(document, path)
|
|
13
|
+
title = document.header&.title
|
|
14
|
+
text = case title
|
|
15
|
+
when Coradoc::AsciiDoc::Model::Title then title.content
|
|
16
|
+
when String then title
|
|
17
|
+
end
|
|
18
|
+
return [] if text && !text.strip.empty?
|
|
19
|
+
|
|
20
|
+
[build_violation(path: path,
|
|
21
|
+
message: 'document has no level-0 title (= ...)')]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
module Lint
|
|
6
|
+
# ADOC005 — section ids must be unique: duplicated anchors
|
|
7
|
+
# break cross-references.
|
|
8
|
+
class DuplicateIds < Coradoc::Lint::Rule
|
|
9
|
+
rule_id 'ADOC005'
|
|
10
|
+
applies_to :asciidoc
|
|
11
|
+
|
|
12
|
+
def check(document, path)
|
|
13
|
+
ids = Hash.new(0)
|
|
14
|
+
collect(document, ids)
|
|
15
|
+
ids.select { |_id, count| count > 1 }
|
|
16
|
+
.map do |id, count|
|
|
17
|
+
build_violation(path: path,
|
|
18
|
+
message: "duplicate section id #{id.inspect} used #{count} times")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def collect(node, ids)
|
|
25
|
+
Array(node.sections).each do |child|
|
|
26
|
+
case child
|
|
27
|
+
when Coradoc::AsciiDoc::Model::Section
|
|
28
|
+
ids[child.id] += 1 if child.id
|
|
29
|
+
collect(child, ids)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
module Lint
|
|
6
|
+
# ADOC003 — sections should carry content: neither block
|
|
7
|
+
# content nor subsections means an empty heading.
|
|
8
|
+
class EmptySection < Coradoc::Lint::Rule
|
|
9
|
+
rule_id 'ADOC003'
|
|
10
|
+
applies_to :asciidoc
|
|
11
|
+
|
|
12
|
+
def check(document, path)
|
|
13
|
+
violations = []
|
|
14
|
+
walk(document, path, violations)
|
|
15
|
+
violations
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def walk(node, path, violations)
|
|
21
|
+
Array(node.sections).each do |child|
|
|
22
|
+
case child
|
|
23
|
+
when Coradoc::AsciiDoc::Model::Section
|
|
24
|
+
if empty?(child)
|
|
25
|
+
violations << build_violation(
|
|
26
|
+
path: path,
|
|
27
|
+
message: "empty section: #{child.title&.content}"
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
walk(child, path, violations)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def empty?(section)
|
|
36
|
+
Array(section.contents).empty? && sections_of(section).empty?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def sections_of(section)
|
|
40
|
+
Array(section.sections).grep(Coradoc::AsciiDoc::Model::Section)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
module Lint
|
|
6
|
+
# ADOC001 — heading levels must not skip: a section may be at
|
|
7
|
+
# most one level deeper than its parent. Skipped levels produce
|
|
8
|
+
# broken document outlines.
|
|
9
|
+
class HeadingLevels < Coradoc::Lint::Rule
|
|
10
|
+
rule_id 'ADOC001'
|
|
11
|
+
applies_to :asciidoc
|
|
12
|
+
|
|
13
|
+
def check(document, path)
|
|
14
|
+
violations = []
|
|
15
|
+
sections(document).each { |s| walk(s, 0, path, violations) }
|
|
16
|
+
violations
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def walk(section, parent_level, path, violations)
|
|
22
|
+
level = section.title&.level_int
|
|
23
|
+
if level && level > parent_level + 1
|
|
24
|
+
violations << build_violation(
|
|
25
|
+
path: path,
|
|
26
|
+
message: "heading level skips from #{parent_level} to #{level}: " \
|
|
27
|
+
"#{title_text(section)}"
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
sections(section).each { |child| walk(child, level || parent_level, path, violations) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def sections(node)
|
|
34
|
+
Array(node.sections).grep(Coradoc::AsciiDoc::Model::Section)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def title_text(section)
|
|
38
|
+
title = section.title
|
|
39
|
+
title.respond_to?(:content) ? title.content : title.to_s
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|