class Asciidoctor::Reader

Public: Methods for retrieving lines from AsciiDoc source files

Attributes

dir[R]
file[R]
lineno[R]

Public: Get the 1-based offset of the current line.

path[R]
process_lines[RW]

Public: Control whether lines are processed using Reader#process_line on first visit (default: true)

source_lines[R]

Public: Get the document source as a String Array of lines.

unterminated[RW]

Public: Indicates that the end of the reader was reached with a delimited block still open.

Public Class Methods

new(data = nil, cursor = nil, opts = {}) click to toggle source

Public: Initialize the Reader object

# File lib/asciidoctor/reader.rb, line 42
def initialize data = nil, cursor = nil, opts = {}
  if !cursor
    @file = nil
    @dir = '.'
    @path = '<stdin>'
    @lineno = 1
  elsif ::String === cursor
    @file = cursor
    @dir, @path = ::File.split @file
    @lineno = 1
  else
    if (@file = cursor.file)
      @dir = cursor.dir || (::File.dirname @file)
      @path = cursor.path || (::File.basename @file)
    else
      @dir = cursor.dir || '.'
      @path = cursor.path || '<stdin>'
    end
    @lineno = cursor.lineno || 1
  end
  @lines = (@source_lines = prepare_lines data, opts).reverse
  @mark = nil
  @look_ahead = 0
  @process_lines = true
  @unescape_next_line = false
  @unterminated = nil
  @saved = nil
end

Public Instance Methods

advance() click to toggle source

Public: Advance to the next line by discarding the line at the front of the stack

Returns a Boolean indicating whether there was a line to discard.

# File lib/asciidoctor/reader.rb, line 211
def advance
  shift ? true : false
end
cursor() click to toggle source
# File lib/asciidoctor/reader.rb, line 479
def cursor
  Cursor.new @file, @dir, @path, @lineno
end
cursor_at_line(lineno) click to toggle source
# File lib/asciidoctor/reader.rb, line 483
def cursor_at_line lineno
  Cursor.new @file, @dir, @path, lineno
end
cursor_at_mark() click to toggle source
# File lib/asciidoctor/reader.rb, line 487
def cursor_at_mark
  @mark ? Cursor.new(*@mark) : cursor
end
cursor_at_prev_line() click to toggle source
# File lib/asciidoctor/reader.rb, line 500
def cursor_at_prev_line
  Cursor.new @file, @dir, @path, @lineno - 1
end
cursor_before_mark() click to toggle source
# File lib/asciidoctor/reader.rb, line 491
def cursor_before_mark
  if @mark
    m_file, m_dir, m_path, m_lineno = @mark
    Cursor.new m_file, m_dir, m_path, m_lineno - 1
  else
    Cursor.new @file, @dir, @path, @lineno - 1
  end
end
discard_save() click to toggle source

Internal: Discard a previous saved state

# File lib/asciidoctor/reader.rb, line 555
def discard_save
  @saved = nil
end
empty?() click to toggle source

Public: Check whether this reader is empty (contains no lines)

Returns true if there are no more lines to peek, otherwise false.

# File lib/asciidoctor/reader.rb, line 90
def empty?
  if @lines.empty?
    @look_ahead = 0
    true
  else
    false
  end
end
Also aliased as: eof?
eof?()
Alias for: empty?
has_more_lines?() click to toggle source

Public: Check whether there are any lines left to read.

If a previous call to this method resulted in a value of false, immediately returned the cached value. Otherwise, delegate to peek_line to determine if there is a next line available.

Returns True if there are more lines, False if there are not.

# File lib/asciidoctor/reader.rb, line 78
def has_more_lines?
  if @lines.empty?
    @look_ahead = 0
    false
  else
    true
  end
end
line_info() click to toggle source

Public: Get information about the last line read, including file name and line number.

Returns A String summary of the last line read

# File lib/asciidoctor/reader.rb, line 511
def line_info
  %(#{@path}: line #{@lineno})
end
lines() click to toggle source

Public: Get a copy of the remaining Array of String lines managed by this Reader

Returns A copy of the String Array of lines remaining in this Reader

# File lib/asciidoctor/reader.rb, line 518
def lines
  @lines.reverse
end
mark() click to toggle source
# File lib/asciidoctor/reader.rb, line 504
def mark
  @mark = @file, @dir, @path, @lineno
end
next_line_empty?() click to toggle source

Public: Peek at the next line and check if it’s empty (i.e., whitespace only)

This method Does not consume the line from the stack.

Returns True if the there are no more lines or if the next line is empty

# File lib/asciidoctor/reader.rb, line 105
def next_line_empty?
  peek_line.nil_or_empty?
end
peek_line(direct = false) click to toggle source

Public: Peek at the next line of source data. Processes the line if not already marked as processed, but does not consume it.

This method will probe the reader for more lines. If there is a next line that has not previously been visited, the line is passed to the Reader#process_line method to be initialized. This call gives sub-classes the opportunity to do preprocessing. If the return value of the Reader#process_line is nil, the data is assumed to be changed and Reader#peek_line is invoked again to perform further processing.

If has_more_lines? is called immediately before peek_line, the direct flag is implicitly true (since the line is flagged as visited).

direct - A Boolean flag to bypasses the check for more lines and immediately

returns the first element of the internal @lines Array. (default: false)

Returns the next line of the source data as a String if there are lines remaining. Returns nothing if there is no more data.

# File lib/asciidoctor/reader.rb, line 127
def peek_line direct = false
  if direct || @look_ahead > 0
    @unescape_next_line ? ((line = @lines[-1]).slice 1, line.length) : @lines[-1]
  elsif @lines.empty?
    @look_ahead = 0
    nil
  else
    # FIXME the problem with this approach is that we aren't
    # retaining the modified line (hence the @unescape_next_line tweak)
    # perhaps we need a stack of proxied lines
    (process_line @lines[-1]) || peek_line
  end
end
peek_lines(num = nil, direct = false) click to toggle source

Public: Peek at the next multiple lines of source data. Processes the lines if not already marked as processed, but does not consume them.

This method delegates to Reader#read_line to process and collect the line, then restores the lines to the stack before returning them. This allows the lines to be processed and marked as such so that subsequent reads will not need to process the lines again.

num - The positive Integer number of lines to peek or nil to peek all lines (default: nil). direct - A Boolean indicating whether processing should be disabled when reading lines (default: false).

Returns A String Array of the next multiple lines of source data, or an empty Array if there are no more lines in this Reader.

# File lib/asciidoctor/reader.rb, line 154
def peek_lines num = nil, direct = false
  old_look_ahead = @look_ahead
  result = []
  (num || MAX_INT).times do
    if (line = direct ? shift : read_line)
      result << line
    else
      @lineno -= 1 if direct
      break
    end
  end

  unless result.empty?
    unshift_all result
    @look_ahead = old_look_ahead if direct
  end

  result
end
read() click to toggle source

Public: Get the remaining lines of source data joined as a String.

Delegates to Reader#read_lines, then joins the result.

Returns the lines read joined as a String

# File lib/asciidoctor/reader.rb, line 204
def read
  read_lines.join LF
end
read_line() click to toggle source

Public: Get the next line of source data. Consumes the line returned.

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

# File lib/asciidoctor/reader.rb, line 178
def read_line
  # has_more_lines? triggers preprocessor
  shift if @look_ahead > 0 || has_more_lines?
end
read_lines() click to toggle source

Public: Get the remaining lines of source data.

This method calls Reader#read_line repeatedly until all lines are consumed and returns the lines as a String Array. This method differs from Reader#lines in that it processes each line in turn, hence triggering any preprocessors implemented in sub-classes.

Returns the lines read as a String Array

# File lib/asciidoctor/reader.rb, line 191
def read_lines
  lines = []
  # has_more_lines? triggers preprocessor
  lines << shift while has_more_lines?
  lines
end
Also aliased as: readlines
read_lines_until(options = {}) { |line)))| ... } click to toggle source

Public: Return all the lines from ‘@lines` until we (1) run out them,

(2) find a blank line with `break_on_blank_lines: true`, or (3) find
a line for which the given block evals to true.

options - an optional Hash of processing options:

* :terminator may be used to specify the contents of the line
    at which the reader should stop
* :break_on_blank_lines may be used to specify to break on
    blank lines
* :break_on_list_continuation may be used to specify to break
    on a list continuation line
* :skip_first_line may be used to tell the reader to advance
    beyond the first line before beginning the scan
* :preserve_last_line may be used to specify that the String
    causing the method to stop processing lines should be
    pushed back onto the `lines` Array.
* :read_last_line may be used to specify that the String
    causing the method to stop processing lines should be
    included in the lines being returned
* :skip_line_comments may be used to look for and skip
    line comments
* :skip_processing is used to disable line (pre)processing
    for the duration of this method

Returns the Array of lines forming the next segment.

Examples

data = [
  "First line\n",
  "Second line\n",
  "\n",
  "Third line\n",
]
reader = Reader.new data, nil, normalize: true

reader.read_lines_until
=> ["First line", "Second line"]
# File lib/asciidoctor/reader.rb, line 391
def read_lines_until options = {}
  result = []
  if @process_lines && options[:skip_processing]
    @process_lines = false
    restore_process_lines = true
  end
  if (terminator = options[:terminator])
    start_cursor = options[:cursor] || cursor
    break_on_blank_lines = false
    break_on_list_continuation = false
  else
    break_on_blank_lines = options[:break_on_blank_lines]
    break_on_list_continuation = options[:break_on_list_continuation]
  end
  skip_comments = options[:skip_line_comments]
  line_read = line_restored = nil
  shift if options[:skip_first_line]
  while (line = read_line)
    if terminator ? line == terminator : ((break_on_blank_lines && line.empty?) ||
        (break_on_list_continuation && line_read && line == LIST_CONTINUATION && (options[:preserve_last_line] = true)) ||
        (block_given? && (yield line)))
      result << line if options[:read_last_line]
      if options[:preserve_last_line]
        unshift line
        line_restored = true
      end
      break
    end
    unless skip_comments && (line.start_with? '//') && !(line.start_with? '///')
      result << line
      line_read = true
    end
  end
  if restore_process_lines
    @process_lines = true
    @look_ahead -= 1 if line_restored && !terminator
  end
  if terminator && terminator != line && (context = options.fetch :context, terminator)
    start_cursor = cursor_at_mark if start_cursor == :at_mark
    logger.warn message_with_context %(unterminated #{context} block), source_location: start_cursor
    @unterminated = true
  end
  result
end
readlines()
Alias for: read_lines
replace_line(replacement)

deprecated

Alias for: replace_next_line
replace_next_line(replacement) click to toggle source

Public: Replace the next line with the specified line.

Calls Reader#advance to consume the current line, then calls Reader#unshift to push the replacement onto the top of the line stack.

replacement - The String line to put in place of the next line (i.e., the line at the cursor).

Returns true.

# File lib/asciidoctor/reader.rb, line 253
def replace_next_line replacement
  shift
  unshift replacement
  true
end
Also aliased as: replace_line
restore_line(line_to_restore)
Alias for: unshift_line
restore_lines(lines_to_restore)
Alias for: unshift_lines
restore_save() click to toggle source

Internal: Restore the state of the reader at cursor

# File lib/asciidoctor/reader.rb, line 545
def restore_save
  if @saved
    @saved.each do |name, val|
      instance_variable_set name, val
    end
    @saved = nil
  end
end
save() click to toggle source

Internal: Save the state of the reader at cursor

# File lib/asciidoctor/reader.rb, line 533
def save
  @saved = {}.tap do |accum|
    instance_variables.each do |name|
      unless name == :@saved || name == :@source_lines
        accum[name] = ::Array === (val = instance_variable_get name) ? (val.drop 0) : val
      end
    end
  end
  nil
end
shift() click to toggle source

Internal: Shift the line off the stack and increment the lineno

This method can be used directly when you’ve already called peek_line and determined that you do, in fact, want to pluck that line off the stack. Use read_line if the line hasn’t (or many not have been) visited yet.

Returns The String line at the top of the stack

# File lib/asciidoctor/reader.rb, line 443
def shift
  @lineno += 1
  @look_ahead -= 1 unless @look_ahead == 0
  @lines.pop
end
skip_blank_lines() click to toggle source

Public: Skip blank lines at the cursor.

Examples

reader.lines
=> ["", "", "Foo", "Bar", ""]
reader.skip_blank_lines
=> 2
reader.lines
=> ["Foo", "Bar", ""]

Returns the [Integer] number of lines skipped or nothing if all lines have been consumed (even if lines were skipped by this method).

# File lib/asciidoctor/reader.rb, line 274
def skip_blank_lines
  return if empty?

  num_skipped = 0
  # optimized code for shortest execution path
  while (next_line = peek_line)
    if next_line.empty?
      shift
      num_skipped += 1
    else
      return num_skipped
    end
  end
end
skip_comment_lines() click to toggle source

Public: Skip consecutive comment lines and block comments.

Examples

@lines
=> ["// foo", "bar"]

comment_lines = skip_comment_lines
=> nil

@lines
=> ["bar"]

Returns nothing

# File lib/asciidoctor/reader.rb, line 302
def skip_comment_lines
  return if empty?

  while (next_line = peek_line) && !next_line.empty?
    if next_line.start_with? '//'
      if next_line.start_with? '///'
        if (ll = next_line.length) > 3 && next_line == '/' * ll
          read_lines_until terminator: next_line, skip_first_line: true, read_last_line: true, skip_processing: true, context: :comment
        else
          break
        end
      else
        shift
      end
    else
      break
    end
  end

  nil
end
skip_line_comments() click to toggle source

Public: Skip consecutive comment lines and return them.

This method assumes the reader only contains simple lines (no blocks).

# File lib/asciidoctor/reader.rb, line 327
def skip_line_comments
  return [] if empty?

  comment_lines = []
  # optimized code for shortest execution path
  while (next_line = peek_line) && !next_line.empty?
    if next_line.start_with? '//'
      comment_lines << shift
    else
      break
    end
  end

  comment_lines
end
source() click to toggle source

Public: Get the source lines for this Reader joined as a String

# File lib/asciidoctor/reader.rb, line 528
def source
  @source_lines.join LF
end
string() click to toggle source

Public: Get a copy of the remaining lines managed by this Reader joined as a String

# File lib/asciidoctor/reader.rb, line 523
def string
  @lines.reverse.join LF
end
terminate() click to toggle source

Public: Advance to the end of the reader, consuming all remaining lines

Returns nothing.

# File lib/asciidoctor/reader.rb, line 346
def terminate
  @lineno += @lines.size
  @lines.clear
  @look_ahead = 0
  nil
end
to_s() click to toggle source
# File lib/asciidoctor/reader.rb, line 559
def to_s
  %(#<#{self.class}@#{object_id} {path: #{@path.inspect}, line: #{@lineno}}>)
end
unshift(line) click to toggle source

Internal: Restore the line to the stack and decrement the lineno

# File lib/asciidoctor/reader.rb, line 450
def unshift line
  @lineno -= 1
  @look_ahead += 1
  @lines.push line
  nil
end
unshift_all(lines_to_restore) click to toggle source

Internal: Restore the lines to the stack and decrement the lineno

# File lib/asciidoctor/reader.rb, line 459
def unshift_all lines_to_restore
  @lineno -= lines_to_restore.size
  @look_ahead += lines_to_restore.size
  if lines_to_restore.respond_to? :reverse
    @lines.push(*lines_to_restore.reverse)
  else
    lines_to_restore.reverse_each {|it| @lines.push it }
  end
  nil
end
unshift_line(line_to_restore) click to toggle source

Public: Push the String line onto the beginning of the Array of source data.

A line pushed on the reader using this method is not processed again. The method assumes the line was previously retrieved from the reader or does not otherwise contain preprocessor directives. Therefore, it is marked as processed immediately.

line_to_restore - the line to restore onto the stack

Returns nothing.

# File lib/asciidoctor/reader.rb, line 225
def unshift_line line_to_restore
  unshift line_to_restore
  nil
end
Also aliased as: restore_line
unshift_lines(lines_to_restore) click to toggle source

Public: Push an Array of lines onto the front of the Array of source data.

Lines pushed on the reader using this method are not processed again. The method assumes the lines were previously retrieved from the reader or do not otherwise contain preprocessor directives. Therefore, they are marked as processed immediately.

Returns nothing.

# File lib/asciidoctor/reader.rb, line 239
def unshift_lines lines_to_restore
  unshift_all lines_to_restore
end
Also aliased as: restore_lines