Module: Sourcerer::AsciiDoc

Defined in:
lib/sourcerer/asciidoc.rb

Overview

AsciiDoc-focused primitives for attribute loading, region extraction, includes, and converter-oriented utilities. AsciiDoc processing module for document conversion and content extraction.

This module provides utilities for working with AsciiDoc files, including:

  • Loading document attributes and snippets via include directives

  • Extracting tagged content from files

  • Converting AsciiDoc to HTML, manpage, and Markdown formats

  • Managing YAML front matter in AsciiDoc documents

  • Extracting commands from code blocks with specific roles

Examples:

Loading attributes from an AsciiDoc file

attributes = AsciiDoc.load_attributes('path/to/file.adoc')

Converting AsciiDoc to Markdown

result = AsciiDoc.mark_down_grade(
  'source.adoc',
  markdown_output_path: 'output.md',
  include_frontmatter: true
)

Extracting tagged content

content = AsciiDoc.extract_tagged_content(
  'file.adoc',
  tags: ['snippet1', 'snippet2']
)

See Also:

Defined Under Namespace

Modules: AttributesFilter

Constant Summary collapse

YAML_FRONTMATTER_REGEXP =
Sourcerer::YamlFrontmatter::REGEXP
YAML_FRONT_MATTER_REGEXP =
YAML_FRONTMATTER_REGEXP
PAGE_ATTRIBUTE_PREFIX =
'page-'

Class Method Summary collapse

Class Method Details

.compose_front_matter_block(frontmatter) ⇒ Object

Compatibility alias.



377
378
379
# File 'lib/sourcerer/asciidoc.rb', line 377

def self.compose_front_matter_block frontmatter
  compose_frontmatter_block(frontmatter)
end

.compose_frontmatter_block(frontmatter) ⇒ String?

Build YAML front matter block content from a hash.

Parameters:

  • frontmatter (Hash)

Returns:

  • (String, nil)


286
287
288
289
290
291
292
293
294
# File 'lib/sourcerer/asciidoc.rb', line 286

def self.compose_frontmatter_block frontmatter
  return nil if frontmatter.nil? || frontmatter.empty?

  yaml_payload = Psych.dump(frontmatter, nil, { line_width: -1 })
  yaml_payload = yaml_payload.sub(/\A---\s*\n/, '')
  yaml_payload = yaml_payload.sub(/\n\.\.\.\s*\z/, "\n")

  "---\n#{yaml_payload}---"
end

.extract_commands(file_path, role: 'testable') ⇒ Array<String>

Extracts commands from listing and literal blocks with a specific role.

Parameters:

  • file_path (String)

    The path to the AsciiDoc file.

  • role (String) (defaults to: 'testable')

    The role to look for.

Returns:

  • (Array<String>)

    An array of command groups.



432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
# File 'lib/sourcerer/asciidoc.rb', line 432

def self.extract_commands file_path, role: 'testable'
  doc = Asciidoctor.load_file(file_path, safe: :unsafe)
  command_groups = []
  current_group = []

  blocks = doc.find_by(context: :listing) + doc.find_by(context: :literal)

  blocks.each do |block|
    next unless block.has_role?(role)

    commands = process_block_content(block.content)
    if block.has_role?('testable-newshell')
      command_groups << current_group.join("\n") unless current_group.empty?
      command_groups << commands.join("\n") unless commands.empty?
      current_group = []
    else
      current_group.concat(commands)
    end
  end

  command_groups << current_group.join("\n") unless current_group.empty?
  command_groups
end

.extract_frontmatter(source_text, document_attributes) ⇒ Hash

Extract front matter from AsciiDoc text using YAML fences and page-* attributes.

Parameters:

  • source_text (String)
  • document_attributes (Hash)

Returns:

  • (Hash)


276
277
278
279
280
# File 'lib/sourcerer/asciidoc.rb', line 276

def self.extract_frontmatter source_text, document_attributes
  page_attrs = extract_page_attributes(document_attributes)
  yaml_frontmatter = extract_yaml_frontmatter(source_text)
  page_attrs.merge(yaml_frontmatter)
end

.extract_page_attributes(document_attributes) ⇒ Hash

Parse page-* attributes using Asciidoctor-resolved document attributes.

Parameters:

  • document_attributes (Hash)

Returns:

  • (Hash)


325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/sourcerer/asciidoc.rb', line 325

def self.extract_page_attributes document_attributes
  attributes = {}
  prefix = PAGE_ATTRIBUTE_PREFIX

  document_attributes.each do |key, value|
    next unless key.start_with?(prefix)

    normalized_key = key.sub(/\A#{Regexp.escape(prefix)}/, '')
    attributes[normalized_key] = coerce_page_attribute_value(value)
  end

  attributes
end

.extract_tagged_content(path_to_tagged_adoc, **options) ⇒ String

Extracts tagged content from a file.

Parameters:

  • path_to_tagged_adoc (String)

    The path to the file with tagged content.

  • tag (String)

    A single tag to extract.

  • tags (Array<String>)

    An array of tags to extract.

  • comment_prefix (String)

    The prefix for comment lines.

  • comment_suffix (String)

    The suffix for comment lines.

  • skip_comments (Boolean)

    Whether to skip comment lines in the output.

Returns:

  • (String)

    The extracted content.



88
89
90
91
92
93
94
95
96
# File 'lib/sourcerer/asciidoc.rb', line 88

def self.extract_tagged_content path_to_tagged_adoc, **options
  options = normalize_extract_tagged_content_options(options)
  tags = normalize_extract_tags(options[:tag], options[:tags])
  collect_tagged_content(
    path_to_tagged_adoc,
    tags: tags,
    comment_prefix: options[:comment_prefix],
    skip_comments: options[:skip_comments])
end

.extract_yaml_front_matter(source_text) ⇒ Object

Compatibility alias.



392
393
394
# File 'lib/sourcerer/asciidoc.rb', line 392

def self.extract_yaml_front_matter source_text
  extract_yaml_frontmatter(source_text)
end

.extract_yaml_frontmatter(source_text) ⇒ Hash

Parse optional YAML front matter fenced with — at the top of source content.

Parameters:

  • source_text (String)

Returns:

  • (Hash)


364
365
366
# File 'lib/sourcerer/asciidoc.rb', line 364

def self.extract_yaml_frontmatter source_text
  Sourcerer::YamlFrontmatter.extract(source_text)
end

.generate_html(source_adoc, target_html, backend: 'asciidoctor-html5s', header_footer: false) ⇒ String

Generates HTML from an AsciiDoc source file.

Parameters:

  • source_adoc (String)

    The path to the source AsciiDoc file.

  • target_html (String)

    The path to the target HTML file.

  • backend (String) (defaults to: 'asciidoctor-html5s')

    Backend selector (‘asciidoctor-html5s` or `html5`).

  • header_footer (Boolean) (defaults to: false)

    Whether to emit full HTML document wrapper.

Returns:

  • (String)

    The backend used for generation.



177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/sourcerer/asciidoc.rb', line 177

def self.generate_html source_adoc, target_html, backend: 'asciidoctor-html5s', header_footer: false
  FileUtils.mkdir_p(File.dirname(target_html))

  selected_backend = resolve_html_backend(backend)
  Asciidoctor.convert_file(
    source_adoc,
    backend: selected_backend,
    safe: :unsafe,
    header_footer: header_footer,
    to_file: target_html)

  selected_backend
end

.generate_manpage(source_adoc, target_manpage) ⇒ Object

Generates a manpage from an AsciiDoc source file.

Parameters:

  • source_adoc (String)

    The path to the source AsciiDoc file.

  • target_manpage (String)

    The path to the target manpage file.



160
161
162
163
164
165
166
167
168
# File 'lib/sourcerer/asciidoc.rb', line 160

def self.generate_manpage source_adoc, target_manpage
  FileUtils.mkdir_p(File.dirname(target_manpage))
  Asciidoctor.convert_file(
    source_adoc,
    backend: 'manpage',
    safe: :unsafe,
    standalone: true,
    to_file: target_manpage)
end

.load_attributes(path) ⇒ Hash

Loads AsciiDoc attributes from a document header as a Hash.

Parameters:

  • path (String)

    The path to the AsciiDoc file.

Returns:

  • (Hash)

    A hash of the document attributes.



47
48
49
50
# File 'lib/sourcerer/asciidoc.rb', line 47

def self.load_attributes path
  doc = Asciidoctor.load_file(path, safe: :unsafe)
  doc.attributes
end

.load_include(path_to_main_adoc, tag: nil, tags: [], leveloffset: nil) ⇒ String

Loads a snippet from an AsciiDoc file using an ‘include::` directive.

Parameters:

  • path_to_main_adoc (String)

    The path to the main AsciiDoc file.

  • tag (String) (defaults to: nil)

    A single tag to include.

  • tags (Array<String>) (defaults to: [])

    An array of tags to include.

  • leveloffset (Integer) (defaults to: nil)

    The level offset for the include.

Returns:

  • (String)

    The content of the included snippet.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/sourcerer/asciidoc.rb', line 59

def self.load_include path_to_main_adoc, tag: nil, tags: [], leveloffset: nil
  opts = []
  opts << "tag=#{tag}" if tag
  opts << "tags=#{tags.join(',')}" if tags.any?
  opts << "leveloffset=#{leveloffset}" if leveloffset

  snippet_doc = <<~ADOC
    include::#{path_to_main_adoc}[#{opts.join(', ')}]
  ADOC

  doc = Asciidoctor.load(
    snippet_doc,
    safe: :unsafe,
    base_dir: File.expand_path('.'),
    header_footer: false,
    attributes: { 'source-highlighter' => nil })

  doc.blocks.map(&:content).join("\n")
end

.mark_down_grade(source_path, markdown_output_path = nil, markdown_converter:, **options) ⇒ Hash

Convert AsciiDoc source to Markdown through an interim HTML conversion.

Parameters:

  • source_path (String)

    Path to AsciiDoc source file.

  • markdown_output_path (String, nil) (defaults to: nil)

    Optional markdown output path.

  • html_output_path (String, nil)

    Optional HTML output path.

  • backend (String)

    HTML backend request (‘html5` or `asciidoctor-html5s`).

  • header_footer (Boolean)

    Whether interim HTML should include document wrapper.

  • include_frontmatter (Boolean)

    Whether to prepend markdown YAML front matter.

  • markdown_options (Hash)

    Options passed to markdown converter.

  • markdown_converter (#call)

    Callable that accepts ‘(html, markdown_options)`.

  • convert_tables_to_markdown (Boolean)

    Convert all tables to markdown UNLESS they have .no-markdown class.

Returns:

  • (Hash)

    Conversion result containing markdown, frontmatter, and backend info.



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/sourcerer/asciidoc.rb', line 203

def self.mark_down_grade source_path, markdown_output_path=nil, markdown_converter:, **options
  options = normalize_mark_down_grade_options(options)

  source_text = File.read(source_path)
  conversion_source_text = strip_yaml_frontmatter(source_text)
  selected_backend = resolve_html_backend(options[:backend])
  document = load_document_for_markdown_grade(
    source_path,
    conversion_source_text,
    backend: selected_backend,
    header_footer: options[:header_footer],
    attributes: options[:attributes])

  frontmatter = options[:include_frontmatter] ? extract_frontmatter(source_text, document.attributes) : {}
  html_body = document.convert
  html_body = ensure_document_title(html_body, document.doctitle)
  html_with_frontmatter = options[:include_frontmatter] ? prepend_frontmatter(html_body, frontmatter) : html_body

  if options[:html_output_path]
    FileUtils.mkdir_p(File.dirname(options[:html_output_path]))
    File.write(options[:html_output_path], html_with_frontmatter)
  end

  frontmatter_block, html_for_markdown = split_frontmatter_block(html_with_frontmatter)
  # Build options for markdown converter, including table conversion mode
  converter_options = options[:markdown_options].dup
  # Pass frontmatter table conversion setting through converter_options
  if options.key?(:convert_tables_to_markdown)
    converter_options[:convert_tables_to_markdown] =
      options[:convert_tables_to_markdown]
  end
  if converter_options[:convert_tables_to_markdown].nil? && frontmatter.key?('tables-to-markdown')
    converter_options[:convert_tables_to_markdown] = frontmatter['tables-to-markdown']
  end
  markdown_body = markdown_converter.call(html_for_markdown, converter_options)
  markdown = frontmatter_block ? "#{frontmatter_block}\n\n#{markdown_body}" : markdown_body

  if markdown_output_path
    FileUtils.mkdir_p(File.dirname(markdown_output_path))
    File.write(markdown_output_path, markdown)
  end

  {
    markdown: markdown,
    frontmatter: frontmatter,
    requested_backend: options[:backend],
    used_backend: selected_backend
  }
end

.prepend_front_matter(content, frontmatter) ⇒ Object

Compatibility alias.



382
383
384
# File 'lib/sourcerer/asciidoc.rb', line 382

def self.prepend_front_matter content, frontmatter
  prepend_frontmatter(content, frontmatter)
end

.prepend_frontmatter(content, frontmatter) ⇒ String

Prepend front matter block to content.

Parameters:

  • content (String)
  • frontmatter (Hash)

Returns:

  • (String)


301
302
303
304
305
306
# File 'lib/sourcerer/asciidoc.rb', line 301

def self.prepend_frontmatter content, frontmatter
  block = compose_frontmatter_block(frontmatter)
  return content unless block

  "#{block}\n\n#{content}"
end

.split_front_matter(content) ⇒ Object

Compatibility alias.



387
388
389
# File 'lib/sourcerer/asciidoc.rb', line 387

def self.split_front_matter content
  split_frontmatter_block(content)
end

.split_frontmatter_block(content) ⇒ Array<(String, String)>

Split front matter block from content if present.

Parameters:

  • content (String)

Returns:

  • (Array<(String, String)>)


312
313
314
315
316
317
318
319
# File 'lib/sourcerer/asciidoc.rb', line 312

def self.split_frontmatter_block content
  match = content.match(YAML_FRONTMATTER_REGEXP)
  return [nil, content] unless match

  full_block = "#{match[1]}#{match[2]}".strip
  remainder = content.sub(YAML_FRONTMATTER_REGEXP, '').sub(/\A\n+/, '')
  [full_block, remainder]
end

.strip_yaml_front_matter(source_text) ⇒ Object

Compatibility alias.



397
398
399
# File 'lib/sourcerer/asciidoc.rb', line 397

def self.strip_yaml_front_matter source_text
  strip_yaml_frontmatter(source_text)
end

.strip_yaml_frontmatter(source_text) ⇒ String

Remove leading YAML front matter fence block from AsciiDoc source.

Parameters:

  • source_text (String)

Returns:

  • (String)


372
373
374
# File 'lib/sourcerer/asciidoc.rb', line 372

def self.strip_yaml_frontmatter source_text
  Sourcerer::YamlFrontmatter.strip(source_text)
end