Module: Coradoc::AsciiDoc::Parser::Inline

Defined in:
lib/coradoc/asciidoc/parser/inline.rb

Constant Summary collapse

INLINE_RULE_ORDER =

Priority-ordered registry of inline rules. The ORDER IS LOAD-BEARING: typographic_quote MUST come before monospace_constrained (Bug 14); each unconstrained rule MUST come before its constrained sibling ( before ``,**before*, __before_`). Adding a new inline rule = appending one symbol here (OCP).

%i[
  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 cross_reference
  term_inline term_inline2
  footnote stem
  link inline_image
  inline_passthrough
  underline small
  hard_line_break
].freeze

Instance Method Summary collapse

Instance Method Details

#attribute_referenceObject



21
22
23
24
25
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 21

def attribute_reference
  str('{').present? >> str('{') >>
    match('[a-zA-Z0-9_-]').repeat(1).as(:attribute_reference) >>
    str('}')
end

#bold_constrainedObject



69
70
71
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 69

def bold_constrained
  constrained_mark('*', reject_paragraph_break: true).as(:bold_constrained)
end

#bold_unconstrainedObject



73
74
75
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 73

def bold_unconstrained
  unconstrained_mark('*').as(:bold_unconstrained)
end

#constrained_mark(marker, reject_paragraph_break: false, content: nil) ⇒ Object

── Constrained / unconstrained mark builders ──

Single source of truth for the four constrained + four unconstrained inline mark rules. Before this extraction each rule hand-rolled the same open → content → close → lookahead shape with subtly different guards (presence checks, newline exclusions, inner-marker allowances). Now each rule is a 1-line wrapper over one of two parameterised builders.

The constrained builder adds a marker.absent? guard on both ends so single-marker constrained never matches when a double-marker unconstrained is intended (e.g. * defers to **). The alternation order in inline handles this too, but the guard is belt-and-suspenders — it keeps each rule self-contained for unit testing.

The unconstrained builder allows newlines in the content (Asciidoctor's behaviour for __, **, ##, `` — a mark can span line breaks). This was Bug 15B's fix scope; highlight_unconstrained was previously inconsistent (excluded newlines). All four are now uniform.



49
50
51
52
53
54
55
56
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 49

def constrained_mark(marker, reject_paragraph_break: false, content: nil)
  open_guard = str(marker) >> str(marker).absent?
  content_rule = content || default_constrained_content(marker)
  close_guard = str(marker) >> str(marker).absent?
  sequence = open_guard >> content_rule >> close_guard
  sequence >>= str("\n\n").absent? if reject_paragraph_break
  sequence
end

#constrained_span_content(marker) ⇒ Object

Content model for a constrained inline span (`…`, *…*, _…_, #…#). Allows the corresponding unconstrained marker pair ( ``, **, __, ##) to appear inside the content rather than terminating the span at the first marker character.

Asciidoctor treats the contents of a constrained span as an inline literal — nested constrained markers do not close the span. Without this allowance, `<<\`\`x\`\`>>` fails to match as a single monospace span: the parser sees the inner `` as a failed single-backtick close attempt, backtracks out of monospace_constrained, and the <<…>> payload then fires the cross_reference rule with the backticks glued onto the target.

Single source of truth for every constrained rule's content model — adding a new constrained marker reuses this helper rather than re-spelling the alternation (DRY).



288
289
290
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 288

def constrained_span_content(marker)
  (match("[^#{marker}\n]") | str(marker * 2)).repeat(1)
end

#default_constrained_content(marker) ⇒ Object



58
59
60
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 58

def default_constrained_content(marker)
  match("[^#{marker}\n]").repeat(1).as(:text).repeat(1, 1)
end

#hard_line_breakObject



228
229
230
231
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 228

def hard_line_break
  ((str(' +') >> str("\n")) |
     (str('\\') >> str("\n"))).as(:hard_line_break)
end

#hard_line_break_marker?Boolean

AsciiDoc hard line break: a space followed by + at end of line, or a backslash at end of line. Both forms render as <br> inside the enclosing paragraph/verse. Recognised ahead of text_unformatted so the marker isn't swallowed as plain text.

Returns:

  • (Boolean)


223
224
225
226
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 223

def hard_line_break_marker?
  (str(' +') >> str("\n")).present? |
    (str('\\') >> str("\n")).present?
end

#highlight_constrainedObject



101
102
103
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 101

def highlight_constrained
  constrained_mark('#').as(:highlight_constrained)
end

#highlight_unconstrainedObject



105
106
107
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 105

def highlight_unconstrained
  unconstrained_mark('#').as(:highlight_unconstrained)
end

#inlineObject



256
257
258
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 256

def inline
  INLINE_RULE_ORDER.map { |name| public_send(name) }.reduce(:|)
end

#inline_chars?Boolean

Returns:

  • (Boolean)


203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 203

def inline_chars?
  match('[\[*#_{<^~`]').present? |
    typographic_quote.present? |
    str('http').present? |
    str('https').present? |
    str('link:').present? |
    str('image:').present? |
    str('+++').present? |
    str('pass:').present? |
    term_type.present? |
    str('footnote').present? |
    stem_type.present? |
    str('\\<<').present? |
    hard_line_break_marker?
end

#inline_imageObject



151
152
153
154
155
156
157
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 151

def inline_image
  (str('image:').present? >> str('image:') >>
    str(':').absent? >>
    match('[A-Za-z0-9_.\\-:/&?=+,%#~;]+').repeat(1).as(:path) >>
    attribute_list(:attribute_list).maybe
  ).as(:inline_image)
end

#inline_passthroughObject



183
184
185
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 183

def inline_passthrough
  inline_passthrough_triple_plus | inline_passthrough_macro
end

#inline_passthrough_macroObject

pass:[raw] macro form. Equivalent semantic to triple-plus: the bracket payload survives all substitutions verbatim. Common use is inside monospace spans to keep characters like < from being re-interpreted as xref markers. The optional subs segment (pass:quotes[...]) is consumed but currently ignored — the payload is always passed through raw.



175
176
177
178
179
180
181
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 175

def inline_passthrough_macro
  (str('pass:').present? >> str('pass:') >>
    match('[a-zA-Z,+]').repeat(0) >>
    str('[') >> (str(']]').absent? >> match('[^\]\n]')).repeat(1).as(:raw) >>
    str(']')
  ).as(:inline_passthrough)
end

#inline_passthrough_triple_plusObject

Triple-plus inline passthrough: +++raw content+++. The content passes through all substitutions verbatim. Common use is to embed raw HTML in AsciiDoc documents.



162
163
164
165
166
167
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 162

def inline_passthrough_triple_plus
  (str('+++') >>
    (str('+++').absent? >> match('[^\n]')).repeat(1).as(:raw) >>
    str('+++')
  ).as(:inline_passthrough)
end

#italic_constrainedObject



93
94
95
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 93

def italic_constrained
  constrained_mark('_').as(:italic_constrained)
end

#italic_unconstrainedObject



97
98
99
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 97

def italic_unconstrained
  unconstrained_mark('_').as(:italic_unconstrained)
end


140
141
142
143
144
145
146
147
148
149
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 140

def link
  ((str('http').present? | str('https').present? | str('ftp').present?) >>
    match('[A-Za-z0-9_.\\-:/&?=+,%#~;]+').repeat(1).as(:path) >>
    (str('[') >> match('[^\\]]').repeat(1).as(:text) >> str(']')).maybe
  ).as(:link) |
    (str('link:').present? >> str('link:') >>
      match('[A-Za-z0-9_.\\-:/&?=+,%#~;]+').repeat(1).as(:path) >>
      (str('[') >> match('[^\\]]').repeat(1).as(:text) >> str(']')).maybe
    ).as(:link)
end

#monospace_constrainedObject



109
110
111
112
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 109

def monospace_constrained
  content = constrained_span_content('`').as(:text).repeat(1, 1)
  constrained_mark('`', content: content).as(:monospace_constrained)
end

#monospace_unconstrainedObject



114
115
116
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 114

def monospace_unconstrained
  unconstrained_mark('`').as(:monospace_unconstrained)
end

#smallObject



195
196
197
198
199
200
201
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 195

def small
  (attribute_list >> match('\\[.small\\]').as(:role) >>
    str('#') >>
    match('[^#\n]').repeat(1).as(:text) >>
    str('#')
  ).as(:small)
end

#spanObject



132
133
134
135
136
137
138
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 132

def span
  attribute_list >>
    (str('#') >>
      match('[^#\n]').repeat(1).as(:text) >>
       str('#') >> str('#').absent?
    ).as(:span)
end

#span_constrainedObject



77
78
79
80
81
82
83
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 77

def span_constrained
  (attribute_list >>
    str('#') >>
    match('[^#\n]').repeat(1).as(:text) >>
     str('#') >> str('#').absent?
  ).as(:span_constrained)
end

#span_unconstrainedObject



85
86
87
88
89
90
91
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 85

def span_unconstrained
  (attribute_list >>
    str('##') >>
    match('[^#\n]').repeat(1).as(:text) >>
     str('##')
  ).as(:span_unconstrained)
end

#subscriptObject



125
126
127
128
129
130
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 125

def subscript
  (str('~') >>
    match('[^~\n]').repeat(1).as(:text).repeat(1, 1) >>
     str('~')
  ).as(:subscript)
end

#superscriptObject



118
119
120
121
122
123
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 118

def superscript
  (str('^') >>
    match('[^^\n]').repeat(1).as(:text).repeat(1, 1) >>
     str('^')
  ).as(:superscript)
end

#text_anyObject



296
297
298
299
300
301
302
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 296

def text_any
  (text_formatted |
          text_unformatted.as(:text)
  ).repeat(2) |
    text_formatted.repeat(1, 1) |
    text_unformatted
end

#text_formattedObject



292
293
294
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 292

def text_formatted
  (inline_chars? >> inline)
end

#text_unformattedObject



260
261
262
263
264
265
266
267
268
269
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 260

def text_unformatted
  # `str('\\<<').absent?` stops text from consuming the backslash of
  # an escaped xref (`\<<`). Without this guard, the `\` is taken as
  # plain text, the `<<` re-enters cross_reference, and the literal
  # escape is destroyed.
  (str('\\<<').absent? >>
    inline.absent? >>
    match("[^\n]")
  ).repeat(1)
end

#typographic_quoteObject

Typographic quote patterns are owned by the shared TypographicQuotes module so both the parser and the transformer reference the same source of truth without one layer reaching across the other.



12
13
14
15
16
17
18
19
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 12

def typographic_quote
  patterns = AsciiDoc::TypographicQuotes::PATTERNS
  combined = patterns.reduce(nil) do |acc, pat|
    atom = str(pat)
    acc ? (acc | atom) : atom
  end
  combined.as(:typographic_quote)
end

#unconstrained_mark(marker) ⇒ Object



62
63
64
65
66
67
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 62

def unconstrained_mark(marker)
  double = marker * 2
  str(double) >>
    match("[^#{marker}]").repeat(1).as(:text).repeat(1, 1) >>
    str(double)
end

#underlineObject



187
188
189
190
191
192
193
# File 'lib/coradoc/asciidoc/parser/inline.rb', line 187

def underline
  (attribute_list >> match('\\[.underline\\]').as(:role) >>
    str('#') >>
    match('[^#\n]').repeat(1).as(:text) >>
    str('#')
  ).as(:underline)
end