Class: Asciidoctor::Reader
- Inherits:
-
Object
- Object
- Asciidoctor::Reader
- Includes:
- Logging
- Defined in:
- lib/asciidoctor/reader.rb
Overview
Methods for retrieving lines from AsciiDoc source files
Direct Known Subclasses
Defined Under Namespace
Classes: Cursor
Instance Attribute Summary collapse
- #dir ⇒ Object readonly
- #file ⇒ Object readonly
-
#lineno ⇒ Object
readonly
Get the 1-based offset of the current line.
- #path ⇒ Object readonly
-
#process_lines ⇒ Object
Control whether lines are processed using Reader#process_line on first visit (default: true).
-
#source_lines ⇒ Object
readonly
Get the document source as a String Array of lines.
-
#unterminated ⇒ Object
Indicates that the end of the reader was reached with a delimited block still open.
Instance Method Summary collapse
-
#advance ⇒ Object
Advance to the next line by discarding the line at the front of the stack.
- #cursor ⇒ Object
- #cursor_at_line(lineno) ⇒ Object
- #cursor_at_mark ⇒ Object
- #cursor_at_prev_line ⇒ Object
- #cursor_before_mark ⇒ Object
-
#empty? ⇒ Boolean
(also: #eof?)
Check whether this reader is empty (contains no lines).
-
#has_more_lines? ⇒ True
Check whether there are any lines left to read.
-
#initialize(data = nil, cursor = nil, opts = {}) ⇒ Reader
constructor
Initialize the Reader object.
-
#line_info ⇒ A
Get information about the last line read, including file name and line number.
-
#lines ⇒ A
Get a copy of the remaining Array of String lines managed by this Reader.
- #mark ⇒ Object
-
#next_line_empty? ⇒ True
Peek at the next line and check if it’s empty (i.e., whitespace only).
-
#peek_line(direct = false) ⇒ Object
Peek at the next line of source data.
-
#peek_lines(num = nil, direct = false) ⇒ A
Peek at the next multiple lines of source data.
-
#read ⇒ Object
Get the remaining lines of source data joined as a String.
-
#read_line ⇒ Object
Get the next line of source data.
-
#read_lines ⇒ Object
(also: #readlines)
Get the remaining lines of source data.
-
#read_lines_until(options = {}) ⇒ Object
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.
-
#replace_next_line(replacement) ⇒ Object
(also: #replace_line)
Replace the next line with the specified line.
-
#skip_blank_lines ⇒ Integer
Skip blank lines at the cursor.
-
#skip_comment_lines ⇒ void
Skip consecutive comment lines and block comments.
-
#skip_line_comments ⇒ Object
Skip consecutive comment lines and return them.
-
#source ⇒ Object
Get the source lines for this Reader joined as a String.
-
#string ⇒ Object
Get a copy of the remaining lines managed by this Reader joined as a String.
-
#terminate ⇒ void
Advance to the end of the reader, consuming all remaining lines.
- #to_s ⇒ Object
-
#unshift_line(line_to_restore) ⇒ void
(also: #restore_line)
Push the String line onto the beginning of the Array of source data.
-
#unshift_lines(lines_to_restore) ⇒ void
(also: #restore_lines)
Push an Array of lines onto the front of the Array of source data.
Methods included from Logging
#logger, #message_with_context
Constructor Details
#initialize(data = nil, cursor = nil, opts = {}) ⇒ Reader
Initialize the Reader object
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# 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 |
Instance Attribute Details
#dir ⇒ Object (readonly)
26 27 28 |
# File 'lib/asciidoctor/reader.rb', line 26 def dir @dir end |
#file ⇒ Object (readonly)
25 26 27 |
# File 'lib/asciidoctor/reader.rb', line 25 def file @file end |
#lineno ⇒ Object (readonly)
Get the 1-based offset of the current line.
30 31 32 |
# File 'lib/asciidoctor/reader.rb', line 30 def lineno @lineno end |
#path ⇒ Object (readonly)
27 28 29 |
# File 'lib/asciidoctor/reader.rb', line 27 def path @path end |
#process_lines ⇒ Object
Control whether lines are processed using Reader#process_line on first visit (default: true)
36 37 38 |
# File 'lib/asciidoctor/reader.rb', line 36 def process_lines @process_lines end |
#source_lines ⇒ Object (readonly)
Get the document source as a String Array of lines.
33 34 35 |
# File 'lib/asciidoctor/reader.rb', line 33 def source_lines @source_lines end |
#unterminated ⇒ Object
Indicates that the end of the reader was reached with a delimited block still open.
39 40 41 |
# File 'lib/asciidoctor/reader.rb', line 39 def unterminated @unterminated end |
Instance Method Details
#advance ⇒ Object
Advance to the next line by discarding the line at the front of the stack
211 212 213 |
# File 'lib/asciidoctor/reader.rb', line 211 def advance shift ? true : false end |
#cursor ⇒ Object
479 480 481 |
# File 'lib/asciidoctor/reader.rb', line 479 def cursor Cursor.new @file, @dir, @path, @lineno end |
#cursor_at_line(lineno) ⇒ Object
483 484 485 |
# File 'lib/asciidoctor/reader.rb', line 483 def cursor_at_line lineno Cursor.new @file, @dir, @path, lineno end |
#cursor_at_mark ⇒ Object
487 488 489 |
# File 'lib/asciidoctor/reader.rb', line 487 def cursor_at_mark @mark ? Cursor.new(*@mark) : cursor end |
#cursor_at_prev_line ⇒ Object
500 501 502 |
# File 'lib/asciidoctor/reader.rb', line 500 def cursor_at_prev_line Cursor.new @file, @dir, @path, @lineno - 1 end |
#cursor_before_mark ⇒ Object
491 492 493 494 495 496 497 498 |
# 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 |
#empty? ⇒ Boolean Also known as: eof?
Check whether this reader is empty (contains no lines)
90 91 92 93 94 95 96 97 |
# File 'lib/asciidoctor/reader.rb', line 90 def empty? if @lines.empty? @look_ahead = 0 true else false end end |
#has_more_lines? ⇒ True
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.
78 79 80 81 82 83 84 85 |
# File 'lib/asciidoctor/reader.rb', line 78 def has_more_lines? if @lines.empty? @look_ahead = 0 false else true end end |
#line_info ⇒ A
Get information about the last line read, including file name and line number.
511 512 513 |
# File 'lib/asciidoctor/reader.rb', line 511 def line_info %(#{@path}: line #{@lineno}) end |
#lines ⇒ A
Get a copy of the remaining Array of String lines managed by this Reader
518 519 520 |
# File 'lib/asciidoctor/reader.rb', line 518 def lines @lines.reverse end |
#mark ⇒ Object
504 505 506 |
# File 'lib/asciidoctor/reader.rb', line 504 def mark @mark = @file, @dir, @path, @lineno end |
#next_line_empty? ⇒ True
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.
105 106 107 |
# File 'lib/asciidoctor/reader.rb', line 105 def next_line_empty? peek_line.nil_or_empty? end |
#peek_line(direct = false) ⇒ Object
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).
127 128 129 130 131 132 133 134 135 136 137 138 139 |
# 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) ⇒ A
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.
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# 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 ⇒ Object
Get the remaining lines of source data joined as a String.
Delegates to Reader#read_lines, then joins the result.
204 205 206 |
# File 'lib/asciidoctor/reader.rb', line 204 def read read_lines.join LF end |
#read_line ⇒ Object
Get the next line of source data. Consumes the line returned.
178 179 180 181 |
# 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 ⇒ Object Also known as: readlines
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.
191 192 193 194 195 196 |
# File 'lib/asciidoctor/reader.rb', line 191 def read_lines lines = [] # has_more_lines? triggers preprocessor lines << shift while has_more_lines? lines end |
#read_lines_until(options = {}) ⇒ Object
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.
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'lib/asciidoctor/reader.rb', line 391 def read_lines_until = {} result = [] if @process_lines && [:skip_processing] @process_lines = false restore_process_lines = true end if (terminator = [:terminator]) start_cursor = [:cursor] || cursor break_on_blank_lines = false break_on_list_continuation = false else break_on_blank_lines = [:break_on_blank_lines] break_on_list_continuation = [:break_on_list_continuation] end skip_comments = [:skip_line_comments] line_read = line_restored = nil shift if [: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 && ([:preserve_last_line] = true)) || (block_given? && (yield line))) result << line if [:read_last_line] if [: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 = .fetch :context, terminator) start_cursor = cursor_at_mark if start_cursor == :at_mark logger.warn %(unterminated #{context} block), source_location: start_cursor @unterminated = true end result end |
#replace_next_line(replacement) ⇒ Object Also known as: replace_line
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.
253 254 255 256 257 |
# File 'lib/asciidoctor/reader.rb', line 253 def replace_next_line replacement shift unshift replacement true end |
#skip_blank_lines ⇒ Integer
Skip blank lines at the cursor.
274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# 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 ⇒ void
This method returns an undefined value.
Skip consecutive comment lines and block comments.
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# 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 ⇒ Object
Skip consecutive comment lines and return them.
This method assumes the reader only contains simple lines (no blocks).
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 |
# 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 ⇒ Object
Get the source lines for this Reader joined as a String
528 529 530 |
# File 'lib/asciidoctor/reader.rb', line 528 def source @source_lines.join LF end |
#string ⇒ Object
Get a copy of the remaining lines managed by this Reader joined as a String
523 524 525 |
# File 'lib/asciidoctor/reader.rb', line 523 def string @lines.reverse.join LF end |
#terminate ⇒ void
This method returns an undefined value.
Advance to the end of the reader, consuming all remaining lines
346 347 348 349 350 351 |
# File 'lib/asciidoctor/reader.rb', line 346 def terminate @lineno += @lines.size @lines.clear @look_ahead = 0 nil end |
#to_s ⇒ Object
559 560 561 |
# File 'lib/asciidoctor/reader.rb', line 559 def to_s %(#<#{self.class}@#{object_id} {path: #{@path.inspect}, line: #{@lineno}}>) end |
#unshift_line(line_to_restore) ⇒ void Also known as: restore_line
This method returns an undefined value.
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.
225 226 227 228 |
# File 'lib/asciidoctor/reader.rb', line 225 def unshift_line line_to_restore unshift line_to_restore nil end |
#unshift_lines(lines_to_restore) ⇒ void Also known as: restore_lines
This method returns an undefined value.
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.
239 240 241 |
# File 'lib/asciidoctor/reader.rb', line 239 def unshift_lines lines_to_restore unshift_all lines_to_restore end |