[Python-checkins] python/dist/src/Lib doctest.py,1.55,1.56

edloper at users.sourceforge.net edloper at users.sourceforge.net
Mon Aug 9 18:14:43 CEST 2004


Update of /cvsroot/python/python/dist/src/Lib
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30812

Modified Files:
	doctest.py 
Log Message:
- DocTest is now a simple container class; its constructor is no longer
  responsible for parsing the string.
- Renamed Parser to DocTestParser
- DocTestParser.get_*() now accept the string & name as command-line
  arguments; the parser's constructor is now empty.
- Added DocTestParser.get_doctest() method
- Replaced "doctest_factory" argument to DocTestFinder with a "parser"
  argument (takes a DocTestParser).
- Changed _tag_msg to take an indentation string argument.


Index: doctest.py
===================================================================
RCS file: /cvsroot/python/python/dist/src/Lib/doctest.py,v
retrieving revision 1.55
retrieving revision 1.56
diff -C2 -d -r1.55 -r1.56
*** doctest.py	9 Aug 2004 15:43:46 -0000	1.55
--- doctest.py	9 Aug 2004 16:14:41 -0000	1.56
***************
*** 315,328 ****
          raise TypeError("Expected a module, string, or None")
  
! def _tag_msg(tag, msg, indent_msg=True):
      """
      Return a string that displays a tag-and-message pair nicely,
      keeping the tag and its message on the same line when that
!     makes sense.  If `indent_msg` is true, then messages that are
!     put on separate lines will be indented.
      """
-     # What string should we use to indent contents?
-     INDENT = '    '
- 
      # If the message doesn't end in a newline, then add one.
      if msg[-1:] != '\n':
--- 315,325 ----
          raise TypeError("Expected a module, string, or None")
  
! def _tag_msg(tag, msg, indent='    '):
      """
      Return a string that displays a tag-and-message pair nicely,
      keeping the tag and its message on the same line when that
!     makes sense.  If the message is displayed on separate lines,
!     then `indent` is added to the beginning of each line.
      """
      # If the message doesn't end in a newline, then add one.
      if msg[-1:] != '\n':
***************
*** 335,342 ****
          return '%s: %s' % (tag, msg)
      else:
!         if indent_msg:
!             msg = '\n'.join([INDENT+l for l in msg.split('\n')])
!             msg = msg[:-len(INDENT)]
!         return '%s:\n%s' % (tag, msg)
  
  # Override some StringIO methods.
--- 332,337 ----
          return '%s: %s' % (tag, msg)
      else:
!         msg = '\n'.join([indent+l for l in msg[:-1].split('\n')])
!         return '%s:\n%s\n' % (tag, msg)
  
  # Override some StringIO methods.
***************
*** 368,379 ****
  ##   where the example was extracted from.
  ##
! ## - A "doctest" is a collection of examples extracted from a string
! ##   (such as an object's docstring).  The DocTest class also includes
! ##   information about where the string was extracted from.
  
  class Example:
      """
      A single doctest example, consisting of source code and expected
!     output.  Example defines the following attributes:
  
        - source:  A single Python statement, always ending with a newline.
--- 363,374 ----
  ##   where the example was extracted from.
  ##
! ## - A "doctest" is a collection of examples, typically extracted from
! ##   a string (such as an object's docstring).  The DocTest class also
! ##   includes information about where the string was extracted from.
  
  class Example:
      """
      A single doctest example, consisting of source code and expected
!     output.  `Example` defines the following attributes:
  
        - source:  A single Python statement, always ending with a newline.
***************
*** 403,407 ****
      """
      A collection of doctest examples that should be run in a single
!     namespace.  Each DocTest defines the following attributes:
  
        - examples: the list of examples.
--- 398,402 ----
      """
      A collection of doctest examples that should be run in a single
!     namespace.  Each `DocTest` defines the following attributes:
  
        - examples: the list of examples.
***************
*** 413,439 ****
          the object whose docstring this DocTest was extracted from).
  
-       - docstring: The docstring being tested
- 
        - filename: The name of the file that this DocTest was extracted
!         from.
  
        - lineno: The line number within filename where this DocTest
!         begins.  This line number is zero-based, with respect to the
!         beginning of the file.
      """
!     def __init__(self, docstring, globs, name, filename, lineno):
          """
!         Create a new DocTest, by extracting examples from `docstring`.
!         The DocTest's globals are initialized with a copy of `globs`.
          """
!         # Store a copy of the globals
          self.globs = globs.copy()
-         # Store identifying information
          self.name = name
          self.filename = filename
          self.lineno = lineno
-         # Parse the docstring.
-         self.docstring = docstring
-         self.examples = Parser(name, docstring).get_examples()
  
      def __repr__(self):
--- 408,435 ----
          the object whose docstring this DocTest was extracted from).
  
        - filename: The name of the file that this DocTest was extracted
!         from, or `None` if the filename is unknown.
  
        - lineno: The line number within filename where this DocTest
!         begins, or `None` if the line number is unavailable.  This
!         line number is zero-based, with respect to the beginning of
!         the file.
! 
!       - docstring: The string that the examples were extracted from,
!         or `None` if the string is unavailable.
      """
!     def __init__(self, examples, globs, name, filename, lineno, docstring):
          """
!         Create a new DocTest containing the given examples.  The
!         DocTest's globals are initialized with a copy of `globs`.
          """
!         assert not isinstance(examples, basestring), \
!                "DocTest no longer accepts str; use DocTestParser instead"
!         self.examples = examples
!         self.docstring = docstring
          self.globs = globs.copy()
          self.name = name
          self.filename = filename
          self.lineno = lineno
  
      def __repr__(self):
***************
*** 456,476 ****
  
  ######################################################################
! ## 2. Example Parser
  ######################################################################
  
! class Parser:
      """
!     Extract doctests from a string.
      """
-     def __init__(self, name, string):
-         """
-         Prepare to extract doctests from string `string`.
- 
-         `name` is an arbitrary (string) name associated with the string,
-         and is used only in error messages.
-         """
-         self.name = name
-         self.string = string.expandtabs()
- 
      _EXAMPLE_RE = re.compile(r'''
          # Source consists of a PS1 line followed by zero or more PS2 lines.
--- 452,462 ----
  
  ######################################################################
! ## 2. DocTestParser
  ######################################################################
  
! class DocTestParser:
      """
!     A class used to parse strings containing doctest examples.
      """
      _EXAMPLE_RE = re.compile(r'''
          # Source consists of a PS1 line followed by zero or more PS2 lines.
***************
*** 487,497 ****
      _IS_BLANK_OR_COMMENT = re.compile('^[ ]*(#.*)?$').match
  
!     def get_examples(self):
          """
!         Extract all doctest examples, from the string, and return them
!         as a list of `Example` objects.  Line numbers are 0-based,
!         because it's most common in doctests that nothing interesting
!         appears on the same line as opening triple-quote, and so the
!         first interesting line is called \"line 1\" then.
  
          >>> text = '''
--- 473,498 ----
      _IS_BLANK_OR_COMMENT = re.compile('^[ ]*(#.*)?$').match
  
!     def get_doctest(self, string, globs, name, filename, lineno):
          """
!         Extract all doctest examples from the given string, and
!         collect them into a `DocTest` object.
! 
!         `globs`, `name`, `filename`, and `lineno` are attributes for
!         the new `DocTest` object.  See the documentation for `DocTest`
!         for more information.
!         """
!         return DocTest(self.get_examples(string, name), globs,
!                        name, filename, lineno, string)
! 
!     def get_examples(self, string, name='<string>'):
!         """
!         Extract all doctest examples from the given string, and return
!         them as a list of `Example` objects.  Line numbers are
!         0-based, because it's most common in doctests that nothing
!         interesting appears on the same line as opening triple-quote,
!         and so the first interesting line is called \"line 1\" then.
! 
!         The optional argument `name` is a name identifying this
!         string, and is only used for error messages.
  
          >>> text = '''
***************
*** 507,511 ****
          ...        5
          ...        '''
!         >>> for x in Parser('<string>', text).get_examples():
          ...     print (x.source, x.want, x.lineno)
          ('x, y = 2, 3  # no output expected\\n', '', 1)
--- 508,512 ----
          ...        5
          ...        '''
!         >>> for x in DocTestParser().get_examples(text):
          ...     print (x.source, x.want, x.lineno)
          ('x, y = 2, 3  # no output expected\\n', '', 1)
***************
*** 516,525 ****
          charno, lineno = 0, 0
          # Find all doctest examples in the string:
!         for m in self._EXAMPLE_RE.finditer(self.string):
              # Update lineno (lines before this example)
!             lineno += self.string.count('\n', charno, m.start())
  
              # Extract source/want from the regexp match.
!             (source, want) = self._parse_example(m, lineno)
              if self._IS_BLANK_OR_COMMENT(source):
                  continue
--- 517,526 ----
          charno, lineno = 0, 0
          # Find all doctest examples in the string:
!         for m in self._EXAMPLE_RE.finditer(string.expandtabs()):
              # Update lineno (lines before this example)
!             lineno += string.count('\n', charno, m.start())
  
              # Extract source/want from the regexp match.
!             (source, want) = self._parse_example(m, name, lineno)
              if self._IS_BLANK_OR_COMMENT(source):
                  continue
***************
*** 527,538 ****
  
              # Update lineno (lines inside this example)
!             lineno += self.string.count('\n', m.start(), m.end())
              # Update charno.
              charno = m.end()
          return examples
  
!     def get_program(self):
          """
!         Return an executable program from the string, as a string.
  
          The format of this isn't rigidly defined.  In general, doctest
--- 528,539 ----
  
              # Update lineno (lines inside this example)
!             lineno += string.count('\n', m.start(), m.end())
              # Update charno.
              charno = m.end()
          return examples
  
!     def get_program(self, string, name="<string>"):
          """
!         Return an executable program from the given string, as a string.
  
          The format of this isn't rigidly defined.  In general, doctest
***************
*** 542,545 ****
--- 543,549 ----
          a doctest test) is also placed in comments.
  
+         The optional argument `name` is a name identifying this
+         string, and is only used for error messages.
+ 
          >>> text = '''
          ...        >>> x, y = 2, 3  # no output expected
***************
*** 554,558 ****
          ...        5
          ...        '''
!         >>> print Parser('<string>', text).get_program()
          x, y = 2, 3  # no output expected
          if 1:
--- 558,562 ----
          ...        5
          ...        '''
!         >>> print DocTestParser().get_program(text)
          x, y = 2, 3  # no output expected
          if 1:
***************
*** 571,583 ****
          charnum, lineno = 0, 0
          # Find all doctest examples in the string:
!         for m in self._EXAMPLE_RE.finditer(self.string):
              # Add any text before this example, as a comment.
              if m.start() > charnum:
!                 lines = self.string[charnum:m.start()-1].split('\n')
                  output.extend([self._comment_line(l) for l in lines])
                  lineno += len(lines)
  
              # Extract source/want from the regexp match.
!             (source, want) = self._parse_example(m, lineno, False)
              # Display the source
              output.append(source)
--- 575,587 ----
          charnum, lineno = 0, 0
          # Find all doctest examples in the string:
!         for m in self._EXAMPLE_RE.finditer(string.expandtabs()):
              # Add any text before this example, as a comment.
              if m.start() > charnum:
!                 lines = string[charnum:m.start()-1].split('\n')
                  output.extend([self._comment_line(l) for l in lines])
                  lineno += len(lines)
  
              # Extract source/want from the regexp match.
!             (source, want) = self._parse_example(m, name, lineno, False)
              # Display the source
              output.append(source)
***************
*** 588,596 ****
  
              # Update the line number & char number.
!             lineno += self.string.count('\n', m.start(), m.end())
              charnum = m.end()
          # Add any remaining text, as comments.
          output.extend([self._comment_line(l)
!                        for l in self.string[charnum:].split('\n')])
          # Trim junk on both ends.
          while output and output[-1] == '#':
--- 592,600 ----
  
              # Update the line number & char number.
!             lineno += string.count('\n', m.start(), m.end())
              charnum = m.end()
          # Add any remaining text, as comments.
          output.extend([self._comment_line(l)
!                        for l in string[charnum:].split('\n')])
          # Trim junk on both ends.
          while output and output[-1] == '#':
***************
*** 601,605 ****
          return '\n'.join(output)
  
!     def _parse_example(self, m, lineno, add_newlines=True):
          # Get the example's indentation level.
          indent = len(m.group('indent'))
--- 605,609 ----
          return '\n'.join(output)
  
!     def _parse_example(self, m, name, lineno, add_newlines=True):
          # Get the example's indentation level.
          indent = len(m.group('indent'))
***************
*** 608,613 ****
          # indented; and then strip their indentation & prompts.
          source_lines = m.group('source').split('\n')
!         self._check_prompt_blank(source_lines, indent, lineno)
!         self._check_prefix(source_lines[1:], ' '*indent+'.', lineno)
          source = '\n'.join([sl[indent+4:] for sl in source_lines])
          if len(source_lines) > 1 and add_newlines:
--- 612,617 ----
          # indented; and then strip their indentation & prompts.
          source_lines = m.group('source').split('\n')
!         self._check_prompt_blank(source_lines, indent, name, lineno)
!         self._check_prefix(source_lines[1:], ' '*indent+'.', name, lineno)
          source = '\n'.join([sl[indent+4:] for sl in source_lines])
          if len(source_lines) > 1 and add_newlines:
***************
*** 617,621 ****
          # indented; and then strip the indentation.
          want_lines = m.group('want').rstrip().split('\n')
!         self._check_prefix(want_lines, ' '*indent,
                             lineno+len(source_lines))
          want = '\n'.join([wl[indent:] for wl in want_lines])
--- 621,625 ----
          # indented; and then strip the indentation.
          want_lines = m.group('want').rstrip().split('\n')
!         self._check_prefix(want_lines, ' '*indent, name,
                             lineno+len(source_lines))
          want = '\n'.join([wl[indent:] for wl in want_lines])
***************
*** 632,649 ****
              return '#'
  
!     def _check_prompt_blank(self, lines, indent, lineno):
          for i, line in enumerate(lines):
              if len(line) >= indent+4 and line[indent+3] != ' ':
                  raise ValueError('line %r of the docstring for %s '
                                   'lacks blank after %s: %r' %
!                                  (lineno+i+1, self.name,
                                    line[indent:indent+3], line))
  
!     def _check_prefix(self, lines, prefix, lineno):
          for i, line in enumerate(lines):
              if line and not line.startswith(prefix):
                  raise ValueError('line %r of the docstring for %s has '
                                   'inconsistent leading whitespace: %r' %
!                                  (lineno+i+1, self.name, line))
  
  
--- 636,653 ----
              return '#'
  
!     def _check_prompt_blank(self, lines, indent, name, lineno):
          for i, line in enumerate(lines):
              if len(line) >= indent+4 and line[indent+3] != ' ':
                  raise ValueError('line %r of the docstring for %s '
                                   'lacks blank after %s: %r' %
!                                  (lineno+i+1, name,
                                    line[indent:indent+3], line))
  
!     def _check_prefix(self, lines, prefix, name, lineno):
          for i, line in enumerate(lines):
              if line and not line.startswith(prefix):
                  raise ValueError('line %r of the docstring for %s has '
                                   'inconsistent leading whitespace: %r' %
!                                  (lineno+i+1, name, line))
  
  
***************
*** 661,670 ****
      """
  
!     def __init__(self, verbose=False, doctest_factory=DocTest,
                   recurse=True, _namefilter=None):
          """
          Create a new doctest finder.
  
!         The optional argument `doctest_factory` specifies a class or
          function that should be used to create new DocTest objects (or
          objects that implement the same interface as DocTest).  The
--- 665,674 ----
      """
  
!     def __init__(self, verbose=False, parser=DocTestParser(),
                   recurse=True, _namefilter=None):
          """
          Create a new doctest finder.
  
!         The optional argument `parser` specifies a class or
          function that should be used to create new DocTest objects (or
          objects that implement the same interface as DocTest).  The
***************
*** 675,679 ****
          only examine the given object, and not any contained objects.
          """
!         self._doctest_factory = doctest_factory
          self._verbose = verbose
          self._recurse = recurse
--- 679,683 ----
          only examine the given object, and not any contained objects.
          """
!         self._parser = parser
          self._verbose = verbose
          self._recurse = recurse
***************
*** 886,890 ****
          else:
              filename = getattr(module, '__file__', module.__name__)
!         return self._doctest_factory(docstring, globs, name, filename, lineno)
  
      def _find_lineno(self, obj, source_lines):
--- 890,895 ----
          else:
              filename = getattr(module, '__file__', module.__name__)
!         return self._parser.get_doctest(docstring, globs, name,
!                                         filename, lineno)
  
      def _find_lineno(self, obj, source_lines):
***************
*** 1255,1282 ****
              compileflags = _extract_future_flags(test.globs)
  
-         save_stdout = sys.stdout
          if out is None:
!             out = save_stdout.write
!         sys.stdout = self._fakeout
  
!         # Patch pdb.set_trace to restore sys.stdout, so that interactive
!         # debugging output is visible (not still redirected to self._fakeout).
!         # Note that we run "the real" pdb.set_trace (captured at doctest
!         # import time) in our replacement.  Because the current run() may
!         # run another doctest (and so on), the current pdb.set_trace may be
!         # our set_trace function, which changes sys.stdout.  If we called
!         # a chain of those, we wouldn't be left with the save_stdout
!         # *this* run() invocation wants.
          def set_trace():
!             sys.stdout = save_stdout
              real_pdb_set_trace()
  
-         save_set_trace = pdb.set_trace
-         pdb.set_trace = set_trace
          try:
              return self.__run(test, compileflags, out)
          finally:
!             sys.stdout = save_stdout
!             pdb.set_trace = save_set_trace
              if clear_globs:
                  test.globs.clear()
--- 1260,1284 ----
              compileflags = _extract_future_flags(test.globs)
  
          if out is None:
!             out = sys.stdout.write
!         saveout = sys.stdout
  
!         # Note that don't save away the previous pdb.set_trace. Rather,
!         # we safe pdb.set_trace on import (see import section above).
!         # We then call and restore that original cersion.  We do it this
!         # way to make this feature testable.  If we kept and called the
!         # previous version, we'd end up restoring the original stdout,
!         # which is not what we want.
          def set_trace():
!             sys.stdout = saveout
              real_pdb_set_trace()
  
          try:
+             sys.stdout = self._fakeout
+             pdb.set_trace = set_trace
              return self.__run(test, compileflags, out)
          finally:
!             sys.stdout = saveout
!             pdb.set_trace = real_pdb_set_trace
              if clear_globs:
                  test.globs.clear()
***************
*** 1493,1497 ****
  
           >>> runner = DebugRunner(verbose=False)
!          >>> test = DocTest('>>> raise KeyError\n42', {}, 'foo', 'foo.py', 0)
           >>> try:
           ...     runner.run(test)
--- 1495,1500 ----
  
           >>> runner = DebugRunner(verbose=False)
!          >>> test = DocTestParser().get_doctest('>>> raise KeyError\n42',
!          ...                                    {}, 'foo', 'foo.py', 0)
           >>> try:
           ...     runner.run(test)
***************
*** 1516,1520 ****
         If the output doesn't match, then a DocTestFailure is raised:
  
!          >>> test = DocTest('''
           ...      >>> x = 1
           ...      >>> x
--- 1519,1523 ----
         If the output doesn't match, then a DocTestFailure is raised:
  
!          >>> test = DocTestParser().get_doctest('''
           ...      >>> x = 1
           ...      >>> x
***************
*** 1548,1552 ****
           {'x': 1}
  
!          >>> test = DocTest('''
           ...      >>> x = 2
           ...      >>> raise KeyError
--- 1551,1555 ----
           {'x': 1}
  
!          >>> test = DocTestParser().get_doctest('''
           ...      >>> x = 2
           ...      >>> raise KeyError
***************
*** 1564,1568 ****
         But the globals are cleared if there is no error:
  
!          >>> test = DocTest('''
           ...      >>> x = 2
           ...      ''', {}, 'foo', 'foo.py', 0)
--- 1567,1571 ----
         But the globals are cleared if there is no error:
  
!          >>> test = DocTestParser().get_doctest('''
           ...      >>> x = 2
           ...      ''', {}, 'foo', 'foo.py', 0)
***************
*** 1780,1784 ****
  
      def runstring(self, s, name):
!         test = DocTest(s, self.globs, name, None, None)
          if self.verbose:
              print "Running string", name
--- 1783,1787 ----
  
      def runstring(self, s, name):
!         test = DocTestParser().get_doctest(s, self.globs, name, None, None)
          if self.verbose:
              print "Running string", name
***************
*** 1888,1892 ****
             exception:
  
!              >>> test = DocTest('>>> raise KeyError\n42',
               ...                {}, 'foo', 'foo.py', 0)
               >>> case = DocTestCase(test)
--- 1891,1895 ----
             exception:
  
!              >>> test = DocTestParser().get_doctest('>>> raise KeyError\n42',
               ...                {}, 'foo', 'foo.py', 0)
               >>> case = DocTestCase(test)
***************
*** 1913,1917 ****
             If the output doesn't match, then a DocTestFailure is raised:
  
!              >>> test = DocTest('''
               ...      >>> x = 1
               ...      >>> x
--- 1916,1920 ----
             If the output doesn't match, then a DocTestFailure is raised:
  
!              >>> test = DocTestParser().get_doctest('''
               ...      >>> x = 1
               ...      >>> x
***************
*** 2033,2037 ****
          globs = {}
  
!     test = DocTest(doc, globs, name, path, 0)
  
      return DocFileCase(test, optionflags, setUp, tearDown)
--- 2036,2040 ----
          globs = {}
  
!     test = DocTestParser().get_doctest(doc, globs, name, path, 0)
  
      return DocFileCase(test, optionflags, setUp, tearDown)
***************
*** 2139,2143 ****
         """
  
!     return Parser('<string>', s).get_program()
  
  def _want_comment(example):
--- 2142,2146 ----
         """
  
!     return DocTestParser().get_program(s)
  
  def _want_comment(example):



More information about the Python-checkins mailing list