module Asciidoctor

Public: 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

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

frozen_string_literal: true

Constants

ADMONITION_STYLES
ADMONITION_STYLE_HEADS
ASCIIDOC_EXTENSIONS

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

ATTR_REF_HEAD
BACKEND_ALIASES

Pointers to the preferred version for a given backend.

BLOCK_MATH_DELIMITERS
BOM_BYTES_UTF_16BE
BOM_BYTES_UTF_16LE
BOM_BYTES_UTF_8

Byte arrays for UTF-* Byte Order Marks

CAPTION_ATTRIBUTE_NAMES

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

CC_ALL

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

CC_ALNUM
CC_ALPHA
CC_ANY

CC_ANY is any character except newlines

CC_EOL
CC_WORD
CG_BLANK
DATA_DIR

The absolute data directory of the Asciidoctor RubyGem

DEFAULT_ATTRIBUTES
DEFAULT_BACKEND

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

DEFAULT_DOCTYPE

The default document type Can influence markup generated by the converters

DEFAULT_EXTENSIONS

Default extensions for the respective base backends

DEFAULT_PAGE_WIDTHS

Default page widths for calculating absolute widths

DEFAULT_STYLESHEET_KEYS
DEFAULT_STYLESHEET_NAME
DELIMITED_BLOCKS
DELIMITED_BLOCK_HEADS
DELIMITED_BLOCK_TAILS
FILE_READ_MODE

The mode to use when opening a file for reading

FILE_WRITE_MODE

The mode to use when opening a file for writing

FLEXIBLE_ATTRIBUTES

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

FONT_AWESOME_VERSION
HARD_LINE_BREAK

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

HIGHLIGHT_JS_VERSION
HYBRID_LAYOUT_BREAK_CHARS
INLINE_MATH_DELIMITERS
INTRINSIC_ATTRIBUTES
LAYOUT_BREAK_CHARS
LF

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

LIB_DIR

The absolute lib directory of the Asciidoctor RubyGem

LINE_CONTINUATION
LINE_CONTINUATION_LEGACY
LIST_CONTINUATION
MARKDOWN_THEMATIC_BREAK_CHARS
MATHJAX_VERSION
MAX_INT

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

NESTABLE_LIST_CONTEXTS

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

NULL

The null character to use for splitting attribute values

ORDERED_LIST_KEYWORDS
ORDERED_LIST_STYLES

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

PARAGRAPH_STYLES
QUOTE_SUBS
REPLACEMENTS

NOTE order of replacements is significant

ROOT_DIR

The absolute root directory of the Asciidoctor RubyGem

RUBY_ENGINE_OPAL

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

SETEXT_SECTION_LEVELS
STEM_TYPE_ALIASES
TAB

String for matching tab character

URI_READ_MODE

The mode to use when opening a URI for reading

USER_HOME

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

UTF_8

Alias UTF_8 encoding for convenience / speed

VERBATIM_STYLES
VERSION

Public Class Methods

const_missing(name) click to toggle source

Internal: Automatically load the Asciidoctor::Extensions module.

Requires the Asciidoctor::Extensions module if the name is :Extensions. Otherwise, delegates to the super method.

This method provides the same functionality as using autoload on Asciidoctor::Extensions, except that the constant isn't recognized as defined prior to it being loaded.

Returns the resolved constant, if resolved, otherwise nothing.

Calls superclass method
# File lib/asciidoctor.rb, line 530
def self.const_missing name
  if name == :Extensions
    require_relative 'asciidoctor/extensions'
    Extensions
  else
    super
  end
end
convert(input, options = {}) click to toggle source

Public: 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 Asciidoctor::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 Asciidoctor::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.

input - the String AsciiDoc source filename options - 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

# 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, %Q(#{doc.attributes['docname']}#{doc.outfilesuffix})
    raise ::IOError, %Q(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, %Q(#{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, %Q(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, %Q(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, %Q(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, %Q(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
Also aliased as: render
convert_file(filename, options = {}) click to toggle source

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

input - the String AsciiDoc source filename options - 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

# File lib/asciidoctor/convert.rb, line 189
def convert_file filename, options = {}
  ::File.open(filename, FILE_READ_MODE) {|file| convert file, options }
end
Also aliased as: render_file
load(input, options = {}) click to toggle source

Public: 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.

input - the AsciiDoc source as a IO, String or Array. options - a String, Array or Hash of options to control processing (default: {})

String and Array values are converted into a Hash.
See {Document#initialize} for details about these options.

Returns the Document

# 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, %Q(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, %Q(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 = %Q(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 %Q(#{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 = {}) click to toggle source

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

input - the String AsciiDoc source filename options - 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

# File lib/asciidoctor/load.rb, line 115
def load_file filename, options = {}
  ::File.open(filename, FILE_READ_MODE) {|file| load file, options }
end
render(input, options = {})

Deprecated: Use {Asciidoctor.convert} instead.

Alias for: convert
render_file(filename, options = {})

Deprecated: Use {Asciidoctor.convert_file} instead.

Alias for: convert_file