Public: Methods for retrieving lines from AsciiDoc source files
Public: Get the 1-based offset of the current line.
Public: Control whether lines are processed using #process_line on first visit (default: true)
Public: Get the document source as a String Array of lines.
Public: Initialize the Reader object
# File lib/asciidoctor/reader.rb, line 36 def initialize data = nil, cursor = nil if cursor.nil? @file = @dir = nil @path = '<stdin>' @lineno = 1 # IMPORTANT lineno assignment must proceed prepare_lines call! elsif cursor.is_a? String @file = cursor @dir = File.dirname @file @path = File.basename @file @lineno = 1 # IMPORTANT lineno assignment must proceed prepare_lines call! else @file = cursor.file @dir = cursor.dir @path = cursor.path || '<stdin>' unless @file.nil? if @dir.nil? # REVIEW might to look at this assignment closer @dir = File.dirname @file @dir = nil if @dir == '.' # right? end if cursor.path.nil? @path = File.basename @file end end @lineno = cursor.lineno || 1 # IMPORTANT lineno assignment must proceed prepare_lines call! end @lines = data.nil? ? [] : (prepare_lines data) @source_lines = @lines.dup @eof = @lines.empty? @look_ahead = 0 @process_lines = true @unescape_next_line = false end
Public: Advance to the next line by discarding the line at the front of the stack
direct - A Boolean flag to bypasses the check for more lines and immediately
returns the first element of the internal @lines Array. (default: true)
returns a Boolean indicating whether there was a line to discard.
# File lib/asciidoctor/reader.rb, line 237 def advance direct = true !(read_line direct).nil? end
# File lib/asciidoctor/reader.rb, line 489 def cursor Cursor.new @file, @dir, @path, @lineno 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 377 def eof? !has_more_lines? 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 111 def has_more_lines? !(@eof || (@eof = peek_line.nil?)) 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 496 def line_info %Q(#{@path}: line #{@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 120 def next_line_empty? (line = peek_line).nil? || line.chomp.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#preprocess_line method to be initialized. This call gives sub-classess the opportunity to do preprocessing. If the return value of the #process_line is nil, the data is assumed to be changed and #peek_line is invoked again to perform further processing.
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 nil if there is no more data.
# File lib/asciidoctor/reader.rb, line 139 def peek_line direct = false if direct || @look_ahead > 0 @unescape_next_line ? @lines.first[1..-1] : @lines.first elsif @eof || @lines.empty? @eof = true @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 proxy lines if (line = process_line @lines.first).nil? peek_line else line end 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 #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 Integer number of lines to peek. direct - A Boolean indicating whether processing should be disabled when reading lines
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 171 def peek_lines num = 1, direct = true old_look_ahead = @look_ahead result = [] (1..num).each do if (line = read_line direct) result << line else break end end unless result.empty? result.reverse_each {|line| unshift line } @look_ahead = old_look_ahead if direct end result end
Internal: Prepare the lines from the provided data
This method strips whitespace from the end of every line of the source data and appends a LF (i.e., Unix endline). This whitespace substitution is very important to how Asciidoctor works.
Any leading or trailing blank lines are also removed.
The normalized lines are assigned to the @lines instance variable.
data - A String Array of input data to be normalized opts - A Hash of options to control what cleansing is done
Returns The String lines extracted from the data
# File lib/asciidoctor/reader.rb, line 86 def prepare_lines data, opts = {} data.is_a?(String) ? data.each_line.to_a : data.dup end
# File lib/asciidoctor/reader.rb, line 501 def prev_line_info %Q(#{@path}: line #{@lineno - 1}) end
Internal: Processes a previously unvisited line
By default, this method marks the line as processed by incrementing the look_ahead counter and returns the line unmodified.
Returns The String line the Reader should make available to the next invocation of #read_line or nil if the Reader should drop the line, advance to the next line and process it.
# File lib/asciidoctor/reader.rb, line 99 def process_line line @look_ahead += 1 if @process_lines line end
Public: Get the remaining lines of source data joined as a String.
Delegates to #read_lines, then joins the result.
Returns the lines read joined as a String
# File lib/asciidoctor/reader.rb, line 227 def read read_lines.join end
Public: Get the next line of source data. Consumes the line returned.
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 String of the next line of the source data if data is present. Returns nil if there is no more data.
# File lib/asciidoctor/reader.rb, line 197 def read_line direct = false if direct || @look_ahead > 0 || has_more_lines? shift else nil end end
Public: Get the remaining lines of source data.
This method calls #read_line repeatedly until all lines are consumed and returns the lines as a String Array. This method differs from #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 213 def read_lines lines = [] while has_more_lines? lines << read_line end 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:
* :break_on_blank_lines may be used to specify to break on blank lines * :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
Returns the Array of lines forming the next segment.
Examples
reader = Reader.new ["First paragraph\n", "Second paragraph\n", "Open block\n", "\n", "Can have blank lines\n", "--\n", "\n", "In a different segment\n"] reader.read_lines_until => ["First paragraph\n", "Second paragraph\n", "Open block\n"]
# File lib/asciidoctor/reader.rb, line 408 def read_lines_until options = {} result = [] advance if options[:skip_first_line] if @process_lines && options[:skip_processing] @process_lines = false restore_process_lines = true else restore_process_lines = false end has_block = block_given? if (terminator = options[:terminator]) break_on_blank_lines = false break_on_list_continuation = false chomp_last_line = options.fetch :chomp_last_line, false else break_on_blank_lines = options[:break_on_blank_lines] break_on_list_continuation = options[:break_on_list_continuation] chomp_last_line = break_on_blank_lines end skip_line_comments = options[:skip_line_comments] line_read = false line_restored = false while (line = read_line) finish = while true break true if terminator && line.chomp == terminator # QUESTION: can we get away with line.chomp.empty? here? break true if break_on_blank_lines && line.chomp.empty? if break_on_list_continuation && line_read && line.chomp == LIST_CONTINUATION options[:preserve_last_line] = true break true end break true if has_block && (yield line) break false end if finish if options[:read_last_line] result << line line_read = true end if options[:preserve_last_line] restore_line line line_restored = true end break end unless skip_line_comments && line.start_with?('//') && line.match(REGEXP[:comment]) result << line line_read = true end end if chomp_last_line && line_read result << result.pop.chomp end if restore_process_lines @process_lines = true @look_ahead -= 1 if line_restored && terminator.nil? end result end
Public: Replace the current line with the specified line.
Calls #advance to consume the current line, then calls #unshift to push the replacement onto the top of the line stack.
replacement - The String line to put in place of the line at the cursor.
Returns nothing.
# File lib/asciidoctor/reader.rb, line 275 def replace_line replacement advance unshift replacement nil end
Internal: Shift the line off the stack and increment the lineno
# File lib/asciidoctor/reader.rb, line 475 def shift @lineno += 1 @look_ahead -= 1 unless @look_ahead == 0 @lines.shift end
Public: Strip off leading blank lines in the Array of lines.
Examples
@lines => ["\n", "\t\n", "Foo\n", "Bar\n", "\n"] skip_blank_lines => 2 @lines => ["Foo\n", "Bar\n"]
Returns an Integer of the number of lines skipped
# File lib/asciidoctor/reader.rb, line 295 def skip_blank_lines return 0 if eof? num_skipped = 0 # optimized code for shortest execution path while (next_line = peek_line) if next_line.chomp.empty? advance num_skipped += 1 else return num_skipped end end num_skipped end
Public: Skip consecutive lines containing line comments and return them.
Examples
@lines => ["// foo\n", "bar\n"] comment_lines = skip_comment_lines => ["// foo\n"] @lines => ["bar\n"]
Returns the Array of lines that were skipped
# File lib/asciidoctor/reader.rb, line 325 def skip_comment_lines opts = {} return [] if eof? comment_lines = [] include_blank_lines = opts[:include_blank_lines] while (next_line = peek_line) if include_blank_lines && next_line.chomp.empty? comment_lines << read_line elsif (commentish = next_line.start_with?('//')) && (match = next_line.match(REGEXP[:comment_blk])) comment_lines << read_line comment_lines.push(*(read_lines_until(:terminator => match[0], :read_last_line => true, :skip_processing => true))) elsif commentish && next_line.match(REGEXP[:comment]) comment_lines << read_line else break end end comment_lines end
Public: Skip consecutive lines that are line comments and return them.
# File lib/asciidoctor/reader.rb, line 347 def skip_line_comments return [] if eof? comment_lines = [] # optimized code for shortest execution path while (next_line = peek_line) if next_line.match(REGEXP[:comment]) comment_lines << read_line else break end end comment_lines end
Public: Get the source lines for this Reader joined as a String
# File lib/asciidoctor/reader.rb, line 518 def source @source_lines.join end
Public: Get a copy of the remaining lines managed by this Reader joined as a String
# File lib/asciidoctor/reader.rb, line 513 def string @lines.join end
Public: Advance to the end of the reader, consuming all remaining lines
Returns nothing.
# File lib/asciidoctor/reader.rb, line 366 def terminate @lineno += @lines.size @lines.clear @eof = true @look_ahead = 0 nil end
Public: Get a summary of this Reader.
Returns A string summary of this reader, which contains the path and line information
# File lib/asciidoctor/reader.rb, line 526 def to_s line_info end
Internal: Restore the line to the stack and decrement the lineno
# File lib/asciidoctor/reader.rb, line 482 def unshift line @lineno -= 1 @look_ahead += 1 @eof = false @lines.unshift line end
Public: Push the String line onto the beginning of the Array of source data.
Since this line was (assumed to be) previously retrieved through the reader, it is marked as seen.
returns nil
# File lib/asciidoctor/reader.rb, line 247 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.
Since these lines were (assumed to be) previously retrieved through the reader, they are marked as seen.
Returns nil
# File lib/asciidoctor/reader.rb, line 259 def unshift_lines lines_to_restore # QUESTION is it faster to use unshift(*lines_to_restore)? lines_to_restore.reverse_each {|line| unshift line } nil end