class Asciidoctor::Reader
Public: Methods for retrieving lines from AsciiDoc source files
Attributes
Public: Get the 1-based offset of the current line.
Public: Control whether lines are processed using Reader#process_line on first visit (default: true)
Public: Indicates that the end of the reader was reached with a delimited block still open.
Public Class Methods
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
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
# File lib/asciidoctor/reader.rb, line 479 def cursor Cursor.new @file, @dir, @path, @lineno end
# File lib/asciidoctor/reader.rb, line 483 def cursor_at_line lineno Cursor.new @file, @dir, @path, lineno end
# File lib/asciidoctor/reader.rb, line 487 def cursor_at_mark @mark ? Cursor.new(*@mark) : cursor end
# File lib/asciidoctor/reader.rb, line 500 def cursor_at_prev_line Cursor.new @file, @dir, @path, @lineno - 1 end
# 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
Internal: Discard a previous saved state
# File lib/asciidoctor/reader.rb, line 555 def discard_save @saved = nil end
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
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
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
# File lib/asciidoctor/reader.rb, line 504 def mark @mark = @file, @dir, @path, @lineno end
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
# File lib/asciidoctor/reader.rb, line 559 def to_s %(#<#{self.class}@#{object_id} {path: #{@path.inspect}, line: #{@lineno}}>) end
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
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
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
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