class Asciidoctor::Reader

Public: Methods for retrieving lines from Asciidoc documents

def apply_attribute_value_subs(value)

Returns The String value with substitutions performed.

value - The String attribute value on which to perform substitutions

apply the verbatim substitutions to the value.
apply the substitutions defined on the macro to the text. Otherwise,
If the value is an inline passthrough macro (e.g., pass:[text]), then

Internal: Apply substitutions to the attribute value
def apply_attribute_value_subs(value)
  if value.match(REGEXP[:pass_macro_basic])
    # copy match for Ruby 1.8.7 compat
    m = $~
    subs = []
    if !m[1].empty?
      subs = @document.resolve_subs(m[1])
    end
    if !subs.empty?
      @document.apply_subs(m[2], subs)
    else
      m[2]
    end
  else
    @document.apply_header_subs(value)
  end
end

def attribute_overridden?(key)

Returns true if the attribute has been overridden, false otherwise

key - The attribute key to check

Internal: Determine if the attribute has been overridden in the document options
def attribute_overridden?(key)
  @overrides.has_key?(key) || @overrides.has_key?(key + '!')
end

def chomp_last!

Returns nil

Delegates to chomp!

Public: Chomp the String on the last line if this reader contains at least one line
def chomp_last!
  @lines.last.chomp! unless @lines.empty?
  nil
end

def consume_comments(opts = {})

=> ["actual text\n"]
@lines

=> ["// foo\n", "////\n", "foo bar\n", "////\n"]
comment_lines = consume_comments

=> ["// foo\n", "////\n", "foo bar\n", "////\n", "actual text\n"]
@lines
Examples

Returns the Array of lines that were consumed

Public: Consume consecutive lines containing line- or block-level comments.
def consume_comments(opts = {})
  comment_lines = []
  while !@lines.empty?
    next_line = peek_line
    if opts[:include_blanks] && next_line.strip.empty?
      comment_lines << get_line
    elsif match = next_line.match(REGEXP[:comment_blk])
      comment_lines << get_line
      comment_lines.push(*(grab_lines_until(:terminator => match[0], :preserve_last_line => true)))
      comment_lines << get_line
    elsif next_line.match(REGEXP[:comment])
      comment_lines << get_line
    else
      break
    end
  end
  comment_lines
end

def consume_line_comments

=> ["bar\n"]
@lines

=> ["// foo\n"]
comment_lines = consume_comments

=> ["// foo\n", "bar\n"]
@lines
Examples

Returns the Array of lines that were consumed

Public: Consume consecutive lines containing line comments.
def consume_line_comments
  comment_lines = []
  while !@lines.empty?
    if peek_line.match(REGEXP[:comment])
      comment_lines << get_line
    else
      break
    end
  end
  comment_lines
end

def empty?

Returns true if @lines.empty? is true, otherwise false.

Public: Check whether this reader is empty (contains no lines)
def empty?
  @lines.empty?
end

def get_line

Returns nil if there is no more data.
Returns the String of the next line of the source data if data is present.

Public: Get the next line of source data. Consumes the line returned.
def get_line
  @lines.shift
end

def grab_lines_until(options = {}, &block)

=> ["First paragraph\n", "Second paragraph\n", "Open block\n"]
reader.grab_lines_until

"--\n", "\n", "In a different segment\n"]
"Open block\n", "\n", "Can have blank lines\n",
reader = Reader.new ["First paragraph\n", "Second paragraph\n",

Examples

Returns the Array of lines forming the next segment.

included in the lines being returned
causing the method to stop processing lines should be
* :grab_last_line may be used to specify that the String
pushed back onto the `lines` Array.
causing the method to stop processing lines should be
* :preserve_last_line may be used to specify that the String
beyond the first line before beginning the scan
* :skip_first_line may be used to tell the reader to advance
blank lines
* :break_on_blank_lines may be used to specify to break on
options - an optional Hash of processing options:

a line for which the given block evals to true.
(2) find a blank line with :break_on_blank_lines => true, or (3) find
Public: Return all the lines from `@lines` until we (1) run out them,
def grab_lines_until(options = {}, &block)
  buffer = []
  finis = false
  get_line if options[:skip_first_line]
  # save options to locals for minor optimization
  terminator = options[:terminator]
  terminator.chomp! if terminator
  break_on_blank_lines = options[:break_on_blank_lines]
  break_on_list_continuation = options[:break_on_list_continuation]
  while (this_line = self.get_line)
    Asciidoctor.debug { "Reader processing line: '#{this_line}'" }
    finis = true if terminator && this_line.chomp == terminator
    finis = true if !finis && break_on_blank_lines && this_line.strip.empty?
    finis = true if !finis && break_on_list_continuation && this_line.chomp == LIST_CONTINUATION
    finis = true if !finis && block && yield(this_line)
    if finis
      self.unshift(this_line) if options[:preserve_last_line]
      buffer << this_line if options[:grab_last_line]
      break
    end
    if options[:skip_line_comments] && this_line.match(REGEXP[:comment])
      # skip it
    else
      buffer << this_line
    end
  end
  buffer
end

def has_lines?

Returns true if !@lines.empty? is true, or false otherwise.

Public: Check whether there are any lines left to read.
def has_lines?
  !@lines.empty?
end

def initialize(data = [], document = nil, overrides = nil, &block)

reader = Asciidoctor::Reader.new data
data = File.readlines(filename)

Examples

data to include in this document.
block - A block that can be used to retrieve external Asciidoc
the document
prevent attribute assignments or removals of matching keys found in
overrides - A Hash of attributes that were passed to the Document and should
document attributes
document - The document with which this reader is associated. Used to access
original instance of this Array is not modified
data - The Array of Strings holding the Asciidoc source document. The

Public: Initialize the Reader object.
def initialize(data = [], document = nil, overrides = nil, &block)
  # if document is nil, we assume this is a preprocessed string
  if document.nil?
    @lines = data.is_a?(String) ? data.lines.entries : data.dup
  elsif !data.empty?
    @overrides = overrides || {}
    @document = document
    process(data.is_a?(String) ? data.lines.entries : data, &block)
  else
    @lines = []
  end
  @source = @lines.join
end

def peek_line

Returns nil if there is no more data.
Returns a String dup of the next line of the source data if data is present.

Public: Get the next line of source data. Does not consume the line returned.
def peek_line
  @lines.first.dup if @lines.first
end

def process(data, &block)

Private: Process raw input, used for the outermost reader.
def process(data, &block)
  raw_source = []
  include_depth = @document.attr('include-depth', 0).to_i
  data.each do |line|
    if inc = line.match(REGEXP[:include_macro])
      if inc[0].start_with? '\\'
        raw_source << line[1..-1]
      # if running in SafeMode::SECURE or greater, don't process
      # this directive (or should we swallow it?)
      elsif @document.safe >= SafeMode::SECURE
        raw_source << line
      # assume that if a block is given, the developer wants
      # to handle when and how to process the include, even
      # if the include-depth attribute is 0
      elsif block_given?
        raw_source.concat yield(inc[1])
      elsif include_depth > 0
        raw_source.concat File.readlines(@document.normalize_asset_path(inc[1], 'include file'))
      else
        raw_source << line
      end
    else
      raw_source << line
    end
  end
  skip_to = nil
  continuing_value = nil
  continuing_key = nil
  @lines = []
  raw_source.each do |line|
    # normalize line ending to LF (purging occurrences of CRLF)
    line = "#{line.rstrip}\n"
    if skip_to
      skip_to = nil if line.match(skip_to)
    elsif continuing_value
      close_continue = false
      # Lines that start with whitespace and end with a '+' are
      # a continuation, so gobble them up into `value`
      if line.match(REGEXP[:attr_continue])
        continuing_value += ' ' + $1.rstrip
      # An empty line ends a continuation
      elsif line.strip.empty?
        raw_source.unshift(line)
        close_continue = true
      else
        # If this continued line isn't empty and doesn't end with a +, then
        # this is the end of the continuation, no matter what the next line
        # does.
        continuing_value += ' ' + line.strip
        close_continue = true
      end
      if close_continue
        unless attribute_overridden? continuing_key
          @document.attributes[continuing_key] = apply_attribute_value_subs(continuing_value)
        end
        continuing_key = nil
        continuing_value = nil
      end
    elsif line.match(REGEXP[:ifdef_macro])
      attr = $2
      skip = case $1
             when 'ifdef';  !@document.attributes.has_key?(attr)
             when 'ifndef'; @document.attributes.has_key?(attr)
             end
      skip_to = /^endif::#{attr}\[\]\s*\n/ if skip
    elsif line.match(REGEXP[:attr_assign])
      key = sanitize_attribute_name($1)
      value = $2
      if value.match(REGEXP[:attr_continue])
        # attribute value continuation line; grab lines until we run out
        # of continuation lines
        continuing_key = key
        continuing_value = $1.rstrip  # strip off the spaces and +
      else
        unless attribute_overridden? key
          @document.attributes[key] = apply_attribute_value_subs(value)
          if key == 'backend'
            @document.update_backend_attributes()
          end
        end
      end
    elsif line.match(REGEXP[:attr_delete])
      key = sanitize_attribute_name($1)
      unless attribute_overridden? key
        @document.attributes.delete(key)
      end
    elsif !line.match(REGEXP[:endif_macro])
      while line.match(REGEXP[:attr_conditional])
        value = @document.attributes.has_key?($1) ? $2 : ''
        line.sub!(REGEXP[:attr_conditional], value)
      end
      # NOTE leave line comments in as they play a role in flow (such as a list divider)
      @lines << line
    end
  end
  # Process bibliography references, so they're available when text
  # before the reference is being rendered.
  # FIXME we don't have support for bibliography lists yet, so disable for now
  # plus, this should be done while we are walking lines above
  #@lines.each do |line|
  #  if biblio = line.match(REGEXP[:biblio])
  #    @document.register(:ids, biblio[1])
  #  end
  #end
end

def sanitize_attribute_name(name)

=> 'foo3-billy'
sanitize_attribute_name('Foo 3 #-Billy')

=> 'foo'
sanitize_attribute_name('foo')

=> 'foobar'
sanitize_attribute_name('Foo Bar')

Examples

Returns a String with the legal name.

name - The String holding the Asciidoc attribute name.

Public: Convert a string to a legal attribute name.
def sanitize_attribute_name(name)
  name.gsub(/[^\w\-]/, '').downcase
end

def skip_blank

Returns an Integer of the number of lines skipped

=> ["Foo\n", "Bar\n"]
@lines

=> 2
skip_blank

=> ["\n", "\t\n", "Foo\n", "Bar\n", "\n"]
@lines

Examples

Private: Strip off leading blank lines in the Array of lines.
def skip_blank
  skipped = 0
  while has_lines? && @lines.first.strip.empty?
    @lines.shift
    skipped += 1
  end
  skipped
end

def skip_list_continuation

Returns nil

Skip the next line if it's a list continuation character
def skip_list_continuation
  if has_lines? && @lines.first.chomp == '+'
    @lines.shift
  end
  nil
end

def unshift(*new_lines)

Returns nil

Public: Push Array of string `lines` onto queue of source data lines, unless `lines` has no non-nil values.
def unshift(*new_lines)
  @lines.unshift(*new_lines) if !new_lines.empty?
  nil
end