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
216 217 218 |
# File 'lib/asciidoctor/reader.rb', line 216 def advance shift ? true : false end |
#cursor ⇒ Object
484 485 486 |
# File 'lib/asciidoctor/reader.rb', line 484 def cursor Cursor.new @file, @dir, @path, @lineno end |
#cursor_at_line(lineno) ⇒ Object
488 489 490 |
# File 'lib/asciidoctor/reader.rb', line 488 def cursor_at_line lineno Cursor.new @file, @dir, @path, lineno end |
#cursor_at_mark ⇒ Object
492 493 494 |
# File 'lib/asciidoctor/reader.rb', line 492 def cursor_at_mark @mark ? Cursor.new(*@mark) : cursor end |
#cursor_at_prev_line ⇒ Object
505 506 507 |
# File 'lib/asciidoctor/reader.rb', line 505 def cursor_at_prev_line Cursor.new @file, @dir, @path, @lineno - 1 end |
#cursor_before_mark ⇒ Object
496 497 498 499 500 501 502 503 |
# File 'lib/asciidoctor/reader.rb', line 496 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.
516 517 518 |
# File 'lib/asciidoctor/reader.rb', line 516 def line_info %(#{@path}: line #{@lineno}) end |
#lines ⇒ A
Get a copy of the remaining Array of String lines managed by this Reader
523 524 525 |
# File 'lib/asciidoctor/reader.rb', line 523 def lines @lines.reverse end |
#mark ⇒ Object
509 510 511 |
# File 'lib/asciidoctor/reader.rb', line 509 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 140 141 142 143 144 |
# File 'lib/asciidoctor/reader.rb', line 127 def peek_line direct = false while true next_line = @lines[-1] if direct || @look_ahead > 0 return @unescape_next_line ? (next_line.slice 1, next_line.length) : next_line elsif next_line # 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 if (line = process_line next_line) return line end else @look_ahead = 0 return end 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.
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/asciidoctor/reader.rb', line 159 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.
209 210 211 |
# File 'lib/asciidoctor/reader.rb', line 209 def read read_lines.join LF end |
#read_line ⇒ Object
Get the next line of source data. Consumes the line returned.
183 184 185 186 |
# File 'lib/asciidoctor/reader.rb', line 183 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.
196 197 198 199 200 201 |
# File 'lib/asciidoctor/reader.rb', line 196 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.
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 435 436 437 438 439 |
# File 'lib/asciidoctor/reader.rb', line 396 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.
258 259 260 261 262 |
# File 'lib/asciidoctor/reader.rb', line 258 def replace_next_line replacement shift unshift replacement true end |
#skip_blank_lines ⇒ Integer
Skip blank lines at the cursor.
279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/asciidoctor/reader.rb', line 279 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.
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/asciidoctor/reader.rb', line 307 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).
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 |
# File 'lib/asciidoctor/reader.rb', line 332 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
533 534 535 |
# File 'lib/asciidoctor/reader.rb', line 533 def source @source_lines.join LF end |
#string ⇒ Object
Get a copy of the remaining lines managed by this Reader joined as a String
528 529 530 |
# File 'lib/asciidoctor/reader.rb', line 528 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
351 352 353 354 355 356 |
# File 'lib/asciidoctor/reader.rb', line 351 def terminate @lineno += @lines.size @lines.clear @look_ahead = 0 nil end |
#to_s ⇒ Object
564 565 566 |
# File 'lib/asciidoctor/reader.rb', line 564 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.
230 231 232 233 |
# File 'lib/asciidoctor/reader.rb', line 230 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.
244 245 246 |
# File 'lib/asciidoctor/reader.rb', line 244 def unshift_lines lines_to_restore unshift_all lines_to_restore end |