Module: Asciidoctor

Defined in:
lib/asciidoctor.rb,
lib/asciidoctor/rx.rb,
lib/asciidoctor/list.rb,
lib/asciidoctor/load.rb,
lib/asciidoctor/block.rb,
lib/asciidoctor/table.rb,
lib/asciidoctor/inline.rb,
lib/asciidoctor/parser.rb,
lib/asciidoctor/reader.rb,
lib/asciidoctor/writer.rb,
lib/asciidoctor/convert.rb,
lib/asciidoctor/helpers.rb,
lib/asciidoctor/logging.rb,
lib/asciidoctor/section.rb,
lib/asciidoctor/timings.rb,
lib/asciidoctor/version.rb,
lib/asciidoctor/callouts.rb,
lib/asciidoctor/document.rb,
lib/asciidoctor/converter.rb,
lib/asciidoctor/rouge_ext.rb,
lib/asciidoctor/extensions.rb,
lib/asciidoctor/cli/invoker.rb,
lib/asciidoctor/cli/options.rb,
lib/asciidoctor/stylesheets.rb,
lib/asciidoctor/substitutors.rb,
lib/asciidoctor/abstract_node.rb,
lib/asciidoctor/path_resolver.rb,
lib/asciidoctor/abstract_block.rb,
lib/asciidoctor/attribute_list.rb,
lib/asciidoctor/converter/html5.rb,
lib/asciidoctor/converter/manpage.rb,
lib/asciidoctor/converter/docbook5.rb,
lib/asciidoctor/converter/template.rb,
lib/asciidoctor/syntax_highlighter.rb,
lib/asciidoctor/converter/composite.rb,
lib/asciidoctor/syntax_highlighter/rouge.rb,
lib/asciidoctor/syntax_highlighter/coderay.rb,
lib/asciidoctor/syntax_highlighter/prettify.rb,
lib/asciidoctor/syntax_highlighter/pygments.rb,
lib/asciidoctor/syntax_highlighter/highlightjs.rb,
lib/asciidoctor/syntax_highlighter/html_pipeline.rb

Overview

The main application interface (API) for Asciidoctor. This API provides methods to parse AsciiDoc content and convert it to various output formats using built-in or third-party converters or Tilt-supported templates.

An AsciiDoc document can be as simple as a single line of content, though it more commonly starts with a document header that declares the document title and document attribute definitions. The document header is then followed by zero or more section titles, optionally nested, to organize the paragraphs, blocks, lists, etc. of the document.

By default, the processor converts the AsciiDoc document to HTML 5 using a built-in converter. However, this behavior can be changed by specifying a different backend (e.g., docbook). A backend is a keyword for an output format (e.g., DocBook). That keyword, in turn, is used to select a converter, which carries out the request to convert the document to that format.

In addition to this API, Asciidoctor also provides a command-line interface (CLI) named asciidoctor for converting AsciiDoc content. See the provided man(ual) page for usage and options.

Examples:

# Convert an AsciiDoc file
Asciidoctor.convert_file 'document.adoc', safe: :safe
# Convert an AsciiDoc string
puts Asciidoctor.convert "I'm using *Asciidoctor* version {asciidoctor-version}.", safe: :safe
# Convert an AsciiDoc file using Tilt-supported templates
Asciidoctor.convert_file 'document.adoc', safe: :safe, template_dir: '/path/to/templates'
# Parse an AsciiDoc file into a document object
doc = Asciidoctor.load_file 'document.adoc', safe: :safe
# Parse an AsciiDoc string into a document object
doc = Asciidoctor.load "= Document Title\n\nfirst paragraph\n\nsecond paragraph", safe: :safe

Defined Under Namespace

Modules: Cli, Compliance, Converter, Extensions, LoggerManager, Logging, RougeExt, Rx, SafeMode, Substitutors, SyntaxHighlighter, VoidWriter, Writer Classes: AbstractBlock, AbstractNode, AttributeList, Block, Callouts, Document, Inline, List, ListItem, Logger, MemoryLogger, NullLogger, Parser, PathResolver, PreprocessorReader, Reader, Section, Stylesheets, Table, Timings

Constant Summary collapse

RUBY_ENGINE_OPAL =

alias the RUBY_ENGINE constant inside the Asciidoctor namespace and define a precomputed alias for runtime

(RUBY_ENGINE = ::RUBY_ENGINE) == 'opal'
ROOT_DIR =

The absolute root directory of the Asciidoctor RubyGem

::File.dirname ::File.absolute_path __dir__
LIB_DIR =

The absolute lib directory of the Asciidoctor RubyGem

::File.join ROOT_DIR, 'lib'
DATA_DIR =

The absolute data directory of the Asciidoctor RubyGem

::File.join ROOT_DIR, 'data'
USER_HOME =

The user’s home directory, as best we can determine it IMPORTANT this rescue is required for running Asciidoctor on GitHub.com

::Dir.home rescue (::ENV['HOME'] || ::Dir.pwd)
LF =

The newline character used for output; stored in constant table as an optimization

?\n
NULL =

The null character to use for splitting attribute values

?\0
TAB =

String for matching tab character

?\t
MAX_INT =

Maximum integer value for “boundless” operations; equal to MAX_SAFE_INTEGER in JavaScript

9007199254740991
UTF_8 =

Alias UTF_8 encoding for convenience / speed

::Encoding::UTF_8
BOM_BYTES_UTF_8 =

Byte arrays for UTF-* Byte Order Marks

[0xef, 0xbb, 0xbf]
BOM_BYTES_UTF_16LE =
[0xff, 0xfe]
BOM_BYTES_UTF_16BE =
[0xfe, 0xff]
FILE_READ_MODE =

The mode to use when opening a file for reading

RUBY_ENGINE_OPAL ? 'r' : 'rb:UTF-8:UTF-8'
URI_READ_MODE =

The mode to use when opening a URI for reading

FILE_READ_MODE
FILE_WRITE_MODE =

The mode to use when opening a file for writing

RUBY_ENGINE_OPAL ? 'w' : 'wb:UTF-8'
DEFAULT_DOCTYPE =

The default document type Can influence markup generated by the converters

'article'
DEFAULT_BACKEND =

The backend determines the format of the converted output, default to html5

'html5'
DEFAULT_STYLESHEET_KEYS =
['', 'DEFAULT'].to_set
DEFAULT_STYLESHEET_NAME =
'asciidoctor.css'
BACKEND_ALIASES =

Pointers to the preferred version for a given backend.

{
  'html' => 'html5',
  'docbook' => 'docbook5'
}
DEFAULT_PAGE_WIDTHS =

Default page widths for calculating absolute widths

{
  'docbook' => 425
}
DEFAULT_EXTENSIONS =

Default extensions for the respective base backends

{
  'html' => '.html',
  'docbook' => '.xml',
  'pdf' => '.pdf',
  'epub' => '.epub',
  'manpage' => '.man',
  'asciidoc' => '.adoc'
}
ASCIIDOC_EXTENSIONS =

A map of file extensions that are recognized as AsciiDoc documents TODO .txt should be deprecated

{
  '.adoc' => true,
  '.asciidoc' => true,
  '.asc' => true,
  '.ad' => true,
  # TODO .txt should be deprecated
  '.txt' => true
}
SETEXT_SECTION_LEVELS =
{
  '=' => 0,
  '-' => 1,
  '~' => 2,
  '^' => 3,
  '+' => 4
}
ADMONITION_STYLES =
['NOTE', 'TIP', 'IMPORTANT', 'WARNING', 'CAUTION'].to_set
ADMONITION_STYLE_HEADS =
::Set.new.tap {|accum| ADMONITION_STYLES.each {|s| accum << s.chr } }
PARAGRAPH_STYLES =
['comment', 'example', 'literal', 'listing', 'normal', 'open', 'pass', 'quote', 'sidebar', 'source', 'verse', 'abstract', 'partintro'].to_set
VERBATIM_STYLES =
['literal', 'listing', 'source', 'verse'].to_set
DELIMITED_BLOCKS =
{
  '--' => [:open, ['comment', 'example', 'literal', 'listing', 'pass', 'quote', 'sidebar', 'source', 'verse', 'admonition', 'abstract', 'partintro'].to_set],
  '----' => [:listing, ['literal', 'source'].to_set],
  '....' => [:literal, ['listing', 'source'].to_set],
  '====' => [:example, ['admonition'].to_set],
  '****' => [:sidebar, ::Set.new],
  '____' => [:quote, ['verse'].to_set],
  '++++' => [:pass, ['stem', 'latexmath', 'asciimath'].to_set],
  '|===' => [:table, ::Set.new],
  ',===' => [:table, ::Set.new],
  ':===' => [:table, ::Set.new],
  '!===' => [:table, ::Set.new],
  '////' => [:comment, ::Set.new],
  '```' => [:fenced_code, ::Set.new]
}
DELIMITED_BLOCK_HEADS =
{}.tap {|accum| DELIMITED_BLOCKS.each_key {|k| accum[k.slice 0, 2] = true } }
DELIMITED_BLOCK_TAILS =
{}.tap {|accum| DELIMITED_BLOCKS.each_key {|k| accum[k] = k[k.length - 1] if k.length == 4 } }
CAPTION_ATTRIBUTE_NAMES =

NOTE the ‘figure’ key as a string is historical and used by image blocks

{ example: 'example-caption', 'figure' => 'figure-caption', listing: 'listing-caption', table: 'table-caption' }
LAYOUT_BREAK_CHARS =
{
  '\'' => :thematic_break,
  '<' => :page_break
}
MARKDOWN_THEMATIC_BREAK_CHARS =
{
  '-' => :thematic_break,
  '*' => :thematic_break,
  '_' => :thematic_break
}
HYBRID_LAYOUT_BREAK_CHARS =
LAYOUT_BREAK_CHARS.merge MARKDOWN_THEMATIC_BREAK_CHARS
NESTABLE_LIST_CONTEXTS =

LIST_CONTEXTS = [:ulist, :olist, :dlist, :colist]

Returns:

  • (:ulist, :olist, :dlist, :colist)
[:ulist, :olist, :dlist]
ORDERED_LIST_STYLES =

TODO validate use of explicit style name above ordered list (this list is for selecting an implicit style)

[:arabic, :loweralpha, :lowerroman, :upperalpha, :upperroman]
ORDERED_LIST_KEYWORDS =
{
  #'arabic' => '1',
  #'decimal' => '1',
  'loweralpha' => 'a',
  'lowerroman' => 'i',
  #'lowergreek' => 'a',
  'upperalpha' => 'A',
  'upperroman' => 'I'
}
ATTR_REF_HEAD =
'{'
LIST_CONTINUATION =
'+'
HARD_LINE_BREAK =

NOTE AsciiDoc.py allows + to be preceded by TAB; Asciidoctor does not

' +'
LINE_CONTINUATION =
' \\'
LINE_CONTINUATION_LEGACY =
' +'
BLOCK_MATH_DELIMITERS =
{
  asciimath: ['\$', '\$'],
  latexmath: ['\[', '\]'],
}
INLINE_MATH_DELIMITERS =
{
  asciimath: ['\$', '\$'],
  latexmath: ['\(', '\)'],
}
FONT_AWESOME_VERSION =
'4.7.0'
HIGHLIGHT_JS_VERSION =
'9.18.3'
MATHJAX_VERSION =
'2.7.9'
DEFAULT_ATTRIBUTES =
{
  'appendix-caption' => 'Appendix',
  'appendix-refsig' => 'Appendix',
  'caution-caption' => 'Caution',
  'chapter-refsig' => 'Chapter',
  #'encoding' => 'UTF-8',
  'example-caption' => 'Example',
  'figure-caption' => 'Figure',
  'important-caption' => 'Important',
  'last-update-label' => 'Last updated',
  #'listing-caption' => 'Listing',
  'note-caption' => 'Note',
  'part-refsig' => 'Part',
  #'preface-title' => 'Preface',
  'prewrap' => '',
  'sectids' => '',
  'section-refsig' => 'Section',
  'table-caption' => 'Table',
  'tip-caption' => 'Tip',
  'toc-placement' => 'auto',
  'toc-title' => 'Table of Contents',
  'untitled-label' => 'Untitled',
  'version-label' => 'Version',
  'warning-caption' => 'Warning',
}
FLEXIBLE_ATTRIBUTES =

attributes which be changed throughout the flow of the document (e.g., sectnums)

['sectnums']
INTRINSIC_ATTRIBUTES =
{
  'startsb' => '[',
  'endsb' => ']',
  'vbar' => '|',
  'caret' => '^',
  'asterisk' => '*',
  'tilde' => '~',
  'plus' => '&#43;',
  'backslash' => '\\',
  'backtick' => '`',
  'blank' => '',
  'empty' => '',
  'sp' => ' ',
  'two-colons' => '::',
  'two-semicolons' => ';;',
  'nbsp' => '&#160;',
  'deg' => '&#176;',
  'zwsp' => '&#8203;',
  'quot' => '&#34;',
  'apos' => '&#39;',
  'lsquo' => '&#8216;',
  'rsquo' => '&#8217;',
  'ldquo' => '&#8220;',
  'rdquo' => '&#8221;',
  'wj' => '&#8288;',
  'brvbar' => '&#166;',
  'pp' => '&#43;&#43;',
  'cpp' => 'C&#43;&#43;',
  'amp' => '&',
  'lt' => '<',
  'gt' => '>'
}
CC_ALL =

CC_ALL is any character, including newlines (must be accompanied by multiline regexp flag)

'.'
CC_ANY =

CC_ANY is any character except newlines

'.'
CC_EOL =
'$'
CC_ALPHA =
CG_ALPHA = '\p{Alpha}'
CC_ALNUM =
CG_ALNUM = '\p{Alnum}'
CG_BLANK =
'\p{Blank}'
CC_WORD =
CG_WORD = '\p{Word}'
QUOTE_SUBS =
{}.tap do |accum|
  # unconstrained quotes:: can appear anywhere
  # constrained quotes:: must be bordered by non-word characters
  # NOTE these substitutions are processed in the order they appear here and
  # the order in which they are replaced is important
  accum[false] = normal = [
    # **strong**
    [:strong, :unconstrained, /\\?(?:\[([^\]]+)\])?\*\*(#{CC_ALL}+?)\*\*/m],
    # *strong*
    [:strong, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?\*(\S|\S#{CC_ALL}*?\S)\*(?!#{CG_WORD})/m],
    # "`double-quoted`"
    [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?"`(\S|\S#{CC_ALL}*?\S)`"(?!#{CG_WORD})/m],
    # '`single-quoted`'
    [:single, :constrained, /(^|[^#{CC_WORD};:`}])(?:\[([^\]]+)\])?'`(\S|\S#{CC_ALL}*?\S)`'(?!#{CG_WORD})/m],
    # ``monospaced``
    [:monospaced, :unconstrained, /\\?(?:\[([^\]]+)\])?``(#{CC_ALL}+?)``/m],
    # `monospaced`
    [:monospaced, :constrained, /(^|[^#{CC_WORD};:"'`}])(?:\[([^\]]+)\])?`(\S|\S#{CC_ALL}*?\S)`(?![#{CC_WORD}"'`])/m],
    # __emphasis__
    [:emphasis, :unconstrained, /\\?(?:\[([^\]]+)\])?__(#{CC_ALL}+?)__/m],
    # _emphasis_
    [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?_(\S|\S#{CC_ALL}*?\S)_(?!#{CG_WORD})/m],
    # ##mark## (referred to in AsciiDoc.py as unquoted)
    [:mark, :unconstrained, /\\?(?:\[([^\]]+)\])?##(#{CC_ALL}+?)##/m],
    # #mark# (referred to in AsciiDoc.py as unquoted)
    [:mark, :constrained, /(^|[^#{CC_WORD}&;:}])(?:\[([^\]]+)\])?#(\S|\S#{CC_ALL}*?\S)#(?!#{CG_WORD})/m],
    # ^superscript^
    [:superscript, :unconstrained, /\\?(?:\[([^\]]+)\])?\^(\S+?)\^/],
    # ~subscript~
    [:subscript, :unconstrained, /\\?(?:\[([^\]]+)\])?~(\S+?)~/]
  ]

  accum[true] = compat = normal.drop 0
  # ``quoted''
  compat[2] = [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?``(\S|\S#{CC_ALL}*?\S)''(?!#{CG_WORD})/m]
  # `quoted'
  compat[3] = [:single, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?`(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m]
  # ++monospaced++
  compat[4] = [:monospaced, :unconstrained, /\\?(?:\[([^\]]+)\])?\+\+(#{CC_ALL}+?)\+\+/m]
  # +monospaced+
  compat[5] = [:monospaced, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?\+(\S|\S#{CC_ALL}*?\S)\+(?!#{CG_WORD})/m]
  # #unquoted#
  #compat[8] = [:unquoted, *compat[8][1..-1]]
  # ##unquoted##
  #compat[9] = [:unquoted, *compat[9][1..-1]]
  # 'emphasis'
  compat.insert 3, [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?'(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m]
end
REPLACEMENTS =

NOTE order of replacements is significant

[
  # (C)
  [/\\?\(C\)/, '&#169;', :none],
  # (R)
  [/\\?\(R\)/, '&#174;', :none],
  # (TM)
  [/\\?\(TM\)/, '&#8482;', :none],
  # foo -- bar (where either space character can be a newline)
  # NOTE this necessarily drops the newline if replacement appears at end of line
  [/(?: |\n|^|\\)--(?: |\n|$)/, '&#8201;&#8212;&#8201;', :none],
  # foo--bar
  [/(#{CG_WORD})\\?--(?=#{CG_WORD})/, '&#8212;&#8203;', :leading],
  # ellipsis
  [/\\?\.\.\./, '&#8230;&#8203;', :none],
  # right single quote
  [/\\?`'/, '&#8217;', :none],
  # apostrophe (inside a word)
  [/(#{CG_ALNUM})\\?'(?=#{CG_ALPHA})/, '&#8217;', :leading],
  # right arrow ->
  [/\\?-&gt;/, '&#8594;', :none],
  # right double arrow =>
  [/\\?=&gt;/, '&#8658;', :none],
  # left arrow <-
  [/\\?&lt;-/, '&#8592;', :none],
  # left double arrow <=
  [/\\?&lt;=/, '&#8656;', :none],
  # restore entities
  [/\\?(&)amp;((?:[a-zA-Z][a-zA-Z]+\d{0,2}|#\d\d\d{0,4}|#x[\da-fA-F][\da-fA-F][\da-fA-F]{0,3});)/, '', :bounding]
]
AuthorInfoLineRx =

Matches the author info line immediately following the document title.

Examples:

Doc Writer <[email protected]>
Mary_Sue Brontë
/^(#{CG_WORD}[#{CC_WORD}\-'.]*)(?: +(#{CG_WORD}[#{CC_WORD}\-'.]*))?(?: +(#{CG_WORD}[#{CC_WORD}\-'.]*))?(?: +<([^>]+)>)?$/
AuthorDelimiterRx =

Matches the delimiter that separates multiple authors.

Examples:

Doc Writer; Junior Writer
/;(?: |$)/
RevisionInfoLineRx =

Matches the revision info line, which appears immediately following the author info line beneath the document title.

Examples:

v1.0
2013-01-01
v1.0, 2013-01-01: Ring in the new year release
1.0, Jan 01, 2013
/^(?:[^\d{]*(#{CC_ANY}*?),)? *(?!:)(#{CC_ANY}*?)(?: *(?!^),?: *(#{CC_ANY}*))?$/
ManpageTitleVolnumRx =

Matches the title and volnum in the manpage doctype.

Examples:

= asciidoctor(1)
= asciidoctor ( 1 )
/^(#{CC_ANY}+?) *\( *(#{CC_ANY}+?) *\)$/
ManpageNamePurposeRx =

Matches the name and purpose in the manpage doctype.

Examples:

asciidoctor - converts AsciiDoc source files to HTML, DocBook and other formats
/^(#{CC_ANY}+?) +- +(#{CC_ANY}+)$/
ConditionalDirectiveRx =

Matches a conditional preprocessor directive (e.g., ifdef, ifndef, ifeval and endif).

Examples:

ifdef::basebackend-html[]
ifndef::theme[]
ifeval::["{asciidoctor-version}" >= "0.1.0"]
ifdef::asciidoctor[Asciidoctor!]
endif::theme[]
endif::basebackend-html[]
endif::[]
/^(\\)?(ifdef|ifndef|ifeval|endif)::(\S*?(?:([,+])\S*?)?)\[(#{CC_ANY}+)?\]$/
EvalExpressionRx =

Matches a restricted (read as safe) eval expression.

Examples:

"{asciidoctor-version}" >= "0.1.0"
/^(#{CC_ANY}+?) *([=!><]=|[><]) *(#{CC_ANY}+)$/
IncludeDirectiveRx =

Matches an include preprocessor directive.

Examples:

include::chapter1.ad[]
include::example.txt[lines=1;2;5..10]
/^(\\)?include::([^\s\[](?:[^\[]*[^\s\[])?)\[(#{CC_ANY}+)?\]$/
TagDirectiveRx =

Matches a trailing tag directive in an include file.

Examples:

// tag::try-catch[]
  try {
    someMethod();
  catch (Exception e) {
    log(e);
  }
  // end::try-catch[]
NOTE m flag is required for Asciidoctor.js
NOTE the regex checks for \r to account of include files that use Windows newlines
/\b(?:tag|(e)nd)::(\S+?)\[\](?=$|[ \r])/m
AttributeEntryRx =

Matches a document attribute entry.

Examples:

:foo: bar
:First Name: Dan
:sectnums!:
:!toc:
:long-entry: Attribute value lines ending in ' \' \
             are joined together as a single value, \
             collapsing the line breaks and indentation to \
             a single space.
/^:(!?#{CG_WORD}[^:]*):(?:[ \t]+(#{CC_ANY}*))?$/
InvalidAttributeNameCharsRx =

Matches invalid characters in an attribute name.

/[^#{CC_WORD}-]/
AttributeEntryPassMacroRx =

NOTE In JavaScript, ^ and $ match the boundaries of the string when the m flag is not set

/\Apass:([a-z]+(?:,[a-z-]+)*)?\[(.*)\]\Z/m
AttributeReferenceRx =

Matches an inline attribute reference.

Examples:

{foobar} or {app_name} or {product-version}
{counter:sequence-name:1}
{set:foo:bar}
{set:name!}
/(\\)?\{(#{CG_WORD}[#{CC_WORD}-]*|(set|counter2?):#{CC_ANY}+?)(\\)?\}/
BlockAnchorRx =

Matches an anchor (i.e., id + optional reference text) on a line above a block.

Examples:

[[idname]]
[[idname,Reference Text]]
/^\[\[(?:|([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+))?)\]\]$/
BlockAttributeListRx =

Matches an attribute list above a block element.

Examples:

# strictly positional
[quote, Adam Smith, Wealth of Nations]
# name/value pairs
[NOTE, caption="Good to know"]
# as attribute reference
[{lead}]
/^\[(|[#{CC_WORD}.#%{,"']#{CC_ANY}*)\]$/
BlockAttributeLineRx =

A combined pattern that matches either a block anchor or a block attribute list.

TODO this one gets hit a lot, should be optimized as much as possible

/^\[(?:|[#{CC_WORD}.#%{,"']#{CC_ANY}*|\[(?:|[#{CC_ALPHA}_:][#{CC_WORD}\-:.]*(?:, *#{CC_ANY}+)?)\])\]$/
BlockTitleRx =

Matches a title above a block.

Examples:

.Title goes here
/^\.(\.?[^ \t.]#{CC_ANY}*)$/
AdmonitionParagraphRx =

Matches an admonition label at the start of a paragraph.

Examples:

NOTE: Just a little note.
TIP: Don't forget!
/^(#{ADMONITION_STYLES.to_a.join '|'}):[ \t]+/
LiteralParagraphRx =

Matches a literal paragraph, which is a line of text preceded by at least one space.

Examples:

<SPACE>Foo
<TAB>Foo
/^([ \t]+#{CC_ANY}*)$/
AtxSectionTitleRx =

Matches an Atx (single-line) section title.

Examples:

== Foo
// ^ a level 1 (h2) section title
== Foo ==
// ^ also a level 1 (h2) section title
/^(=={0,5})[ \t]+(#{CC_ANY}+?)(?:[ \t]+\1)?$/
ExtAtxSectionTitleRx =

Matches an extended Atx section title that includes support for the Markdown variant.

/^(=={0,5}|#\#{0,5})[ \t]+(#{CC_ANY}+?)(?:[ \t]+\1)?$/
SetextSectionTitleRx =

Matches the title only (first line) of an Setext (two-line) section title. The title cannot begin with a dot and must have at least one alphanumeric character.

/^((?!\.)#{CC_ANY}*?#{CG_ALNUM}#{CC_ANY}*)$/
InlineSectionAnchorRx =

Matches an anchor (i.e., id + optional reference text) inside a section title.

Examples:

Section Title [[idname]]
Section Title [[idname,Reference Text]]
/ (\\)?\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+))?\]\]$/
InvalidSectionIdCharsRx =

Matches invalid ID characters in a section title.

NOTE uppercase chars not included since expression is only run on a lowercase string

/<[^>]+>|&(?:[a-z][a-z]+\d{0,2}|#\d\d\d{0,4}|#x[\da-f][\da-f][\da-f]{0,3});|[^ #{CC_WORD}\-.]+?/
SectionLevelStyleRx =

Matches an explicit section level style like sect1

/^sect\d$/
AnyListRx =

Detects the start of any list item.

NOTE we only have to check as far as the blank character because we know it means non-whitespace follows. IMPORTANT if this regexp does not agree with the regexp for each list type, the parser will hang.

%r(^(?:[ \t]*(?:-|\*\**|\.\.*|\u2022|\d+\.|[a-zA-Z]\.|[IVXivx]+\))[ \t]|(?!//[^/])[ \t]*[^ \t]#{CC_ANY}*?(?::::{0,2}|;;)(?:$|[ \t])|<(?:\d+|\.)>[ \t]))
UnorderedListRx =

Matches an unordered list item (one level for hyphens, up to 5 levels for asterisks).

Examples:

* Foo
- Foo
NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
/^[ \t]*(-|\*\**|\u2022)[ \t]+(#{CC_ANY}*)$/
OrderedListRx =

Matches an ordered list item (explicit numbering or up to 5 consecutive dots).

Examples:

. Foo
.. Foo
1. Foo (arabic, default)
a. Foo (loweralpha)
A. Foo (upperalpha)
i. Foo (lowerroman)
I. Foo (upperroman)
NOTE leading space match is not always necessary, but is used for list reader
NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
/^[ \t]*(\.\.*|\d+\.|[a-zA-Z]\.|[IVXivx]+\))[ \t]+(#{CC_ANY}*)$/
OrderedListMarkerRxMap =

Matches the ordinals for each type of ordered list.

{
  arabic: /\d+\./,
  loweralpha: /[a-z]\./,
  lowerroman: /[ivx]+\)/,
  upperalpha: /[A-Z]\./,
  upperroman: /[IVX]+\)/,
  #lowergreek: /[a-z]\]/,
}
DescriptionListRx =

Matches a description list entry.

Examples:

foo::
bar:::
baz::::
blah;;
# the term may be followed by a description on the same line...
foo:: The metasyntactic variable that commonly accompanies 'bar' (see also, <<bar>>).
# ...or on a separate line, which may optionally be indented
foo::
  The metasyntactic variable that commonly accompanies 'bar' (see also, <<bar>>).
# attribute references may be used in both the term and the description
{foo-term}:: {foo-desc}
NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
NOTE must skip line comment when looking for next list item inside list
%r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?)(:::{0,2}|;;)(?:$|[ \t]+(#{CC_ANY}*)$))
DescriptionListSiblingRx =

Matches a sibling description list item (excluding the delimiter specified by the key). NOTE must skip line comment when looking for sibling list item

{
  '::' => %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?[^:]|[^ \t:])(::)(?:$|[ \t]+(#{CC_ANY}*)$)),
  ':::' => %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?[^:]|[^ \t:])(:::)(?:$|[ \t]+(#{CC_ANY}*)$)),
  '::::' => %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?[^:]|[^ \t:])(::::)(?:$|[ \t]+(#{CC_ANY}*)$)),
  ';;' => %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?)(;;)(?:$|[ \t]+(#{CC_ANY}*)$))
}
CalloutListRx =

Matches a callout list item.

Examples:

<1> Explanation
or
<.> Explanation with automatic number
NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
/^<(\d+|\.)>[ \t]+(#{CC_ANY}*)$/
CalloutExtractRx =

Matches a callout reference inside literal text.

Examples:

<1> (optionally prefixed by //, #, -- or ;; line comment chars)
<1> <2> (multiple callouts on one line)
<!--1--> (for XML-based languages)
<.> (auto-numbered)
NOTE extract regexps are applied line-by-line, so we can use $ as end-of-line char
%r(((?://|#|--|;;) ?)?(\\)?<!?(|--)(\d+|\.)\3>(?=(?: ?\\?<!?\3(?:\d+|\.)\3>)*$))
CalloutExtractRxt =
'(\\\\)?<()(\\d+|\\.)>(?=(?: ?\\\\?<(?:\\d+|\\.)>)*$)'
CalloutExtractRxMap =
::Hash.new {|h, k| h[k] = /(#{k.empty? ? '' : "#{::Regexp.escape k} ?"})?#{CalloutExtractRxt}/ }
CalloutScanRx =

NOTE special characters have not been replaced when scanning

/\\?<!?(|--)(\d+|\.)\1>(?=(?: ?\\?<!?\1(?:\d+|\.)\1>)*#{CC_EOL})/
CalloutSourceRx =

NOTE special characters have already been replaced when converting to an SGML format

%r(((?://|#|--|;;) ?)?(\\)?&lt;!?(|--)(\d+|\.)\3&gt;(?=(?: ?\\?&lt;!?\3(?:\d+|\.)\3&gt;)*#{CC_EOL}))
CalloutSourceRxt =
"(\\\\)?&lt;()(\\d+|\\.)&gt;(?=(?: ?\\\\?&lt;(?:\\d+|\\.)&gt;)*#{CC_EOL})"
CalloutSourceRxMap =
::Hash.new {|h, k| h[k] = /(#{k.empty? ? '' : "#{::Regexp.escape k} ?"})?#{CalloutSourceRxt}/ }
ListRxMap =

A Hash of regexps for lists used for dynamic access.

{ ulist: UnorderedListRx, olist: OrderedListRx, dlist: DescriptionListRx, colist: CalloutListRx }
ColumnSpecRx =

Parses the column spec (i.e., colspec) for a table.

Examples:

1*h,2*,^3e
/^(?:(\d+)\*)?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?(\d+%?|~)?([a-z])?$/
CellSpecStartRx =

Parses the start and end of a cell spec (i.e., cellspec) for a table.

Examples:

2.3+<.>m
FIXME use step-wise scan (or treetop) rather than this mega-regexp
/^[ \t]*(?:(\d+(?:\.\d*)?|(?:\d*\.)?\d+)([*+]))?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?([a-z])?$/
CellSpecEndRx =
/[ \t]+(?:(\d+(?:\.\d*)?|(?:\d*\.)?\d+)([*+]))?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?([a-z])?$/
CustomBlockMacroRx =

Matches the custom block macro pattern.

Examples:

gist::123456[]
--
NOTE we've relaxed the match for target to accommodate the short format (e.g., name::[attrlist])
/^(#{CG_WORD}[#{CC_WORD}-]*)::(|\S|\S#{CC_ANY}*?\S)\[(#{CC_ANY}+)?\]$/
BlockMediaMacroRx =

Matches an image, video or audio block macro.

Examples:

image::filename.png[Caption]
video::http://youtube.com/12345[Cats vs Dogs]
/^(image|video|audio)::(\S|\S#{CC_ANY}*?\S)\[(#{CC_ANY}+)?\]$/
BlockTocMacroRx =

Matches the TOC block macro.

Examples:

toc::[]
toc::[levels=2]
/^toc::\[(#{CC_ANY}+)?\]$/
InlineAnchorRx =

Matches an anchor (i.e., id + optional reference text) in the flow of text.

Examples:

[[idname]]
[[idname,Reference Text]]
anchor:idname[]
anchor:idname[Reference Text]
/(\\)?(?:\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+?))?\]\]|anchor:([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)\[(?:\]|(#{CC_ANY}*?[^\\])\]))/
InlineAnchorScanRx =

Scans for a non-escaped anchor (i.e., id + optional reference text) in the flow of text.

/(?:^|[^\\\[])\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+?))?\]\]|(?:^|[^\\])anchor:([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)\[(?:\]|(#{CC_ANY}*?[^\\])\])/
LeadingInlineAnchorRx =

Scans for a leading, non-escaped anchor (i.e., id + optional reference text).

/^\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+?))?\]\]/
InlineBiblioAnchorRx =

Matches a bibliography anchor at the start of the list item text (in a bibliography list).

Examples:

[[[Fowler_1997]]] Fowler M. ...
/^\[\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+?))?\]\]\]/
InlineEmailRx =

Matches an inline e-mail address.

[email protected]

%r(([\\>:/])?#{CG_WORD}(?:&amp;|[#{CC_WORD}\-.%+])*@#{CG_ALNUM}[#{CC_ALNUM}_\-.]*\.[a-zA-Z]{2,5}\b)
InlineFootnoteMacroRx =

Matches an inline footnote macro, which is allowed to span multiple lines.

Examples:

footnote:[text] (not referenceable)
footnote:id[text] (referenceable)
footnote:id[] (reference)
footnoteref:[id,text] (legacy)
footnoteref:[id] (legacy)
%r(\\?footnote(?:(ref):|:([#{CC_WORD}-]+)?)\[(?:|(#{CC_ALL}*?[^\\]))\](?!</a>))m
InlineImageMacroRx =

Matches an image or icon inline macro.

Examples:

image:filename.png[Alt Text]
image:http://example.com/images/filename.png[Alt Text]
image:filename.png[More [Alt\] Text] (alt text becomes "More [Alt] Text")
icon:github[large]
NOTE be as non-greedy as possible by not allowing newline or left square bracket in target
/\\?i(?:mage|con):([^:\s\[](?:[^\n\[]*[^\s\[])?)\[(|#{CC_ALL}*?[^\\])\]/m
InlineIndextermMacroRx =

Matches an indexterm inline macro, which may span multiple lines.

Examples:

indexterm:[Tigers,Big cats]
(((Tigers,Big cats)))
indexterm2:[Tigers]
((Tigers))
/\\?(?:(indexterm2?):\[(#{CC_ALL}*?[^\\])\]|\(\((#{CC_ALL}+?)\)\)(?!\)))/m
InlineKbdBtnMacroRx =

Matches either the kbd or btn inline macro.

Examples:

kbd:[F3]
kbd:[Ctrl+Shift+T]
kbd:[Ctrl+\]]
kbd:[Ctrl,T]
btn:[Save]
/(\\)?(kbd|btn):\[(#{CC_ALL}*?[^\\])\]/m
InlineLinkRx =

Matches an implicit link and some of the link inline macro.

Examples:

https://github.com
https://github.com[GitHub]
<https://github.com> <= angle brackets not included in autolink
link:https://github.com[]
"https://github.com[]"
(https://github.com) <= parenthesis not included in autolink
%r((^|link:|#{CG_BLANK}|\\?&lt;()|[>\(\)\[\];"'])(\\?(?:https?|file|ftp|irc)://)(?:([^\s\[\]]+)\[(|#{CC_ALL}*?[^\\])\]|\2([^\s]*?)&gt;|([^\s\[\]<]*([^\s,.?!\[\]<\)]))))m
InlineLinkMacroRx =

Match a link or e-mail inline macro.

Examples:

link:path[label]
mailto:doc.writer@example.com[]
NOTE be as non-greedy as possible by not allowing space or left square bracket in target
/\\?(?:link|(mailto)):(|[^:\s\[][^\s\[]*)\[(|#{CC_ALL}*?[^\\])\]/m
MacroNameRx =

Matches the name of a macro.

/^#{CG_WORD}[#{CC_WORD}-]*$/
InlineStemMacroRx =

Matches a stem (and alternatives, asciimath and latexmath) inline macro, which may span multiple lines.

Examples:

stem:[x != 0]
asciimath:[x != 0]
latexmath:[\sqrt{4} = 2]
/\\?(stem|(?:latex|ascii)math):([a-z]+(?:,[a-z-]+)*)?\[(#{CC_ALL}*?[^\\])\]/m
InlineMenuMacroRx =

Matches a menu inline macro.

Examples:

menu:File[Save As...]
menu:Edit[]
menu:View[Page Style > No Style]
menu:View[Page Style, No Style]
/\\?menu:(#{CG_WORD}|[#{CC_WORD}&][^\n\[]*[^\s\[])\[ *(?:|(#{CC_ALL}*?[^\\]))\]/m
InlineMenuRx =

Matches an implicit menu inline macro.

Examples:

"File > New..."
/\\?"([#{CC_WORD}&][^"]*?[ \n]+&gt;[ \n]+[^"]*)"/
InlinePassRx =

Matches an inline passthrough, which may span multiple lines.

Examples:

+text+
[x-]+text+
[x-]`text`
`text` (compat only)
[role]`text` (compat only)
NOTE we always capture the attributes so we know when to use compatible (i.e., legacy) behavior
{
  false => ['+', '-]', /((?:^|[^#{CC_WORD};:\\])(?=(\[)|\+)|\\(?=\[)|(?=\\\+))(?:\2(x-|[^\]]+ x-)\]|(?:\[([^\]]+)\])?(?=(\\)?\+))(\5?(\+|`)(\S|\S#{CC_ALL}*?\S)\7)(?!#{CG_WORD})/m],
  true => ['`', nil, /(^|[^`#{CC_WORD}])(?:(\Z)()|\[([^\]]+)\](?=(\\))?)?(\5?(`)([^`\s]|[^`\s]#{CC_ALL}*?\S)\7)(?![`#{CC_WORD}])/m],
}
InlinePassMacroRx =

Matches several variants of the passthrough inline macro, which may span multiple lines.

Examples:

+++text+++
$$text$$
pass:quotes[text]
NOTE we have to support an empty pass:[] for compatibility with AsciiDoc.py
/(?:(?:(\\?)\[([^\]]+)\])?(\\{0,2})(\+\+\+?|\$\$)(#{CC_ALL}*?)\4|(\\?)pass:([a-z]+(?:,[a-z-]+)*)?\[(|#{CC_ALL}*?[^\\])\])/m
InlineXrefMacroRx =

Matches an xref (i.e., cross-reference) inline macro, which may span multiple lines.

Examples:

<<id,reftext>>
xref:id[reftext]
NOTE special characters have already been escaped, hence the entity references
NOTE { is included in start characters to support target that begins with attribute reference in title content
%r(\\?(?:&lt;&lt;([#{CC_WORD}#/.:{]#{CC_ALL}*?)&gt;&gt;|xref:([#{CC_WORD}#/.:{]#{CC_ALL}*?)\[(?:\]|(#{CC_ALL}*?[^\\])\])))m
HardLineBreakRx =

NOTE In Ruby, ^ and $ always match start and end of line

/^(.*) \+$/
MarkdownThematicBreakRx =

Matches a Markdown horizontal rule.

/^ {0,3}([-*_])( *)\1\2\1$/
ExtLayoutBreakRx =

Matches an AsciiDoc or Markdown horizontal rule or AsciiDoc page break.

Examples:

''' (horizontal rule)
<<< (page break)
--- or - - - (horizontal rule, Markdown)
*** or * * * (horizontal rule, Markdown)
___ or _ _ _ (horizontal rule, Markdown)
/^(?:'{3,}|<{3,}|([-*_])( *)\1\2\1)$/
BlankLineRx =

Matches consecutive blank lines.

Examples:

one
two
/\n{2,}/
EscapedSpaceRx =

Matches whitespace (space, tab, newline) escaped by a backslash.

Examples:

three\ blind\ mice
/\\([ \t\n])/
ReplaceableTextRx =

Detects if text is a possible candidate for the replacements substitution.

/[&']|--|\.\.\.|\([CRT]M?\)/
SpaceDelimiterRx =

Matches a whitespace delimiter, a sequence of spaces, tabs, and/or newlines. Matches the parsing rules of %w strings in Ruby.

Examples:

one two	 three   four
five	six
TODO change to /(?<!\\)[ \t\n]+/ once lookbehind assertions are implemented in all modern browsers
/([^\\])[ \t\n]+/
SubModifierSniffRx =

Matches a + or - modifier in a subs list

/[+-]/
TrailingDigitsRx =

Matches one or more consecutive digits at the end of a line.

Examples:

docbook5
html5
/\d+$/
UriSniffRx =
%r(\A#{CG_ALPHA}[#{CC_ALNUM}.+-]+:/{0,2})
XmlSanitizeRx =

Detects XML tags

/<[^>]+>/
VERSION =
'2.0.22'

Class Method Summary collapse

Class Method Details

.convert(input, options = {}) ⇒ Object Also known as: render

Parse the AsciiDoc source input into an Asciidoctor::Document and convert it to the specified backend format.

Accepts input as an IO (or StringIO), String or String Array object. If the input is a File, the object is expected to be opened for reading and is not closed afterwards by this method. Information about the file (filename, directory name, etc) gets assigned to attributes on the Document object.

If the :to_file option is true, and the input is a File, the output is written to a file adjacent to the input file, having an extension that corresponds to the backend format. Otherwise, if the :to_file option is specified, the file is written to that file. If :to_file is not an absolute path, it is resolved relative to :to_dir, if given, otherwise the Document#base_dir. If the target directory does not exist, it will not be created unless the :mkdirs option is set to true. If the file cannot be written because the target directory does not exist, or because it falls outside of the Document#base_dir in safe mode, an IOError is raised.

If the output is going to be written to a file, the header and footer are included unless specified otherwise (writing to a file implies creating a standalone document). Otherwise, the header and footer are not included by default and the converted result is returned.

Parameters:

  • input

    the String AsciiDoc source filename

  • options (defaults to: {})

    a String, Array or Hash of options to control processing (default: {}) String and Array values are converted into a Hash. See Asciidoctor::Document#initialize for details about options.

Returns:

  • the Document object if the converted String is written to a file, otherwise the converted String



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/asciidoctor/convert.rb', line 34

def convert input, options = {}
  (options = options.merge).delete :parse
  to_dir = options.delete :to_dir
  mkdirs = options.delete :mkdirs

  case (to_file = options.delete :to_file)
  when true, nil
    unless (write_to_target = to_dir)
      sibling_path = ::File.absolute_path input.path if ::File === input
    end
    to_file = nil
  when false
    to_file = nil
  when '/dev/null'
    return load input, options
  else
    options[:to_file] = write_to_target = to_file unless (stream_output = to_file.respond_to? :write)
  end

  unless options.key? :standalone
    if sibling_path || write_to_target
      options[:standalone] = options.fetch :header_footer, true
    elsif options.key? :header_footer
      options[:standalone] = options[:header_footer]
    end
  end

  # NOTE outfile may be controlled by document attributes, so resolve outfile after loading
  if sibling_path
    options[:to_dir] = outdir = ::File.dirname sibling_path
  elsif write_to_target
    if to_dir
      if to_file
        options[:to_dir] = ::File.dirname ::File.expand_path to_file, to_dir
      else
        options[:to_dir] = ::File.expand_path to_dir
      end
    elsif to_file
      options[:to_dir] = ::File.dirname ::File.expand_path to_file
    end
  end

  # NOTE :to_dir is always set when outputting to a file
  # NOTE :to_file option only passed if assigned an explicit path
  doc = load input, options

  if sibling_path # write to file in same directory
    outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
    raise ::IOError, %(input file and output file cannot be the same: #{outfile}) if outfile == sibling_path
  elsif write_to_target # write to explicit file or directory
    working_dir = (options.key? :base_dir) ? (::File.expand_path options[:base_dir]) : ::Dir.pwd
    # QUESTION should the jail be the working_dir or doc.base_dir???
    jail = doc.safe >= SafeMode::SAFE ? working_dir : nil
    if to_dir
      outdir = doc.normalize_system_path(to_dir, working_dir, jail, target_name: 'to_dir', recover: false)
      if to_file
        outfile = doc.normalize_system_path(to_file, outdir, nil, target_name: 'to_dir', recover: false)
        # reestablish outdir as the final target directory (in the case to_file had directory segments)
        outdir = ::File.dirname outfile
      else
        outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
      end
    elsif to_file
      outfile = doc.normalize_system_path(to_file, working_dir, jail, target_name: 'to_dir', recover: false)
      # establish outdir as the final target directory (in the case to_file had directory segments)
      outdir = ::File.dirname outfile
    end

    if ::File === input && outfile == (::File.absolute_path input.path)
      raise ::IOError, %(input file and output file cannot be the same: #{outfile})
    end

    if mkdirs
      Helpers.mkdir_p outdir
    else
      # NOTE we intentionally refer to the directory as it was passed to the API
      raise ::IOError, %(target directory does not exist: #{to_dir} (hint: set :mkdirs option)) unless ::File.directory? outdir
    end
  else # write to stream
    outfile = to_file
    outdir = nil
  end

  if outfile && !stream_output
    output = doc.convert 'outfile' => outfile, 'outdir' => outdir
  else
    output = doc.convert
  end

  if outfile
    doc.write output, outfile

    # NOTE document cannot control this behavior if safe >= SafeMode::SERVER
    # NOTE skip if stylesdir is a URI
    if !stream_output && doc.safe < SafeMode::SECURE && (doc.attr? 'linkcss') && (doc.attr? 'copycss') &&
        (doc.basebackend? 'html') && !((stylesdir = (doc.attr 'stylesdir')) && (Helpers.uriish? stylesdir))
      if (stylesheet = doc.attr 'stylesheet')
        if DEFAULT_STYLESHEET_KEYS.include? stylesheet
          copy_asciidoctor_stylesheet = true
        elsif !(Helpers.uriish? stylesheet)
          copy_user_stylesheet = true
        end
      end
      copy_syntax_hl_stylesheet = (syntax_hl = doc.syntax_highlighter) && (syntax_hl.write_stylesheet? doc)
      if copy_asciidoctor_stylesheet || copy_user_stylesheet || copy_syntax_hl_stylesheet
        stylesoutdir = doc.normalize_system_path(stylesdir, outdir, doc.safe >= SafeMode::SAFE ? outdir : nil)
        if mkdirs
          Helpers.mkdir_p stylesoutdir
        else
          raise ::IOError, %(target stylesheet directory does not exist: #{stylesoutdir} (hint: set :mkdirs option)) unless ::File.directory? stylesoutdir
        end

        if copy_asciidoctor_stylesheet
          Stylesheets.instance.write_primary_stylesheet stylesoutdir
        # FIXME should Stylesheets also handle the user stylesheet?
        elsif copy_user_stylesheet
          if (stylesheet_src = doc.attr 'copycss') == '' || stylesheet_src == true
            stylesheet_src = doc.normalize_system_path stylesheet
          else
            # NOTE in this case, copycss is a source location (but cannot be a URI)
            stylesheet_src = doc.normalize_system_path stylesheet_src.to_s
          end
          stylesheet_dest = doc.normalize_system_path stylesheet, stylesoutdir, (doc.safe >= SafeMode::SAFE ? outdir : nil)
          # NOTE don't warn if src can't be read and dest already exists (see #2323)
          if stylesheet_src != stylesheet_dest && (stylesheet_data = doc.read_asset stylesheet_src,
              warn_on_failure: !(::File.file? stylesheet_dest), label: 'stylesheet')
            if (stylesheet_outdir = ::File.dirname stylesheet_dest) != stylesoutdir && !(::File.directory? stylesheet_outdir)
              if mkdirs
                Helpers.mkdir_p stylesheet_outdir
              else
                raise ::IOError, %(target stylesheet directory does not exist: #{stylesheet_outdir} (hint: set :mkdirs option))
              end
            end
            ::File.write stylesheet_dest, stylesheet_data, mode: FILE_WRITE_MODE
          end
        end
        syntax_hl.write_stylesheet doc, stylesoutdir if copy_syntax_hl_stylesheet
      end
    end
    doc
  else
    output
  end
end

.convert_file(filename, options = {}) ⇒ Object Also known as: render_file

Parse the contents of the AsciiDoc source file into an Asciidoctor::Document and convert it to the specified backend format.

Parameters:

  • input

    the String AsciiDoc source filename

  • options (defaults to: {})

    a String, Array or Hash of options to control processing (default: {}) String and Array values are converted into a Hash. See Asciidoctor::Document#initialize for details about options.

Returns:

  • the Document object if the converted String is written to a file, otherwise the converted String



189
190
191
# File 'lib/asciidoctor/convert.rb', line 189

def convert_file filename, options = {}
  ::File.open(filename, FILE_READ_MODE) {|file| convert file, options }
end

.load(input, options = {}) ⇒ Object

Parse the AsciiDoc source input into a Document

Accepts input as an IO (or StringIO), String or String Array object. If the input is a File, the object is expected to be opened for reading and is not closed afterwards by this method. Information about the file (filename, directory name, etc) gets assigned to attributes on the Document object.

Parameters:

  • input

    the AsciiDoc source as a IO, String or Array.

  • options (defaults to: {})

    a String, Array or Hash of options to control processing (default: {}) String and Array values are converted into a Hash. See Asciidoctor::Document#initialize for details about these options.

Returns:

  • the Document



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/asciidoctor/load.rb', line 17

def load input, options = {}
  options = options.merge

  if (timings = options[:timings])
    timings.start :read
  end

  if (options.key? :logger) && (logger = options[:logger]) != LoggerManager.logger
    LoggerManager.logger = logger || NullLogger.new
  end

  if !(attrs = options[:attributes])
    attrs = {}
  elsif ::Hash === attrs
    attrs = attrs.merge
  elsif (defined? ::Java::JavaUtil::Map) && ::Java::JavaUtil::Map === attrs
    attrs = attrs.dup
  elsif ::Array === attrs
    attrs = {}.tap do |accum|
      attrs.each do |entry|
        k, _, v = entry.partition '='
        accum[k] = v
      end
    end
  elsif ::String === attrs
    # condense and convert non-escaped spaces to null, unescape escaped spaces, then split on null
    attrs = {}.tap do |accum|
      attrs.gsub(SpaceDelimiterRx, '\1' + NULL).gsub(EscapedSpaceRx, '\1').split(NULL).each do |entry|
        k, _, v = entry.partition '='
        accum[k] = v
      end
    end
  elsif (attrs.respond_to? :keys) && (attrs.respond_to? :[])
    # coerce attrs to a real Hash
    attrs = {}.tap {|accum| attrs.keys.each {|k| accum[k] = attrs[k] } }
  else
    raise ::ArgumentError, %(illegal type for attributes option: #{attrs.class.ancestors.join ' < '})
  end

  if ::File === input
    # File#mtime on JRuby 9.1 for Windows doesn't honor TZ environment variable; see https://github.com/jruby/jruby/issues/6659
    options[:input_mtime] = RUBY_ENGINE == 'jruby' ? (::Time.at input.mtime.to_i) : input.mtime
    # NOTE defer setting infile and indir until we get a better sense of their purpose
    # TODO cli checks if input path can be read and is file, but might want to add check to API too
    attrs['docfile'] = input_path = ::File.absolute_path input.path
    attrs['docdir'] = ::File.dirname input_path
    attrs['docname'] = Helpers.basename input_path, (attrs['docfilesuffix'] = Helpers.extname input_path)
    source = input.read
  elsif input.respond_to? :read
    # NOTE tty, pipes & sockets can't be rewound, but can't be sniffed easily either
    # just fail the rewind operation silently to handle all cases
    input.rewind rescue nil
    source = input.read
  elsif ::String === input
    source = input
  elsif ::Array === input
    source = input.drop 0
  elsif input
    raise ::ArgumentError, %(unsupported input type: #{input.class})
  end

  if timings
    timings.record :read
    timings.start :parse
  end

  options[:attributes] = attrs
  doc = options[:parse] == false ? (Document.new source, options) : (Document.new source, options).parse

  timings.record :parse if timings
  doc
rescue => e
  begin
    context = %(asciidoctor: FAILED: #{attrs['docfile'] || '<stdin>'}: Failed to load AsciiDoc document)
    if e.respond_to? :exception
      # The original message must be explicitly preserved when wrapping a Ruby exception
      wrapped_e = e.exception %(#{context} - #{e.message})
      # JRuby automatically sets backtrace; MRI did not until 2.6
      wrapped_e.set_backtrace e.backtrace
    else
      # Likely a Java exception class
      wrapped_e = e.class.new context, e
      wrapped_e.stack_trace = e.stack_trace
    end
  rescue
    wrapped_e = e
  end
  raise wrapped_e
end

.load_file(filename, options = {}) ⇒ Object

Parse the contents of the AsciiDoc source file into an Asciidoctor::Document

Parameters:

  • input

    the String AsciiDoc source filename

  • options (defaults to: {})

    a String, Array or Hash of options to control processing (default: {}) String and Array values are converted into a Hash. See Asciidoctor::Document#initialize for details about options.

Returns:

  • the Asciidoctor::Document



115
116
117
# File 'lib/asciidoctor/load.rb', line 115

def load_file filename, options = {}
  ::File.open(filename, FILE_READ_MODE) {|file| load file, options }
end