module RubyDocTest # === Description # Keeps track of which lines within a document belong to a group. Line groups are # determined by their indentation level, as in the Python programming language. # # === Example # This line and the next one # are part of the same group # # But this line is separate from # this line. # # === Note # This class also considers one '#' character (comment) as an indentation character, # i.e. similar to how whitespace is treated. class Lines def initialize(doc_lines, line_index = 0) @doc_lines, @line_index = doc_lines, line_index end def line_number @line_index + 1 end # === Tests # doctest: Return an array of 1 line if there is only one line # >> l = RubyDocTest::Lines.new(["line 1"]) # >> l.lines # => ["line 1"] # # doctest: Remove indentation from lines 2 to the end of this Lines group. # >> l = RubyDocTest::Lines.new(["line 1", " line 2", " line 3", " line 4"]) # >> l.lines # => ["line 1", "line 2", "line 3", " line 4"] def lines r = range size = r.last - r.first + 1 if size > 1 # Remove indentation from all lines after the first, # as measured from the 2nd line's indentation level idt2 = indentation(@doc_lines, @line_index + 1) [@doc_lines[range.first]] + @doc_lines[(range.first + 1)..(range.last)]. map{ |l| $1 if l =~ /^#{Regexp.escape(idt2)}(.*)/ } else @doc_lines[range] end end # === Description # Calculate the range of python-like indentation within this Lines group # # === Tests # >> l = RubyDocTest::Lines.new([]) # # doctest: Return a range of one line when there is only one line to begin with # >> l.range %w(a), 0 # => 0..0 # # doctest: Return a range of one line when there are two lines, side by side # >> l.range %w(a b), 0 # => 0..0 # >> l.range %w(a b), 1 # => 1..1 # # doctest: Return a range of two lines when there are two lines, the second blank # >> l.range ["a", ""], 0 # => 0..1 # # doctest: Return a range of two lines when the second is indented # >> l.range ["a", " b"], 0 # => 0..1 # # doctest: Indentation can also include the ?> marker # >> l.range [">> 1 +", "?> 2"], 0 # => 0..1 def range(doc_lines = @doc_lines, start_index = @line_index) end_index = start_index idt = indentation(doc_lines, start_index) # Find next lines that are blank, or have indentation more than the first line remaining_lines(doc_lines, start_index + 1).each do |current_line| if current_line =~ /^(#{Regexp.escape(idt)}(\s+|\?>\s)|\s*$)/ end_index += 1 else break end end # Compute the range from what we found start_index..end_index end def inspect "#<#{self.class} lines=#{lines.inspect}>" end protected # === Tests # >> l = RubyDocTest::Lines.new([]) # # doctest: Get a whitespace indent from a line with whitespace # >> l.send :indentation, [" a"], 0 # => " " # # doctest: Get a whitespace and '#' indent from a comment line # >> l.send :indentation, [" # a"], 0 # => " # " def indentation(doc_lines = @doc_lines, line_index = @line_index) if doc_lines[line_index] doc_lines[line_index][/^(\s*#\s*|\s*)(\?>\s?)?/] else "" end end # === Description # Get lines from +start_index+ up to the end of the document. # # === Tests # >> l = RubyDocTest::Lines.new([]) # # doctest: Return an empty array if start_index is out of bounds # >> l.send :remaining_lines, [], 1 # => [] # >> l.send :remaining_lines, [], -1 # => [] # # doctest: Return the specified line at start_index, up to and including the # last line of +doc_lines+. # >> l.send :remaining_lines, %w(a b c), 1 # => %w(b c) # >> l.send :remaining_lines, %w(a b c), 2 # => %w(c) # def remaining_lines(doc_lines = @doc_lines, start_index = @line_index) return [] if start_index < 0 or start_index >= doc_lines.size doc_lines[start_index..-1] end end end