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.



384
385
386
# File 'lib/sourcerer/asciidoc.rb', line 384

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)


293
294
295
296
297
298
299
300
301
# File 'lib/sourcerer/asciidoc.rb', line 293

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.



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# File 'lib/sourcerer/asciidoc.rb', line 439

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)


283
284
285
286
287
# File 'lib/sourcerer/asciidoc.rb', line 283

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)


332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/sourcerer/asciidoc.rb', line 332

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.



92
93
94
95
96
97
98
99
100
# File 'lib/sourcerer/asciidoc.rb', line 92

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.



399
400
401
# File 'lib/sourcerer/asciidoc.rb', line 399

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)


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

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.



181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/sourcerer/asciidoc.rb', line 181

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.



164
165
166
167
168
169
170
171
172
# File 'lib/sourcerer/asciidoc.rb', line 164

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, user_only: false) ⇒ Hash

Loads AsciiDoc attributes from a document header as a Hash.

Parameters:

  • path (String)

    The path to the AsciiDoc file.

  • user_only (Boolean) (defaults to: false)

    When true, strips Asciidoctor built-in attributes and returns only user-defined attributes. Defaults to false.

Returns:

  • (Hash)

    A hash of the document attributes.



49
50
51
52
53
54
# File 'lib/sourcerer/asciidoc.rb', line 49

def self.load_attributes path, user_only: false
  doc = Asciidoctor.load_file(path, safe: :unsafe)
  return AttributesFilter.user_attributes(doc) if user_only

  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.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/sourcerer/asciidoc.rb', line 63

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: nil, **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.

  • markdown_converter (#call, nil) (defaults to: nil)

    Callable that accepts (html, markdown_options). Defaults to Sourcerer::MarkDownGrade.convert_html when nil.

  • 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.

  • 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.



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
252
253
254
255
256
257
258
# File 'lib/sourcerer/asciidoc.rb', line 208

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

  markdown_converter ||= ->(html, opts) { Sourcerer::MarkDownGrade.convert_html(html, opts || {}) }

  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.



389
390
391
# File 'lib/sourcerer/asciidoc.rb', line 389

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)


308
309
310
311
312
313
# File 'lib/sourcerer/asciidoc.rb', line 308

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.



394
395
396
# File 'lib/sourcerer/asciidoc.rb', line 394

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)>)


319
320
321
322
323
324
325
326
# File 'lib/sourcerer/asciidoc.rb', line 319

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.



404
405
406
# File 'lib/sourcerer/asciidoc.rb', line 404

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)


379
380
381
# File 'lib/sourcerer/asciidoc.rb', line 379

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