coradoc-adoc 2.0.29 → 2.0.31

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.
@@ -128,17 +128,31 @@ module Coradoc
128
128
  # Used by the paragraph and reviewer_note rules to share the same
129
129
  # line-shape handling (DRY).
130
130
  def self.lines_to_text_elements(lines)
131
- Array(lines).map do |line|
131
+ # Parslet may deliver `lines` as a single Hash (one line captured)
132
+ # or an Array of Hashes (multiple lines). `Array(hash)` converts
133
+ # to nested pairs, which we don't want — normalize explicitly.
134
+ normalized = case lines
135
+ when nil then []
136
+ when Array then lines
137
+ when Hash then [lines]
138
+ else Array(lines)
139
+ end
140
+ normalized.map do |line|
132
141
  next line unless line.is_a?(Hash) && line.key?(:text)
133
142
 
134
143
  text_content = line[:text]
135
- transformed = if text_content.is_a?(Array)
136
- text_content.map do |item|
137
- item.is_a?(Hash) ? new.apply(item) : item
138
- end
139
- else
140
- text_content
141
- end
144
+ # `text_content` may be a single Hash (one inline), an Array
145
+ # of Hashes (multiple inlines), or a String. Normalize to an
146
+ # Array of Hashes for uniform processing.
147
+ text_array = case text_content
148
+ when Array then text_content
149
+ when Hash then [text_content]
150
+ when String then [{ text: text_content }]
151
+ else [{ text: text_content.to_s }]
152
+ end
153
+ transformed = text_array.map do |item|
154
+ item.is_a?(Hash) ? new.apply(item) : item
155
+ end
142
156
 
143
157
  Model::TextElement.new(
144
158
  content: transformed,
@@ -147,6 +161,57 @@ module Coradoc
147
161
  end
148
162
  end
149
163
 
164
+ # Split `:lines` entries that span multiple source lines (via
165
+ # text_any greedy-matching across hard_line_break). Each call
166
+ # returns one entry per logical source line: when a line entry's
167
+ # `:text` array contains a `hard_line_break`, the entry is split
168
+ # at each hard_break, producing N+1 entries for N hard breaks.
169
+ # The hard_break is preserved as the `:line_break` of its segment.
170
+ #
171
+ # Without this, a dd source like:
172
+ # `term::` First line. +
173
+ # +
174
+ # attached
175
+ # produces a single `:lines` entry whose text contains
176
+ # ["First line.", hard_line_break(" +\n"), "+", "attached..."],
177
+ # which the renderer joins into "First line. + attached" — the
178
+ # `+` continuation marker ends up inside the dd's text instead
179
+ # of triggering the attached-block path.
180
+ #
181
+ # Used by the dlist transformer before lines_to_text_elements.
182
+ def self.split_lines_on_hard_break(lines)
183
+ normalized = case lines
184
+ when nil then []
185
+ when Array then lines
186
+ when Hash then [lines]
187
+ else Array(lines)
188
+ end
189
+ normalized.flat_map do |line|
190
+ next [line] unless line.is_a?(Hash) && line[:text].is_a?(Array)
191
+ next [line] unless line[:text].any? { |p| p.is_a?(Hash) && p.key?(:hard_line_break) }
192
+
193
+ segments = []
194
+ current_text = []
195
+ current_break = nil
196
+ line[:text].each do |part|
197
+ if part.is_a?(Hash) && part.key?(:hard_line_break)
198
+ segments << { text: current_text, line_break: part[:hard_line_break] }
199
+ current_text = []
200
+ current_break = :had_hard_break
201
+ else
202
+ current_text << part
203
+ end
204
+ end
205
+ # Final segment gets the original line's line_break (or empty
206
+ # if it ended with a hard break).
207
+ final_break = current_break == :had_hard_break ? "\n" : line[:line_break]
208
+ segments << { text: current_text, line_break: final_break }
209
+ # Drop leading empty segment if the line started with a hard break
210
+ # (shouldn't happen in practice but be defensive).
211
+ segments.reject { |s| s[:text].empty? && s != segments.last }
212
+ end
213
+ end
214
+
150
215
  # Legacy transform method (deprecated)
151
216
  # @deprecated Use {.transform} instead
152
217
  def self.legacy_transform(syntax_tree)
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module AsciiDoc
5
+ # Single source of truth for AsciiDoc's typographic quote substitution.
6
+ #
7
+ # Asciidoctor replaces four 2-char patterns with the corresponding
8
+ # Unicode curly quotes. Both the parser (to recognise the patterns)
9
+ # and the transformer (to emit the Unicode chars) reference this
10
+ # module — the mapping lives here, not on either consumer, so the
11
+ # parser/transformer seam stays clean.
12
+ #
13
+ # The patterns MUST be recognised before +monospace_constrained+ in
14
+ # the +inline+ alternation, otherwise the lone backtick in +`"`+ or
15
+ # +`"``+ fires monospace and the surrounding quote collapses to
16
+ # straight ASCII quotes wrapped around a spurious code span.
17
+ module TypographicQuotes
18
+ PATTERN_TO_CHAR = {
19
+ '"`' => '“', # U+201C left double
20
+ '`"' => '”', # U+201D right double
21
+ "'`" => '‘', # U+2018 left single
22
+ "`'" => '’' # U+2019 right single
23
+ }.freeze
24
+
25
+ # All four 2-char patterns, suitable for building a Parslet alternation.
26
+ # @return [Array<String>]
27
+ PATTERNS = PATTERN_TO_CHAR.keys.freeze
28
+
29
+ # Look up the Unicode char for a matched pattern.
30
+ # @param pattern [String, Parslet::Slice] the matched 2-char pattern
31
+ # @return [String] the Unicode curly quote char
32
+ def self.char_for(pattern)
33
+ PATTERN_TO_CHAR.fetch(pattern.to_s, pattern.to_s)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Coradoc
4
4
  module AsciiDoc
5
- VERSION = '2.0.29'
5
+ VERSION = '2.0.31'
6
6
  end
7
7
  end
@@ -28,6 +28,7 @@ end
28
28
  module Coradoc
29
29
  module AsciiDoc
30
30
  autoload :DelimiterMapping, "#{__dir__}/asciidoc/delimiter_mapping"
31
+ autoload :TypographicQuotes, "#{__dir__}/asciidoc/typographic_quotes"
31
32
  autoload :Model, "#{__dir__}/asciidoc/model"
32
33
  autoload :Parser, "#{__dir__}/asciidoc/parser"
33
34
  autoload :Transformer, "#{__dir__}/asciidoc/transformer"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coradoc-adoc
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.29
4
+ version: 2.0.31
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -348,6 +348,7 @@ files:
348
348
  - lib/coradoc/asciidoc/transformer/table_cell_builder.rb
349
349
  - lib/coradoc/asciidoc/transformer/table_layout.rb
350
350
  - lib/coradoc/asciidoc/transformer/text_rules.rb
351
+ - lib/coradoc/asciidoc/typographic_quotes.rb
351
352
  - lib/coradoc/asciidoc/version.rb
352
353
  - lib/coradoc/util.rb
353
354
  - lib/coradoc/util/asciidoc.rb
@@ -357,6 +358,7 @@ licenses:
357
358
  metadata:
358
359
  homepage_uri: https://github.com/lutaml/coradoc
359
360
  source_code_uri: https://github.com/lutaml/coradoc
361
+ rubygems_mfa_required: 'true'
360
362
  rdoc_options: []
361
363
  require_paths:
362
364
  - lib