class Asciidoctor::Reader
Public: Methods for retrieving lines from AsciiDoc source files
def advance direct = true
returns the first element of the internal @lines Array. (default: true)
direct - A Boolean flag to bypasses the check for more lines and immediately
Public: Advance to the next line by discarding the line at the front of the stack
def advance direct = true !!read_line(direct) end
def cursor
def cursor Cursor.new @file, @dir, @path, @lineno end
def eof?
Public: Check whether this reader is empty (contains no lines)
def eof? !has_more_lines? end
def has_more_lines?
peek_line to determine if there is a next line available.
immediately returned the cached value. Otherwise, delegate to
If a previous call to this method resulted in a value of false,
Public: Check whether there are any lines left to read.
def has_more_lines? !(@eof || (@eof = peek_line.nil?)) end
def initialize data = nil, cursor = nil, opts = {:normalize => false}
def initialize data = nil, cursor = nil, opts = {:normalize => false} if !cursor @file = @dir = nil @path = '<stdin>' @lineno = 1 # IMPORTANT lineno assignment must proceed prepare_lines call! elsif cursor.is_a? ::String @file = cursor @dir, @path = ::File.split @file @lineno = 1 # IMPORTANT lineno assignment must proceed prepare_lines call! else @file = cursor.file @dir = cursor.dir @path = cursor.path || '<stdin>' if @file unless @dir # REVIEW might to look at this assignment closer @dir = ::File.dirname @file @dir = nil if @dir == '.' # right? end unless cursor.path @path = ::File.basename @file end end @lineno = cursor.lineno || 1 # IMPORTANT lineno assignment must proceed prepare_lines call! end @lines = data ? (prepare_lines data, opts) : [] @source_lines = @lines.dup @eof = @lines.empty? @look_ahead = 0 @process_lines = true @unescape_next_line = false end
def line_info
Public: Get information about the last line read, including file name and line number.
def line_info %(#{@path}: line #{@lineno}) end
def lines
Public: Get a copy of the remaining Array of String lines managed by this Reader
def lines @lines.dup end
def next_line_empty?
This method Does not consume the line from the stack.
Public: Peek at the next line and check if it's empty (i.e., whitespace only)
def next_line_empty? peek_line.nil_or_empty? end
def peek_line direct = false
Returns the next line of the source data as a String if there are lines remaining.
returns the first element of the internal @lines Array. (default: false)
direct - A Boolean flag to bypasses the check for more lines and immediately
Reader#peek_line is invoked again to perform further processing.
the Reader#process_line is nil, the data is assumed to be changed and
sub-classess the opportunity to do preprocessing. If the return value of
Reader#process_line method to be initialized. This call gives
that has not previously been visited, the line is passed to the
This method will probe the reader for more lines. If there is a next line
already marked as processed, but does not consume it.
Public: Peek at the next line of source data. Processes the line, if not
def peek_line direct = false if direct || @look_ahead > 0 @unescape_next_line ? @lines[0][1..-1] : @lines[0] 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[0]) peek_line else line end end end
def peek_lines num = 1, direct = true
Returns A String Array of the next multiple lines of source data, or an empty Array
direct - A Boolean indicating whether processing should be disabled when reading lines
num - The Integer number of lines to peek.
the lines again.
be processed and marked as such so that subsequent reads will not need to process
restores the lines to the stack before returning them. This allows the lines to
This method delegates to Reader#read_line to process and collect the line, then
already marked as processed, but does not consume them.
Public: Peek at the next multiple lines of source data. Processes the lines, if not
def peek_lines num = 1, direct = true old_look_ahead = @look_ahead result = [] num.times 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
def prepare_lines data, opts = {}
opts - A Hash of options to control what cleansing is done
data - A String Array of input data to be normalized
Any leading or trailing blank lines are also removed.
works.
whitespace substitution is very important to how Asciidoctor
the source data and appends a LF (i.e., Unix endline). This
This method strips whitespace from the end of every line of
Internal: Prepare the lines from the provided data
def prepare_lines data, opts = {} if data.is_a? ::String if opts[:normalize] Helpers.normalize_lines_from_string data else data.split EOL end else if opts[:normalize] Helpers.normalize_lines_array data else data.dup end end end
def prev_line_info
def prev_line_info %(#{@path}: line #{@lineno - 1}) end
def process_line line
invocation of Reader#read_line or nil if the Reader should drop the line,
Returns The String line the Reader should make available to the next
the line unmodified.
by incrementing the look_ahead counter and returns
By default, this method marks the line as processed
Internal: Processes a previously unvisited line
def process_line line @look_ahead += 1 if @process_lines line end
def read
Delegates to Reader#read_lines, then joins the result.
Public: Get the remaining lines of source data joined as a String.
def read read_lines * EOL end
def read_line direct = false
Returns the String of the next line of the source data if data is present.
returns the first element of the internal @lines Array. (default: false)
direct - A Boolean flag to bypasses the check for more lines and immediately
Public: Get the next line of source data. Consumes the line returned.
def read_line direct = false if direct || @look_ahead > 0 || has_more_lines? shift else nil end end
def read_lines
any preprocessors implemented in sub-classes.
Reader#lines in that it processes each line in turn, hence triggering
and returns the lines as a String Array. This method differs from
This method calls Reader#read_line repeatedly until all lines are consumed
Public: Get the remaining lines of source data.
def read_lines lines = [] while has_more_lines? lines << shift end lines end
def read_lines_until options = {}
reader.read_lines_until
reader = Reader.new data, nil, :normalize => true
]
"Third line\n",
"\n",
"Second line\n",
"First line\n",
data = [
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
* :read_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 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 if (terminator = options[:terminator]) 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_line_comments = options[:skip_line_comments] line_read = false line_restored = false complete = false while !complete && (line = read_line) complete = while true break true if terminator && line == terminator # QUESTION: can we get away with line.empty? here? break true if break_on_blank_lines && line.empty? if break_on_list_continuation && line_read && line == LIST_CONTINUATION options[:preserve_last_line] = true break true end break true if block_given? && (yield line) break false end if complete if options[:read_last_line] result << line line_read = true end if options[:preserve_last_line] restore_line line line_restored = true end else unless skip_line_comments && line.start_with?('//') && CommentLineRx =~ line result << line line_read = true end end end if restore_process_lines @process_lines = true @look_ahead -= 1 if line_restored && !terminator end result end
def replace_line replacement
replacement - The String line to put in place of the line at the cursor.
line stack.
Reader#unshift to push the replacement onto the top of the
Calls Reader#advance to consume the current line, then calls
Public: Replace the current line with the specified line.
def replace_line replacement advance unshift replacement nil end
def shift
and determined that you do, in fact, want to pluck that line off the stack.
This method can be used directly when you've already called peek_line
Internal: Shift the line off the stack and increment the lineno
def shift @lineno += 1 @look_ahead -= 1 unless @look_ahead == 0 @lines.shift end
def skip_blank_lines
=> ["Foo", "Bar", ""]
@lines
=> 2
skip_blank_lines
=> ["", "", "Foo", "Bar", ""]
@lines
Examples
Public: Strip off leading blank lines in the Array of lines.
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.empty? advance num_skipped += 1 else return num_skipped end end num_skipped end
def skip_comment_lines opts = {}
=> ["bar"]
@lines
=> ["// foo"]
comment_lines = skip_comment_lines
=> ["// foo", "bar"]
@lines
Examples
Public: Skip consecutive lines containing line comments and return them.
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.empty? comment_lines << shift elsif (commentish = next_line.start_with?('//')) && (match = CommentBlockRx.match(next_line)) comment_lines << shift comment_lines.push(*(read_lines_until(:terminator => match[0], :read_last_line => true, :skip_processing => true))) elsif commentish && CommentLineRx =~ next_line comment_lines << shift else break end end comment_lines end
def skip_line_comments
def skip_line_comments return [] if eof? comment_lines = [] # optimized code for shortest execution path while (next_line = peek_line) if CommentLineRx =~ next_line comment_lines << shift else break end end comment_lines end
def source
def source @source_lines * EOL end
def string
def string @lines * EOL end
def terminate
Public: Advance to the end of the reader, consuming all remaining lines
def terminate @lineno += @lines.size @lines.clear @eof = true @look_ahead = 0 nil end
def to_s
Public: Get a summary of this Reader.
def to_s line_info end
def unshift line
def unshift line @lineno -= 1 @look_ahead += 1 @eof = false @lines.unshift line end
def unshift_line line_to_restore
reader, it is marked as seen.
Since this line was (assumed to be) previously retrieved through the
Public: Push the String line onto the beginning of the Array of source data.
def unshift_line line_to_restore unshift line_to_restore nil end
def unshift_lines lines_to_restore
reader, they are marked as seen.
Since these lines were (assumed to be) previously retrieved through the
Public: Push an Array of lines onto the front of the Array of source data.
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