Class: Coradoc::AsciiDoc::Transformer

Inherits:
Parslet::Transform
  • Object
show all
Defined in:
lib/coradoc/asciidoc/transformer.rb,
lib/coradoc/asciidoc/transformer/list_rules.rb,
lib/coradoc/asciidoc/transformer/misc_rules.rb,
lib/coradoc/asciidoc/transformer/text_rules.rb,
lib/coradoc/asciidoc/transformer/block_rules.rb,
lib/coradoc/asciidoc/transformer/header_rules.rb,
lib/coradoc/asciidoc/transformer/inline_rules.rb,
lib/coradoc/asciidoc/transformer/table_layout.rb,
lib/coradoc/asciidoc/transformer/structural_rules.rb,
lib/coradoc/asciidoc/transformer/table_cell_builder.rb,
lib/coradoc/asciidoc/transformer/block_type_classifier.rb,
lib/coradoc/asciidoc/transformer/source_line_extractor.rb,
lib/coradoc/asciidoc/transformer/attribute_list_normalizer.rb

Overview

Parslet::Transform subclass that converts AST to AsciiDoc model objects.

This transformer uses a modular rule system where each group of rules is defined in a separate file for maintainability.

Rule modules (each autoloaded):

  • HeaderRules: Document header, author, revision
  • InlineRules: Inline formatting (bold, italic, etc.)
  • TextRules: Text elements and paragraphs
  • BlockRules: Block elements (example, admonition, etc.)
  • ListRules: List items and list types
  • StructuralRules: Sections, tables, documents
  • MiscRules: Comments, attributes, media elements

Defined Under Namespace

Modules: AttributeListNormalizer, BlockRules, BlockTypeClassifier, HeaderRules, InlineRules, ListRules, MiscRules, SourceLineExtractor, StructuralRules, TableCellBuilder, TableLayout, TextRules

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.build_table_cell(format, content) ⇒ Object



95
96
97
# File 'lib/coradoc/asciidoc/transformer.rb', line 95

def self.build_table_cell(format, content)
  TableCellBuilder.build(format, content)
end

.extract_inline_content(data) ⇒ Object

Helper method for extracting inline content (used by InlineRules)



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/coradoc/asciidoc/transformer.rb', line 48

def self.extract_inline_content(data)
  if data.is_a?(Hash) && data.key?(:content)
    data[:content]
  elsif data.is_a?(Array)
    data.map do |item|
      if item.is_a?(Hash) && item.key?(:text)
        text = item[:text]
        if text.is_a?(Model::Base) && text.class.attributes.key?(:content)
          text.content
        elsif text.is_a?(Model::Base)
          text
        else
          text.to_s
        end
      else
        item
      end
    end
  else
    data
  end
end

.extract_simple_inline_content(data) ⇒ Object

Helper method for extracting simple inline content



72
73
74
75
76
77
78
79
80
81
82
# File 'lib/coradoc/asciidoc/transformer.rb', line 72

def self.extract_simple_inline_content(data)
  if data.is_a?(Hash) && data.key?(:content)
    data[:content]
  elsif data.is_a?(Array)
    data.map do |item|
      item.is_a?(Hash) && item.key?(:text) ? item[:text].to_s : item
    end.join
  else
    data
  end
end

.group_cells_into_rows(cells, explicit_col_count = nil) ⇒ Object



103
104
105
# File 'lib/coradoc/asciidoc/transformer.rb', line 103

def self.group_cells_into_rows(cells, explicit_col_count = nil)
  TableLayout.group_cells_into_rows(cells, explicit_col_count)
end

.infer_column_count(cells) ⇒ Object



107
108
109
# File 'lib/coradoc/asciidoc/transformer.rb', line 107

def self.infer_column_count(cells)
  TableLayout.infer_column_count(cells)
end

.legacy_transform(syntax_tree) ⇒ Object

Deprecated.

Use transform instead

Legacy transform method (deprecated)



217
218
219
# File 'lib/coradoc/asciidoc/transformer.rb', line 217

def self.legacy_transform(syntax_tree)
  new.apply(syntax_tree)
end

.lines_to_text_elements(lines) ⇒ Object

Convert parser-output "lines" into an array of TextElement model objects. Each line is one of:

- { text: <Array or scalar>, line_break: <str> }
- any other shape (passed through unchanged)

Used by the paragraph and reviewer_note rules to share the same line-shape handling (DRY).



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/coradoc/asciidoc/transformer.rb', line 130

def self.lines_to_text_elements(lines)
  # Parslet may deliver `lines` as a single Hash (one line captured)
  # or an Array of Hashes (multiple lines). `Array(hash)` converts
  # to nested pairs, which we don't want — normalize explicitly.
  normalized = case lines
               when nil then []
               when Array then lines
               when Hash then [lines]
               else Array(lines)
               end
  normalized.map do |line|
    next line unless line.is_a?(Hash) && line.key?(:text)

    text_content = line[:text]
    # `text_content` may be a single Hash (one inline), an Array
    # of Hashes (multiple inlines), or a String. Normalize to an
    # Array of Hashes for uniform processing.
    text_array = case text_content
                 when Array then text_content
                 when Hash then [text_content]
                 when String then [{ text: text_content }]
                 else [{ text: text_content.to_s }]
                 end
    transformed = text_array.map do |item|
      item.is_a?(Hash) ? new.apply(item) : item
    end

    Model::TextElement.new(
      content: transformed,
      line_break: line[:line_break]
    )
  end
end

.parse_block_content(text) ⇒ Object



91
92
93
# File 'lib/coradoc/asciidoc/transformer.rb', line 91

def self.parse_block_content(text)
  TableCellBuilder.parse_block_content(text)
end

.parse_cols_attribute(attrs) ⇒ Object



99
100
101
# File 'lib/coradoc/asciidoc/transformer.rb', line 99

def self.parse_cols_attribute(attrs)
  TableLayout.parse_cols_attribute(attrs)
end

.parse_inline_content(text, style = nil) ⇒ Object

Helper method for parsing inline content from raw text. Kept as a thin delegator for backwards compatibility; implementation lives in TableCellBuilder (table cells are the primary consumer).



87
88
89
# File 'lib/coradoc/asciidoc/transformer.rb', line 87

def self.parse_inline_content(text, style = nil)
  TableCellBuilder.parse_inline_content(text, style)
end

.regroup_table_rows(rows, attrs = nil) ⇒ Object



111
112
113
# File 'lib/coradoc/asciidoc/transformer.rb', line 111

def self.regroup_table_rows(rows, attrs = nil)
  TableLayout.regroup_table_rows(rows, attrs)
end

.split_lines_on_hard_break(lines) ⇒ Object

Split :lines entries that span multiple source lines (via text_any greedy-matching across hard_line_break). Each call returns one entry per logical source line: when a line entry's :text array contains a hard_line_break, the entry is split at each hard_break, producing N+1 entries for N hard breaks. The hard_break is preserved as the :line_break of its segment.

Without this, a dd source like:

`term::` First line. +
+
attached

produces a single :lines entry whose text contains ["First line.", hard_line_break(" +\n"), "+", "attached..."], which the renderer joins into "First line. + attached" — the + continuation marker ends up inside the dd's text instead of triggering the attached-block path.

Used by the dlist transformer before lines_to_text_elements.



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/coradoc/asciidoc/transformer.rb', line 182

def self.split_lines_on_hard_break(lines)
  normalized = case lines
               when nil then []
               when Array then lines
               when Hash then [lines]
               else Array(lines)
               end
  normalized.flat_map do |line|
    next [line] unless line.is_a?(Hash) && line[:text].is_a?(Array)
    next [line] unless line[:text].any? { |p| p.is_a?(Hash) && p.key?(:hard_line_break) }

    segments = []
    current_text = []
    current_break = nil
    line[:text].each do |part|
      if part.is_a?(Hash) && part.key?(:hard_line_break)
        segments << { text: current_text, line_break: part[:hard_line_break] }
        current_text = []
        current_break = :had_hard_break
      else
        current_text << part
      end
    end
    # Final segment gets the original line's line_break (or empty
    # if it ended with a hard break).
    final_break = current_break == :had_hard_break ? "\n" : line[:line_break]
    segments << { text: current_text, line_break: final_break }
    # Drop leading empty segment if the line started with a hard break
    # (shouldn't happen in practice but be defensive).
    segments.reject { |s| s[:text].empty? && s != segments.last }
  end
end

.transform(syntax_tree) ⇒ Object

Transform a syntax tree using this transformer's rules

Parameters:

  • syntax_tree (Hash, Array)

    The AST from the parser

Returns:

  • (Object)

    The transformed model object(s)



119
120
121
# File 'lib/coradoc/asciidoc/transformer.rb', line 119

def self.transform(syntax_tree)
  new.apply(syntax_tree)
end

Instance Method Details

#call_on_match(bindings, block) ⇒ Object

Single deepening seam for source_line propagation. Parslet's transform pipeline funnels every rule block through call_on_match(bindings, block); overriding it lets us post- process the block's result and inject source_line from the matched bindings, so individual rules no longer need to call SourceLineExtractor.extract themselves (DRY — was 47 call sites across 7 rule files).

Safety:

* Only Model::Base results get an injection — Strings, Arrays,
and intermediate hashes pass through unchanged.
* Existing explicit source_line values are preserved — the
injection is fill-in-the-blank, never overwrite.
* No Slice in the bindings → SourceLineExtractor returns nil,
no injection (synthetic transformations stay clean).


236
237
238
239
240
241
242
243
244
# File 'lib/coradoc/asciidoc/transformer.rb', line 236

def call_on_match(bindings, block)
  result = super
  return result unless result.is_a?(Model::Base)
  return result if result.source_line

  line = self.class::SourceLineExtractor.extract(bindings)
  result.source_line = line if line
  result
end