rubydoctest 0.2.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +10 -0
 - data/License.txt +16 -17
 - data/Manifest.txt +13 -5
 - data/PostInstall.txt +2 -3
 - data/README.txt +48 -57
 - data/Ruby DocTest.tmproj +197 -0
 - data/bin/rubydoctest +41 -25
 - data/config/hoe.rb +3 -5
 - data/lib/code_block.rb +68 -0
 - data/lib/doctest_require.rb +3 -0
 - data/lib/lines.rb +143 -0
 - data/lib/result.rb +63 -0
 - data/lib/rubydoctest.rb +13 -255
 - data/lib/rubydoctest/version.rb +3 -3
 - data/lib/runner.rb +370 -0
 - data/lib/special_directive.rb +44 -0
 - data/lib/statement.rb +75 -0
 - data/lib/test.rb +29 -0
 - data/rubydoctest.gemspec +6 -6
 - data/script/console +0 -0
 - data/script/destroy +0 -0
 - data/script/generate +0 -0
 - data/script/rstakeout +92 -0
 - data/script/txt2html +0 -0
 - data/tasks/doctests.rake +2 -1
 - data/textmate/DocTest (Markdown).textmate +7 -0
 - data/textmate/DocTest (Ruby).textmate +55 -0
 - data/textmate/DocTest (Text).textmate +66 -0
 - data/website/index.html +141 -11
 - data/website/template.html.erb +1 -1
 - metadata +22 -15
 - data/test/doctest/file_relative.doctest +0 -5
 - data/test/doctest/runner.doctest +0 -58
 - data/test/doctest/simple.doctest +0 -30
 - data/test/test_helper.rb +0 -2
 - data/test/test_rubydoctest.rb +0 -11
 
    
        data/lib/rubydoctest/version.rb
    CHANGED
    
    
    
        data/lib/runner.rb
    ADDED
    
    | 
         @@ -0,0 +1,370 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            $:.unshift(File.dirname(__FILE__)) unless
         
     | 
| 
      
 2 
     | 
    
         
            +
              $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require 'rubydoctest'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'statement'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'result'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'special_directive'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require 'code_block'
         
     | 
| 
      
 9 
     | 
    
         
            +
            require 'test'
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            module RubyDocTest
         
     | 
| 
      
 12 
     | 
    
         
            +
              class Runner
         
     | 
| 
      
 13 
     | 
    
         
            +
                attr_reader :groups, :blocks, :tests
         
     | 
| 
      
 14 
     | 
    
         
            +
                
         
     | 
| 
      
 15 
     | 
    
         
            +
                @@color = {
         
     | 
| 
      
 16 
     | 
    
         
            +
                  :html => {
         
     | 
| 
      
 17 
     | 
    
         
            +
                    :red    => %{<font color="red">%s</font>},
         
     | 
| 
      
 18 
     | 
    
         
            +
                    :yellow => %{<font color="#C0C000">%s</font>},
         
     | 
| 
      
 19 
     | 
    
         
            +
                    :green  => %{<font color="green">%s</font>}
         
     | 
| 
      
 20 
     | 
    
         
            +
                  },
         
     | 
| 
      
 21 
     | 
    
         
            +
                  :ansi => {
         
     | 
| 
      
 22 
     | 
    
         
            +
                    :red    => %{\e[31m%s\e[0m},
         
     | 
| 
      
 23 
     | 
    
         
            +
                    :yellow => %{\e[33m%s\e[0m},
         
     | 
| 
      
 24 
     | 
    
         
            +
                    :green  => %{\e[32m%s\e[0m}
         
     | 
| 
      
 25 
     | 
    
         
            +
                  },
         
     | 
| 
      
 26 
     | 
    
         
            +
                  :plain => {
         
     | 
| 
      
 27 
     | 
    
         
            +
                    :red    => "%s",
         
     | 
| 
      
 28 
     | 
    
         
            +
                    :yellow => "%s",
         
     | 
| 
      
 29 
     | 
    
         
            +
                    :green  => "%s"
         
     | 
| 
      
 30 
     | 
    
         
            +
                  }
         
     | 
| 
      
 31 
     | 
    
         
            +
                }
         
     | 
| 
      
 32 
     | 
    
         
            +
                
         
     | 
| 
      
 33 
     | 
    
         
            +
                # The evaluation mode, either :doctest or :ruby.
         
     | 
| 
      
 34 
     | 
    
         
            +
                #
         
     | 
| 
      
 35 
     | 
    
         
            +
                # Modes:
         
     | 
| 
      
 36 
     | 
    
         
            +
                #   :doctest
         
     | 
| 
      
 37 
     | 
    
         
            +
                #     - The the Runner expects the file to contain text (e.g. a markdown file).
         
     | 
| 
      
 38 
     | 
    
         
            +
                #       In addition, it assumes that the text will occasionally be interspersed
         
     | 
| 
      
 39 
     | 
    
         
            +
                #       with irb lines which it should eval, e.g. '>>' and '=>'.
         
     | 
| 
      
 40 
     | 
    
         
            +
                #
         
     | 
| 
      
 41 
     | 
    
         
            +
                #   :ruby
         
     | 
| 
      
 42 
     | 
    
         
            +
                #     - The Runner expects the file to be a Ruby source file.  The source may contain
         
     | 
| 
      
 43 
     | 
    
         
            +
                #       comments that are interspersed with irb lines to eval, e.g. '>>' and '=>'.
         
     | 
| 
      
 44 
     | 
    
         
            +
                attr_accessor :mode
         
     | 
| 
      
 45 
     | 
    
         
            +
                
         
     | 
| 
      
 46 
     | 
    
         
            +
                # === Tests
         
     | 
| 
      
 47 
     | 
    
         
            +
                # 
         
     | 
| 
      
 48 
     | 
    
         
            +
                # doctest: Runner mode should default to :doctest and :ruby from the filename
         
     | 
| 
      
 49 
     | 
    
         
            +
                # >> r = RubyDocTest::Runner.new("", "test.doctest")
         
     | 
| 
      
 50 
     | 
    
         
            +
                # >> r.mode
         
     | 
| 
      
 51 
     | 
    
         
            +
                # => :doctest
         
     | 
| 
      
 52 
     | 
    
         
            +
                #
         
     | 
| 
      
 53 
     | 
    
         
            +
                # >> r = RubyDocTest::Runner.new("", "test.rb")
         
     | 
| 
      
 54 
     | 
    
         
            +
                # >> r.mode
         
     | 
| 
      
 55 
     | 
    
         
            +
                # => :ruby
         
     | 
| 
      
 56 
     | 
    
         
            +
                #
         
     | 
| 
      
 57 
     | 
    
         
            +
                # doctest: The src_lines should be separated into an array
         
     | 
| 
      
 58 
     | 
    
         
            +
                # >> r = RubyDocTest::Runner.new("a\nb\n", "test.doctest")
         
     | 
| 
      
 59 
     | 
    
         
            +
                # >> r.instance_variable_get("@src_lines")
         
     | 
| 
      
 60 
     | 
    
         
            +
                # => ["a", "b"]
         
     | 
| 
      
 61 
     | 
    
         
            +
                def initialize(src, file_name = "test.doctest", initial_mode = nil)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  @src, @file_name = src, file_name
         
     | 
| 
      
 63 
     | 
    
         
            +
                  @mode = initial_mode || (File.extname(file_name) == ".rb" ? :ruby : :doctest)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  
         
     | 
| 
      
 65 
     | 
    
         
            +
                  @src_lines = src.split("\n")
         
     | 
| 
      
 66 
     | 
    
         
            +
                  @groups, @blocks = [], []
         
     | 
| 
      
 67 
     | 
    
         
            +
                  $rubydoctest = self
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
                
         
     | 
| 
      
 70 
     | 
    
         
            +
                # doctest: Using the doctest_require: SpecialDirective should require a file relative to the current one.
         
     | 
| 
      
 71 
     | 
    
         
            +
                # >> r = RubyDocTest::Runner.new("# doctest_require: 'doctest_require.rb'", __FILE__)
         
     | 
| 
      
 72 
     | 
    
         
            +
                # >> r.prepare_tests
         
     | 
| 
      
 73 
     | 
    
         
            +
                # >> is_doctest_require_successful?
         
     | 
| 
      
 74 
     | 
    
         
            +
                # => true
         
     | 
| 
      
 75 
     | 
    
         
            +
                def prepare_tests
         
     | 
| 
      
 76 
     | 
    
         
            +
                  @groups = read_groups
         
     | 
| 
      
 77 
     | 
    
         
            +
                  @blocks = organize_blocks
         
     | 
| 
      
 78 
     | 
    
         
            +
                  @tests = organize_tests
         
     | 
| 
      
 79 
     | 
    
         
            +
                  eval(@src, TOPLEVEL_BINDING, @file_name) if @mode == :ruby
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
                
         
     | 
| 
      
 82 
     | 
    
         
            +
                # === Tests
         
     | 
| 
      
 83 
     | 
    
         
            +
                # doctest: Run through a simple inline doctest (rb) file and see if it passes
         
     | 
| 
      
 84 
     | 
    
         
            +
                # >> file = File.join(File.dirname(__FILE__), "..", "test", "inline.rb")
         
     | 
| 
      
 85 
     | 
    
         
            +
                # >> r = RubyDocTest::Runner.new(IO.read(file), "inline.rb")
         
     | 
| 
      
 86 
     | 
    
         
            +
                # >> r.pass?
         
     | 
| 
      
 87 
     | 
    
         
            +
                # => true
         
     | 
| 
      
 88 
     | 
    
         
            +
                def pass?
         
     | 
| 
      
 89 
     | 
    
         
            +
                  prepare_tests
         
     | 
| 
      
 90 
     | 
    
         
            +
                  @tests.all?{ |t| t.pass? }
         
     | 
| 
      
 91 
     | 
    
         
            +
                end
         
     | 
| 
      
 92 
     | 
    
         
            +
                
         
     | 
| 
      
 93 
     | 
    
         
            +
                # === Description
         
     | 
| 
      
 94 
     | 
    
         
            +
                # Starts an IRB prompt when the "!!!" SpecialDirective is given.
         
     | 
| 
      
 95 
     | 
    
         
            +
                def start_irb
         
     | 
| 
      
 96 
     | 
    
         
            +
                  IRB.init_config(nil)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  IRB.conf[:PROMPT_MODE] = :SIMPLE
         
     | 
| 
      
 98 
     | 
    
         
            +
                  irb = IRB::Irb.new(IRB::WorkSpace.new(TOPLEVEL_BINDING))
         
     | 
| 
      
 99 
     | 
    
         
            +
                  IRB.conf[:MAIN_CONTEXT] = irb.context
         
     | 
| 
      
 100 
     | 
    
         
            +
                  catch(:IRB_EXIT) do
         
     | 
| 
      
 101 
     | 
    
         
            +
                    irb.eval_input
         
     | 
| 
      
 102 
     | 
    
         
            +
                  end
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
                
         
     | 
| 
      
 105 
     | 
    
         
            +
                def format_color(text, color)
         
     | 
| 
      
 106 
     | 
    
         
            +
                  @@color[RubyDocTest.output_format][color] % text.to_s
         
     | 
| 
      
 107 
     | 
    
         
            +
                end
         
     | 
| 
      
 108 
     | 
    
         
            +
                
         
     | 
| 
      
 109 
     | 
    
         
            +
                def run
         
     | 
| 
      
 110 
     | 
    
         
            +
                  prepare_tests
         
     | 
| 
      
 111 
     | 
    
         
            +
                  newline = "\n       "
         
     | 
| 
      
 112 
     | 
    
         
            +
                  everything_passed = true
         
     | 
| 
      
 113 
     | 
    
         
            +
                  puts "=== Testing '#{@file_name}'..."
         
     | 
| 
      
 114 
     | 
    
         
            +
                  ok, fail, err = 0, 0, 0
         
     | 
| 
      
 115 
     | 
    
         
            +
                  @tests.each do |t|
         
     | 
| 
      
 116 
     | 
    
         
            +
                    if SpecialDirective === t and t.name == "!!!"
         
     | 
| 
      
 117 
     | 
    
         
            +
                      start_irb unless RubyDocTest.ignore_interactive
         
     | 
| 
      
 118 
     | 
    
         
            +
                    else
         
     | 
| 
      
 119 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 120 
     | 
    
         
            +
                        if t.pass?
         
     | 
| 
      
 121 
     | 
    
         
            +
                          ok += 1
         
     | 
| 
      
 122 
     | 
    
         
            +
                          status = ["OK".center(4), :green]
         
     | 
| 
      
 123 
     | 
    
         
            +
                          detail = nil
         
     | 
| 
      
 124 
     | 
    
         
            +
                        else
         
     | 
| 
      
 125 
     | 
    
         
            +
                          fail += 1
         
     | 
| 
      
 126 
     | 
    
         
            +
                          everything_passed = false
         
     | 
| 
      
 127 
     | 
    
         
            +
                          status = ["FAIL".center(4), :red]
         
     | 
| 
      
 128 
     | 
    
         
            +
                          detail = format_color(
         
     | 
| 
      
 129 
     | 
    
         
            +
                            "Got: #{t.actual_result}#{newline}Expected: #{t.expected_result}" + newline +
         
     | 
| 
      
 130 
     | 
    
         
            +
                              "  from #{@file_name}:#{t.first_failed.result.line_number}",
         
     | 
| 
      
 131 
     | 
    
         
            +
                            :red)
         
     | 
| 
      
 132 
     | 
    
         
            +
                          
         
     | 
| 
      
 133 
     | 
    
         
            +
                        end
         
     | 
| 
      
 134 
     | 
    
         
            +
                      rescue EvaluationError => e
         
     | 
| 
      
 135 
     | 
    
         
            +
                        err += 1
         
     | 
| 
      
 136 
     | 
    
         
            +
                        status = ["ERR".center(4), :yellow]
         
     | 
| 
      
 137 
     | 
    
         
            +
                        exception_text = e.original_exception.to_s.split("\n").join(newline)
         
     | 
| 
      
 138 
     | 
    
         
            +
                        if RubyDocTest.output_format == :html
         
     | 
| 
      
 139 
     | 
    
         
            +
                          exception_text = exception_text.gsub("<", "<").gsub(">", ">")
         
     | 
| 
      
 140 
     | 
    
         
            +
                        end
         
     | 
| 
      
 141 
     | 
    
         
            +
                        detail = format_color(
         
     | 
| 
      
 142 
     | 
    
         
            +
                          "#{e.original_exception.class.to_s}: #{exception_text}" + newline +
         
     | 
| 
      
 143 
     | 
    
         
            +
                            "  from #{@file_name}:#{e.statement.line_number}" + newline +
         
     | 
| 
      
 144 
     | 
    
         
            +
                            e.statement.source_code,
         
     | 
| 
      
 145 
     | 
    
         
            +
                          :yellow)
         
     | 
| 
      
 146 
     | 
    
         
            +
                      end
         
     | 
| 
      
 147 
     | 
    
         
            +
                      puts \
         
     | 
| 
      
 148 
     | 
    
         
            +
                        "#{format_color(*status)} | " +
         
     | 
| 
      
 149 
     | 
    
         
            +
                        "#{t.description.split("\n").join(newline)}" +
         
     | 
| 
      
 150 
     | 
    
         
            +
                        (detail ? newline + detail : "")
         
     | 
| 
      
 151 
     | 
    
         
            +
                    end
         
     | 
| 
      
 152 
     | 
    
         
            +
                  end
         
     | 
| 
      
 153 
     | 
    
         
            +
                  puts \
         
     | 
| 
      
 154 
     | 
    
         
            +
                    "#{@blocks.select{ |b| b.is_a? CodeBlock }.size} comparisons, " +
         
     | 
| 
      
 155 
     | 
    
         
            +
                    "#{@tests.size} doctests, " +
         
     | 
| 
      
 156 
     | 
    
         
            +
                    "#{fail} failures, " +
         
     | 
| 
      
 157 
     | 
    
         
            +
                    "#{err} errors"
         
     | 
| 
      
 158 
     | 
    
         
            +
                  everything_passed
         
     | 
| 
      
 159 
     | 
    
         
            +
                end
         
     | 
| 
      
 160 
     | 
    
         
            +
                
         
     | 
| 
      
 161 
     | 
    
         
            +
                # === Tests
         
     | 
| 
      
 162 
     | 
    
         
            +
                # 
         
     | 
| 
      
 163 
     | 
    
         
            +
                # doctest: Non-statement lines get ignored while statement / result lines are included
         
     | 
| 
      
 164 
     | 
    
         
            +
                #          Default mode is :doctest, so non-irb prompts should be ignored.
         
     | 
| 
      
 165 
     | 
    
         
            +
                # >> r = RubyDocTest::Runner.new("a\nb\n >> c = 1\n => 1")
         
     | 
| 
      
 166 
     | 
    
         
            +
                # >> groups = r.read_groups
         
     | 
| 
      
 167 
     | 
    
         
            +
                # >> groups.size
         
     | 
| 
      
 168 
     | 
    
         
            +
                # => 2
         
     | 
| 
      
 169 
     | 
    
         
            +
                #
         
     | 
| 
      
 170 
     | 
    
         
            +
                # doctest: Group types are correctly created
         
     | 
| 
      
 171 
     | 
    
         
            +
                # >> groups.map{ |g| g.class }
         
     | 
| 
      
 172 
     | 
    
         
            +
                # => [RubyDocTest::Statement, RubyDocTest::Result]
         
     | 
| 
      
 173 
     | 
    
         
            +
                #
         
     | 
| 
      
 174 
     | 
    
         
            +
                # doctest: A ruby document can have =begin and =end blocks in it
         
     | 
| 
      
 175 
     | 
    
         
            +
                # >> r = RubyDocTest::Runner.new(<<-RUBY, "test.rb")
         
     | 
| 
      
 176 
     | 
    
         
            +
                #    some_ruby_code = 1
         
     | 
| 
      
 177 
     | 
    
         
            +
                #    =begin
         
     | 
| 
      
 178 
     | 
    
         
            +
                #     this is a normal ruby comment
         
     | 
| 
      
 179 
     | 
    
         
            +
                #     >> z = 10
         
     | 
| 
      
 180 
     | 
    
         
            +
                #     => 10
         
     | 
| 
      
 181 
     | 
    
         
            +
                #    =end
         
     | 
| 
      
 182 
     | 
    
         
            +
                #    more_ruby_code = 2
         
     | 
| 
      
 183 
     | 
    
         
            +
                #    RUBY
         
     | 
| 
      
 184 
     | 
    
         
            +
                # >> groups = r.read_groups
         
     | 
| 
      
 185 
     | 
    
         
            +
                # >> groups.size
         
     | 
| 
      
 186 
     | 
    
         
            +
                # => 2
         
     | 
| 
      
 187 
     | 
    
         
            +
                # >> groups.map{ |g| g.lines.first }
         
     | 
| 
      
 188 
     | 
    
         
            +
                # => [" >> z = 10", " => 10"]
         
     | 
| 
      
 189 
     | 
    
         
            +
                def read_groups(src_lines = @src_lines, mode = @mode, start_index = 0)
         
     | 
| 
      
 190 
     | 
    
         
            +
                  groups = []
         
     | 
| 
      
 191 
     | 
    
         
            +
                  (start_index).upto(src_lines.size) do |index|
         
     | 
| 
      
 192 
     | 
    
         
            +
                    line = src_lines[index]
         
     | 
| 
      
 193 
     | 
    
         
            +
                    case mode
         
     | 
| 
      
 194 
     | 
    
         
            +
                    when :ruby
         
     | 
| 
      
 195 
     | 
    
         
            +
                      case line
         
     | 
| 
      
 196 
     | 
    
         
            +
                      
         
     | 
| 
      
 197 
     | 
    
         
            +
                      # Beginning of a multi-line comment section
         
     | 
| 
      
 198 
     | 
    
         
            +
                      when /^=begin/
         
     | 
| 
      
 199 
     | 
    
         
            +
                        groups +=
         
     | 
| 
      
 200 
     | 
    
         
            +
                          # Get statements, results, and directives as if inside a doctest
         
     | 
| 
      
 201 
     | 
    
         
            +
                          read_groups(src_lines, :doctest_with_end, index)
         
     | 
| 
      
 202 
     | 
    
         
            +
                      
         
     | 
| 
      
 203 
     | 
    
         
            +
                      else
         
     | 
| 
      
 204 
     | 
    
         
            +
                        if g = match_group("\\s*#\\s*", src_lines, index)
         
     | 
| 
      
 205 
     | 
    
         
            +
                          groups << g
         
     | 
| 
      
 206 
     | 
    
         
            +
                        end
         
     | 
| 
      
 207 
     | 
    
         
            +
                      
         
     | 
| 
      
 208 
     | 
    
         
            +
                      end
         
     | 
| 
      
 209 
     | 
    
         
            +
                    when :doctest
         
     | 
| 
      
 210 
     | 
    
         
            +
                      if g = match_group("\\s*", src_lines, index)
         
     | 
| 
      
 211 
     | 
    
         
            +
                        groups << g
         
     | 
| 
      
 212 
     | 
    
         
            +
                      end
         
     | 
| 
      
 213 
     | 
    
         
            +
                      
         
     | 
| 
      
 214 
     | 
    
         
            +
                    when :doctest_with_end
         
     | 
| 
      
 215 
     | 
    
         
            +
                      break if line =~ /^=end/
         
     | 
| 
      
 216 
     | 
    
         
            +
                      if g = match_group("\\s*", src_lines, index)
         
     | 
| 
      
 217 
     | 
    
         
            +
                        groups << g
         
     | 
| 
      
 218 
     | 
    
         
            +
                      end
         
     | 
| 
      
 219 
     | 
    
         
            +
                      
         
     | 
| 
      
 220 
     | 
    
         
            +
                    end
         
     | 
| 
      
 221 
     | 
    
         
            +
                  end
         
     | 
| 
      
 222 
     | 
    
         
            +
                  groups
         
     | 
| 
      
 223 
     | 
    
         
            +
                end
         
     | 
| 
      
 224 
     | 
    
         
            +
                
         
     | 
| 
      
 225 
     | 
    
         
            +
                def match_group(prefix, src_lines, index)
         
     | 
| 
      
 226 
     | 
    
         
            +
                  case src_lines[index]
         
     | 
| 
      
 227 
     | 
    
         
            +
                  
         
     | 
| 
      
 228 
     | 
    
         
            +
                  # An irb '>>' marker after a '#' indicates an embedded doctest
         
     | 
| 
      
 229 
     | 
    
         
            +
                  when /^(#{prefix})>>(\s|\s*$)/
         
     | 
| 
      
 230 
     | 
    
         
            +
                    Statement.new(src_lines, index, @file_name)
         
     | 
| 
      
 231 
     | 
    
         
            +
                  
         
     | 
| 
      
 232 
     | 
    
         
            +
                  # An irb '=>' marker after a '#' indicates an embedded result
         
     | 
| 
      
 233 
     | 
    
         
            +
                  when /^(#{prefix})=>\s/
         
     | 
| 
      
 234 
     | 
    
         
            +
                    Result.new(src_lines, index)
         
     | 
| 
      
 235 
     | 
    
         
            +
                  
         
     | 
| 
      
 236 
     | 
    
         
            +
                  # Whenever we match a directive (e.g. 'doctest'), add that in as well
         
     | 
| 
      
 237 
     | 
    
         
            +
                  when /^(#{prefix})(#{SpecialDirective::NAMES_FOR_RX})(.*)$/
         
     | 
| 
      
 238 
     | 
    
         
            +
                    SpecialDirective.new(src_lines, index)
         
     | 
| 
      
 239 
     | 
    
         
            +
                  
         
     | 
| 
      
 240 
     | 
    
         
            +
                  else
         
     | 
| 
      
 241 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 242 
     | 
    
         
            +
                  end
         
     | 
| 
      
 243 
     | 
    
         
            +
                end
         
     | 
| 
      
 244 
     | 
    
         
            +
                
         
     | 
| 
      
 245 
     | 
    
         
            +
                # === Tests
         
     | 
| 
      
 246 
     | 
    
         
            +
                # 
         
     | 
| 
      
 247 
     | 
    
         
            +
                # doctest: The organize_blocks method should separate Statement, Result and SpecialDirective
         
     | 
| 
      
 248 
     | 
    
         
            +
                #          objects into CodeBlocks.
         
     | 
| 
      
 249 
     | 
    
         
            +
                # >> r = RubyDocTest::Runner.new(">> t = 1\n>> t + 2\n=> 3\n>> u = 1", "test.doctest")
         
     | 
| 
      
 250 
     | 
    
         
            +
                # >> r.prepare_tests
         
     | 
| 
      
 251 
     | 
    
         
            +
                # 
         
     | 
| 
      
 252 
     | 
    
         
            +
                # >> r.blocks.first.statements.map{|s| s.lines}
         
     | 
| 
      
 253 
     | 
    
         
            +
                # => [[">> t = 1"], [">> t + 2"]]
         
     | 
| 
      
 254 
     | 
    
         
            +
                # 
         
     | 
| 
      
 255 
     | 
    
         
            +
                # >> r.blocks.first.result.lines
         
     | 
| 
      
 256 
     | 
    
         
            +
                # => ["=> 3"]
         
     | 
| 
      
 257 
     | 
    
         
            +
                # 
         
     | 
| 
      
 258 
     | 
    
         
            +
                # >> r.blocks.last.statements.map{|s| s.lines}
         
     | 
| 
      
 259 
     | 
    
         
            +
                # => [[">> u = 1"]]
         
     | 
| 
      
 260 
     | 
    
         
            +
                # 
         
     | 
| 
      
 261 
     | 
    
         
            +
                # >> r.blocks.last.result
         
     | 
| 
      
 262 
     | 
    
         
            +
                # => nil
         
     | 
| 
      
 263 
     | 
    
         
            +
                #
         
     | 
| 
      
 264 
     | 
    
         
            +
                # doctest: Two doctest directives--each having its own statement--should be separated properly
         
     | 
| 
      
 265 
     | 
    
         
            +
                #          by organize_blocks.
         
     | 
| 
      
 266 
     | 
    
         
            +
                # >> r = RubyDocTest::Runner.new("doctest: one\n>> t = 1\ndoctest: two\n>> t + 2", "test.doctest")
         
     | 
| 
      
 267 
     | 
    
         
            +
                # >> r.prepare_tests
         
     | 
| 
      
 268 
     | 
    
         
            +
                # >> r.blocks.map{|b| b.class}
         
     | 
| 
      
 269 
     | 
    
         
            +
                # => [RubyDocTest::SpecialDirective, RubyDocTest::CodeBlock,
         
     | 
| 
      
 270 
     | 
    
         
            +
                #     RubyDocTest::SpecialDirective, RubyDocTest::CodeBlock]
         
     | 
| 
      
 271 
     | 
    
         
            +
                #
         
     | 
| 
      
 272 
     | 
    
         
            +
                # >> r.blocks[0].value
         
     | 
| 
      
 273 
     | 
    
         
            +
                # => "one"
         
     | 
| 
      
 274 
     | 
    
         
            +
                #
         
     | 
| 
      
 275 
     | 
    
         
            +
                # >> r.blocks[1].statements.map{|s| s.lines}
         
     | 
| 
      
 276 
     | 
    
         
            +
                # => [[">> t = 1"]]
         
     | 
| 
      
 277 
     | 
    
         
            +
                #
         
     | 
| 
      
 278 
     | 
    
         
            +
                # >> r.blocks[2].value
         
     | 
| 
      
 279 
     | 
    
         
            +
                # => "two"
         
     | 
| 
      
 280 
     | 
    
         
            +
                #
         
     | 
| 
      
 281 
     | 
    
         
            +
                # >> r.blocks[3].statements.map{|s| s.lines}
         
     | 
| 
      
 282 
     | 
    
         
            +
                # => [[">> t + 2"]]
         
     | 
| 
      
 283 
     | 
    
         
            +
                def organize_blocks(groups = @groups)
         
     | 
| 
      
 284 
     | 
    
         
            +
                  blocks = []
         
     | 
| 
      
 285 
     | 
    
         
            +
                  current_statements = []
         
     | 
| 
      
 286 
     | 
    
         
            +
                  groups.each do |g|
         
     | 
| 
      
 287 
     | 
    
         
            +
                    case g
         
     | 
| 
      
 288 
     | 
    
         
            +
                    when Statement
         
     | 
| 
      
 289 
     | 
    
         
            +
                      current_statements << g
         
     | 
| 
      
 290 
     | 
    
         
            +
                    when Result
         
     | 
| 
      
 291 
     | 
    
         
            +
                      blocks << CodeBlock.new(current_statements, g)
         
     | 
| 
      
 292 
     | 
    
         
            +
                      current_statements = []
         
     | 
| 
      
 293 
     | 
    
         
            +
                    when SpecialDirective
         
     | 
| 
      
 294 
     | 
    
         
            +
                      case g.name
         
     | 
| 
      
 295 
     | 
    
         
            +
                      when "doctest:"
         
     | 
| 
      
 296 
     | 
    
         
            +
                        blocks << CodeBlock.new(current_statements) unless current_statements.empty?
         
     | 
| 
      
 297 
     | 
    
         
            +
                        current_statements = []
         
     | 
| 
      
 298 
     | 
    
         
            +
                      when "doctest_require:"
         
     | 
| 
      
 299 
     | 
    
         
            +
                        doctest_require = eval(g.value, TOPLEVEL_BINDING, @file_name, g.line_number)
         
     | 
| 
      
 300 
     | 
    
         
            +
                        if doctest_require.is_a? String
         
     | 
| 
      
 301 
     | 
    
         
            +
                          require_relative_to_file_name(doctest_require, @file_name)
         
     | 
| 
      
 302 
     | 
    
         
            +
                        end
         
     | 
| 
      
 303 
     | 
    
         
            +
                      when "!!!"
         
     | 
| 
      
 304 
     | 
    
         
            +
                        # ignore
         
     | 
| 
      
 305 
     | 
    
         
            +
                      end
         
     | 
| 
      
 306 
     | 
    
         
            +
                      blocks << g
         
     | 
| 
      
 307 
     | 
    
         
            +
                    end
         
     | 
| 
      
 308 
     | 
    
         
            +
                  end
         
     | 
| 
      
 309 
     | 
    
         
            +
                  blocks << CodeBlock.new(current_statements) unless current_statements.empty?
         
     | 
| 
      
 310 
     | 
    
         
            +
                  blocks
         
     | 
| 
      
 311 
     | 
    
         
            +
                end
         
     | 
| 
      
 312 
     | 
    
         
            +
                
         
     | 
| 
      
 313 
     | 
    
         
            +
                def require_relative_to_file_name(file_name, relative_to)
         
     | 
| 
      
 314 
     | 
    
         
            +
                  load_path = $:.dup
         
     | 
| 
      
 315 
     | 
    
         
            +
                  $:.unshift File.expand_path(File.join(File.dirname(relative_to), File.dirname(file_name)))
         
     | 
| 
      
 316 
     | 
    
         
            +
                  require File.basename(file_name)
         
     | 
| 
      
 317 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 318 
     | 
    
         
            +
                  $:.shift
         
     | 
| 
      
 319 
     | 
    
         
            +
                end
         
     | 
| 
      
 320 
     | 
    
         
            +
                
         
     | 
| 
      
 321 
     | 
    
         
            +
                # === Tests
         
     | 
| 
      
 322 
     | 
    
         
            +
                # 
         
     | 
| 
      
 323 
     | 
    
         
            +
                # doctest: Tests should be organized into groups based on the 'doctest' SpecialDirective
         
     | 
| 
      
 324 
     | 
    
         
            +
                # >> r = RubyDocTest::Runner.new("doctest: one\n>> t = 1\ndoctest: two\n>> t + 2", "test.doctest")
         
     | 
| 
      
 325 
     | 
    
         
            +
                # >> r.prepare_tests
         
     | 
| 
      
 326 
     | 
    
         
            +
                # >> r.tests.size
         
     | 
| 
      
 327 
     | 
    
         
            +
                # => 2
         
     | 
| 
      
 328 
     | 
    
         
            +
                # >> r.tests[0].code_blocks.map{|c| c.statements}.flatten.map{|s| s.lines}
         
     | 
| 
      
 329 
     | 
    
         
            +
                # => [[">> t = 1"]]
         
     | 
| 
      
 330 
     | 
    
         
            +
                # >> r.tests[1].code_blocks.map{|c| c.statements}.flatten.map{|s| s.lines}
         
     | 
| 
      
 331 
     | 
    
         
            +
                # => [[">> t + 2"]]
         
     | 
| 
      
 332 
     | 
    
         
            +
                # >> r.tests[0].description
         
     | 
| 
      
 333 
     | 
    
         
            +
                # => "one"
         
     | 
| 
      
 334 
     | 
    
         
            +
                # >> r.tests[1].description
         
     | 
| 
      
 335 
     | 
    
         
            +
                # => "two"
         
     | 
| 
      
 336 
     | 
    
         
            +
                #
         
     | 
| 
      
 337 
     | 
    
         
            +
                # doctest: Without a 'doctest' SpecialDirective, there is one Test called "Default Test".
         
     | 
| 
      
 338 
     | 
    
         
            +
                # >> r = RubyDocTest::Runner.new(">> t = 1\n>> t + 2\n=> 3\n>> u = 1", "test.doctest")
         
     | 
| 
      
 339 
     | 
    
         
            +
                # >> r.prepare_tests
         
     | 
| 
      
 340 
     | 
    
         
            +
                # >> r.tests.size
         
     | 
| 
      
 341 
     | 
    
         
            +
                # => 1
         
     | 
| 
      
 342 
     | 
    
         
            +
                # 
         
     | 
| 
      
 343 
     | 
    
         
            +
                # >> r.tests.first.description
         
     | 
| 
      
 344 
     | 
    
         
            +
                # => "Default Test"
         
     | 
| 
      
 345 
     | 
    
         
            +
                # 
         
     | 
| 
      
 346 
     | 
    
         
            +
                # >> r.tests.first.code_blocks.size
         
     | 
| 
      
 347 
     | 
    
         
            +
                # => 2
         
     | 
| 
      
 348 
     | 
    
         
            +
                def organize_tests(blocks = @blocks)
         
     | 
| 
      
 349 
     | 
    
         
            +
                  tests = []
         
     | 
| 
      
 350 
     | 
    
         
            +
                  assigned_blocks = nil
         
     | 
| 
      
 351 
     | 
    
         
            +
                  unassigned_blocks = []
         
     | 
| 
      
 352 
     | 
    
         
            +
                  blocks.each do |g|
         
     | 
| 
      
 353 
     | 
    
         
            +
                    case g
         
     | 
| 
      
 354 
     | 
    
         
            +
                    when CodeBlock
         
     | 
| 
      
 355 
     | 
    
         
            +
                      (assigned_blocks || unassigned_blocks) << g
         
     | 
| 
      
 356 
     | 
    
         
            +
                    when SpecialDirective
         
     | 
| 
      
 357 
     | 
    
         
            +
                      case g.name
         
     | 
| 
      
 358 
     | 
    
         
            +
                      when "doctest:"
         
     | 
| 
      
 359 
     | 
    
         
            +
                        assigned_blocks = []
         
     | 
| 
      
 360 
     | 
    
         
            +
                        tests << Test.new(g.value, assigned_blocks)
         
     | 
| 
      
 361 
     | 
    
         
            +
                      when "!!!"
         
     | 
| 
      
 362 
     | 
    
         
            +
                        tests << g
         
     | 
| 
      
 363 
     | 
    
         
            +
                      end
         
     | 
| 
      
 364 
     | 
    
         
            +
                    end
         
     | 
| 
      
 365 
     | 
    
         
            +
                  end
         
     | 
| 
      
 366 
     | 
    
         
            +
                  tests << Test.new("Default Test", unassigned_blocks) unless unassigned_blocks.empty?
         
     | 
| 
      
 367 
     | 
    
         
            +
                  tests
         
     | 
| 
      
 368 
     | 
    
         
            +
                end
         
     | 
| 
      
 369 
     | 
    
         
            +
              end
         
     | 
| 
      
 370 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,44 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            $:.unshift(File.dirname(__FILE__)) unless
         
     | 
| 
      
 2 
     | 
    
         
            +
              $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require 'lines'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module RubyDocTest
         
     | 
| 
      
 7 
     | 
    
         
            +
              class SpecialDirective < Lines
         
     | 
| 
      
 8 
     | 
    
         
            +
                NAMES = ["doctest:", "!!!", "doctest_require:"]
         
     | 
| 
      
 9 
     | 
    
         
            +
                NAMES_FOR_RX = NAMES.map{ |n| Regexp.escape(n) }.join("|")
         
     | 
| 
      
 10 
     | 
    
         
            +
                
         
     | 
| 
      
 11 
     | 
    
         
            +
                # === Test
         
     | 
| 
      
 12 
     | 
    
         
            +
                #
         
     | 
| 
      
 13 
     | 
    
         
            +
                # doctest: The name of the directive should be detected in the first line
         
     | 
| 
      
 14 
     | 
    
         
            +
                # >> s = RubyDocTest::SpecialDirective.new(["doctest: Testing Stuff", "Other Stuff"])
         
     | 
| 
      
 15 
     | 
    
         
            +
                # >> s.name
         
     | 
| 
      
 16 
     | 
    
         
            +
                # => "doctest:"
         
     | 
| 
      
 17 
     | 
    
         
            +
                def name
         
     | 
| 
      
 18 
     | 
    
         
            +
                  if m = lines.first.match(/^#{Regexp.escape(indentation)}(#{NAMES_FOR_RX})/)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    m[1]
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
                
         
     | 
| 
      
 23 
     | 
    
         
            +
                # === Test
         
     | 
| 
      
 24 
     | 
    
         
            +
                #
         
     | 
| 
      
 25 
     | 
    
         
            +
                # doctest: The value of the directive should be detected in the first line
         
     | 
| 
      
 26 
     | 
    
         
            +
                # >> s = RubyDocTest::SpecialDirective.new(["doctest: Testing Stuff", "Other Stuff"])
         
     | 
| 
      
 27 
     | 
    
         
            +
                # >> s.value
         
     | 
| 
      
 28 
     | 
    
         
            +
                # => "Testing Stuff"
         
     | 
| 
      
 29 
     | 
    
         
            +
                #
         
     | 
| 
      
 30 
     | 
    
         
            +
                # >> s = RubyDocTest::SpecialDirective.new(["  # doctest: Testing Stuff", "  # Other Stuff"])
         
     | 
| 
      
 31 
     | 
    
         
            +
                # >> s.value
         
     | 
| 
      
 32 
     | 
    
         
            +
                # => "Testing Stuff"
         
     | 
| 
      
 33 
     | 
    
         
            +
                #
         
     | 
| 
      
 34 
     | 
    
         
            +
                # doctest: Multiple lines for the directive value should work as well
         
     | 
| 
      
 35 
     | 
    
         
            +
                # >> s = RubyDocTest::SpecialDirective.new(["doctest: Testing Stuff", "  On Two Lines"])
         
     | 
| 
      
 36 
     | 
    
         
            +
                # >> s.value
         
     | 
| 
      
 37 
     | 
    
         
            +
                # => "Testing Stuff\nOn Two Lines"
         
     | 
| 
      
 38 
     | 
    
         
            +
                def value
         
     | 
| 
      
 39 
     | 
    
         
            +
                  if m = lines.join("\n").match(/^#{Regexp.escape(indentation)}(#{NAMES_FOR_RX})(.*)/m)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    m[2].strip
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
      
 44 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/statement.rb
    ADDED
    
    | 
         @@ -0,0 +1,75 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            $:.unshift(File.dirname(__FILE__)) unless
         
     | 
| 
      
 2 
     | 
    
         
            +
              $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require 'rubydoctest'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'lines'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module RubyDocTest
         
     | 
| 
      
 8 
     | 
    
         
            +
              class EvaluationError < Exception
         
     | 
| 
      
 9 
     | 
    
         
            +
                attr_reader :statement, :original_exception
         
     | 
| 
      
 10 
     | 
    
         
            +
                def initialize(statement, original_exception)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @statement, @original_exception = statement, original_exception
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
              
         
     | 
| 
      
 15 
     | 
    
         
            +
              class Statement < Lines
         
     | 
| 
      
 16 
     | 
    
         
            +
                
         
     | 
| 
      
 17 
     | 
    
         
            +
                attr_reader :actual_result
         
     | 
| 
      
 18 
     | 
    
         
            +
                
         
     | 
| 
      
 19 
     | 
    
         
            +
                # === Tests
         
     | 
| 
      
 20 
     | 
    
         
            +
                # 
         
     | 
| 
      
 21 
     | 
    
         
            +
                # doctest: The FILENAME ruby constant should be replaced by the name of the file
         
     | 
| 
      
 22 
     | 
    
         
            +
                # >> __FILE__[/statement\.rb$/]
         
     | 
| 
      
 23 
     | 
    
         
            +
                # => "statement.rb"
         
     | 
| 
      
 24 
     | 
    
         
            +
                def initialize(doc_lines, line_index = 0, file_name = nil)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @file_name = file_name
         
     | 
| 
      
 26 
     | 
    
         
            +
                  super(doc_lines, line_index)
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
                
         
     | 
| 
      
 29 
     | 
    
         
            +
                # === Tests
         
     | 
| 
      
 30 
     | 
    
         
            +
                # 
         
     | 
| 
      
 31 
     | 
    
         
            +
                # doctest: A statement should parse out a '>>' irb prompt
         
     | 
| 
      
 32 
     | 
    
         
            +
                # >> s = RubyDocTest::Statement.new([">> a = 1"])
         
     | 
| 
      
 33 
     | 
    
         
            +
                # >> s.source_code
         
     | 
| 
      
 34 
     | 
    
         
            +
                # => "a = 1"
         
     | 
| 
      
 35 
     | 
    
         
            +
                #
         
     | 
| 
      
 36 
     | 
    
         
            +
                # doctest: More than one line should get included, if indentation so indicates
         
     | 
| 
      
 37 
     | 
    
         
            +
                # >> s = RubyDocTest::Statement.new([">> b = 1 +", " 1", "not part of the statement"])
         
     | 
| 
      
 38 
     | 
    
         
            +
                # >> s.source_code
         
     | 
| 
      
 39 
     | 
    
         
            +
                # => "b = 1 +\n1"
         
     | 
| 
      
 40 
     | 
    
         
            +
                #
         
     | 
| 
      
 41 
     | 
    
         
            +
                # doctest: Lines indented by ?> should have the ?> removed.
         
     | 
| 
      
 42 
     | 
    
         
            +
                # >> s = RubyDocTest::Statement.new([">> b = 1 +", "?> 1"])
         
     | 
| 
      
 43 
     | 
    
         
            +
                # >> s.source_code
         
     | 
| 
      
 44 
     | 
    
         
            +
                # => "b = 1 +\n1"
         
     | 
| 
      
 45 
     | 
    
         
            +
                def source_code
         
     | 
| 
      
 46 
     | 
    
         
            +
                  lines.first =~ /^#{Regexp.escape(indentation)}>>\s(.*)$/
         
     | 
| 
      
 47 
     | 
    
         
            +
                  first = [$1]
         
     | 
| 
      
 48 
     | 
    
         
            +
                  remaining = (lines[1..-1] || [])
         
     | 
| 
      
 49 
     | 
    
         
            +
                  (first + remaining).join("\n")
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
                
         
     | 
| 
      
 52 
     | 
    
         
            +
                # === Test
         
     | 
| 
      
 53 
     | 
    
         
            +
                #
         
     | 
| 
      
 54 
     | 
    
         
            +
                # doctest: Evaluating a multi-line statement should be ok
         
     | 
| 
      
 55 
     | 
    
         
            +
                # >> s = RubyDocTest::Statement.new([">> b = 1 +", " 1", "not part of the statement"])
         
     | 
| 
      
 56 
     | 
    
         
            +
                # >> s.evaluate
         
     | 
| 
      
 57 
     | 
    
         
            +
                # => 2
         
     | 
| 
      
 58 
     | 
    
         
            +
                #
         
     | 
| 
      
 59 
     | 
    
         
            +
                # doctest: Evaluating a syntax error should raise an EvaluationError
         
     | 
| 
      
 60 
     | 
    
         
            +
                # >> s = RubyDocTest::Statement.new([">> b = 1 +"])
         
     | 
| 
      
 61 
     | 
    
         
            +
                # >> begin s.evaluate; :fail; rescue RubyDocTest::EvaluationError; :ok end
         
     | 
| 
      
 62 
     | 
    
         
            +
                # => :ok
         
     | 
| 
      
 63 
     | 
    
         
            +
                def evaluate
         
     | 
| 
      
 64 
     | 
    
         
            +
                  sc = source_code.gsub("__FILE__", @file_name.inspect)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  # puts "EVAL: #{sc}"
         
     | 
| 
      
 66 
     | 
    
         
            +
                  @actual_result = eval(sc, TOPLEVEL_BINDING, __FILE__, __LINE__)
         
     | 
| 
      
 67 
     | 
    
         
            +
                rescue Exception => e
         
     | 
| 
      
 68 
     | 
    
         
            +
                  if RubyDocTest.trace
         
     | 
| 
      
 69 
     | 
    
         
            +
                    raise e.class, e.to_s + "\n" + e.backtrace.first
         
     | 
| 
      
 70 
     | 
    
         
            +
                  else
         
     | 
| 
      
 71 
     | 
    
         
            +
                    raise EvaluationError.new(self, e)
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
      
 74 
     | 
    
         
            +
              end
         
     | 
| 
      
 75 
     | 
    
         
            +
            end
         
     |