[Checkins] SVN: manuel/trunk/src/ - add manuel.capture

Benji York benji at zope.com
Mon Jun 22 08:04:13 EDT 2009


Log message for revision 101217:
  - add manuel.capture
  - fix a bug in manuel.doctest
  - convert the footnotes docs to use manuel.capture
  - small cleanups
  

Changed:
  U   manuel/trunk/src/index.txt
  U   manuel/trunk/src/intro.txt
  U   manuel/trunk/src/manuel/README.txt
  U   manuel/trunk/src/manuel/__init__.py
  A   manuel/trunk/src/manuel/capture.py
  A   manuel/trunk/src/manuel/capture.txt
  U   manuel/trunk/src/manuel/codeblock.py
  U   manuel/trunk/src/manuel/doctest.py
  U   manuel/trunk/src/manuel/footnote.txt
  U   manuel/trunk/src/manuel/ignore.py
  U   manuel/trunk/src/manuel/testing.py
  U   manuel/trunk/src/manuel/tests.py

-=-
Modified: manuel/trunk/src/index.txt
===================================================================
--- manuel/trunk/src/index.txt	2009-06-22 11:36:12 UTC (rev 101216)
+++ manuel/trunk/src/index.txt	2009-06-22 12:04:13 UTC (rev 101217)
@@ -8,6 +8,7 @@
 
     intro.txt
     getting-started.txt
+    manuel/capture.txt
     manuel/codeblock.txt
     manuel/doctest.txt
     manuel/footnote.txt

Modified: manuel/trunk/src/intro.txt
===================================================================
--- manuel/trunk/src/intro.txt	2009-06-22 11:36:12 UTC (rev 101216)
+++ manuel/trunk/src/intro.txt	2009-06-22 12:04:13 UTC (rev 101217)
@@ -36,6 +36,9 @@
 
 Manuel includes quite a bit of functionality out of the box:
 
+:ref:`manuel.capture <capture>`
+    stores regions of a document in variables for later processing
+
 :ref:`manuel.codeblock <code-blocks>`
     executes code in ".. code-block:: python" blocks
 

Modified: manuel/trunk/src/manuel/README.txt
===================================================================
--- manuel/trunk/src/manuel/README.txt	2009-06-22 11:36:12 UTC (rev 101216)
+++ manuel/trunk/src/manuel/README.txt	2009-06-22 12:04:13 UTC (rev 101217)
@@ -221,7 +221,6 @@
     ...     print (region.lineno, region.parsed or region.source)
     (1, 'This is my\ndoctest.\n\n')
     (4, <zope.testing.doctest.Example instance at 0x...>)
-    (6, '\n')
 
 Now we can evaluate the examples.
 
@@ -230,7 +229,6 @@
     ...     print (region.lineno, region.evaluated or region.source)
     (1, 'This is my\ndoctest.\n\n')
     (4, <manuel.doctest.DocTestResult instance at 0x...>)
-    (6, '\n')
 
 And format the results.
 
@@ -249,7 +247,7 @@
     ...     42
     ... """)
     >>> document.process_with(m, globs={})
-    >>> print document.formatted()
+    >>> print document.formatted(),
     File "<memory>", line 4, in <memory>
     Failed example:
         1 + 1
@@ -276,7 +274,7 @@
     ...     1
     ... """)
     >>> document.process_with(m, globs={})
-    >>> print document.formatted()
+    >>> print document.formatted(),
 
 Imported modules are added to the global namespace as well.
 
@@ -290,7 +288,7 @@
     ...
     ... """)
     >>> document.process_with(m, globs={})
-    >>> print document.formatted()
+    >>> print document.formatted(),
 
 
 Combining Test Types
@@ -465,7 +463,7 @@
 information.
 
     >>> document.process_with(m, globs={})
-    >>> print document.formatted()
+    >>> print document.formatted(),
     File "<memory>", line 10, in <memory>
     Failed example:
         a + b
@@ -496,7 +494,7 @@
     ... """)
 
     >>> document.process_with(m, globs={})
-    >>> print document.formatted()
+    >>> print document.formatted(),
     File "<memory>", line 10, in <memory>
     Failed example:
         a + b # doesn't mention "c"
@@ -543,7 +541,7 @@
 included in the debugging information.
 
     >>> document.process_with(m, globs={})
-    >>> print document.formatted()
+    >>> print document.formatted(),
     File "<memory>", line 10, in <memory>
     Failed example:
         a + b # doesn't mention "c"

Modified: manuel/trunk/src/manuel/__init__.py
===================================================================
--- manuel/trunk/src/manuel/__init__.py	2009-06-22 11:36:12 UTC (rev 101216)
+++ manuel/trunk/src/manuel/__init__.py	2009-06-22 12:04:13 UTC (rev 101217)
@@ -197,10 +197,15 @@
         region2 = Region(region.lineno+lines_in_source1, source2)
         self.regions.insert(region_index, region2)
         self.regions.insert(region_index, region1)
+        if not region.source == source1 + source2:
+            raise RuntimeError('when splitting a region, combined results do '
+                'not equal the input')
         return region1, region2
 
     # XXX this method needs a better name
     def replace_region(self, to_be_replaced, parsed):
+        if parsed is None:
+            raise RuntimeError('None is not a valid value for "parsed"')
         new_regions = []
         old_regions = list(self.regions)
         while old_regions:
@@ -237,7 +242,7 @@
                 'Only regions not already in the document may be inserted.')
         if new_region in self.shadow_regions:
             raise ValueError(
-                'Regions regurned by "find_regions" can not be directly '
+                'Regions returned by "find_regions" can not be directly '
                 'inserted into a document.  Use "replace_region" instead.')
 
         for index, region in enumerate(self.regions):
@@ -325,4 +330,3 @@
         m.__extend(self)
         m.__extend(other)
         return m
-

Added: manuel/trunk/src/manuel/capture.py
===================================================================
--- manuel/trunk/src/manuel/capture.py	                        (rev 0)
+++ manuel/trunk/src/manuel/capture.py	2009-06-22 12:04:13 UTC (rev 101217)
@@ -0,0 +1,66 @@
+import manuel
+import re
+import string
+import textwrap
+
+CAPTURE_DIRECTIVE = re.compile(r'^\.\.\s*->\s*(?P<name>\S+).*$', re.MULTILINE)
+
+
+class Capture(object):
+    def __init__(self, name, block):
+        self.name = name
+        self.block = block
+
+
+ at manuel.timing(manuel.EARLY)
+def find_captures(document):
+    for region in document.find_regions(CAPTURE_DIRECTIVE):
+        # note that start and end have different bases, "start" is the offset
+        # from the begining of the region, "end" is a document line number
+        end = region.lineno-2
+
+        # not that we've extracted the information we need, lets slice up the
+        # document's regions to match
+
+        for candidate in document:
+            if candidate.lineno >= region.lineno:
+                break
+            found_region = candidate
+
+        lines = found_region.source.splitlines()
+        if found_region.lineno + len(lines) < end:
+            raise RuntimeError('both start and end lines must be in the '
+                'same region')
+
+        for offset, line in reversed(list(enumerate(lines))):
+            if offset > end - found_region.lineno:
+                continue
+            if line and line[0] in string.whitespace:
+                start = offset
+            if line and line[0] not in string.whitespace:
+                break
+        else:
+            raise RuntimeError("couldn't find the start of the block")
+
+        _, temp_region = document.split_region(found_region,
+            found_region.lineno+start)
+
+        # there are some extra lines in the new region, trim them off
+        final_region, _ = document.split_region(temp_region, end+1)
+        document.remove_region(final_region)
+
+        name = region.start_match.group('name')
+        block = textwrap.dedent(final_region.source)
+        document.replace_region(region, Capture(name, block))
+
+
+def store_capture(region, document, globs):
+    if not isinstance(region.parsed, Capture):
+        return
+
+    globs[region.parsed.name] = region.parsed.block
+
+
+class Manuel(manuel.Manuel):
+    def __init__(self):
+        manuel.Manuel.__init__(self, [find_captures], [store_capture])


Property changes on: manuel/trunk/src/manuel/capture.py
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: manuel/trunk/src/manuel/capture.txt
===================================================================
--- manuel/trunk/src/manuel/capture.txt	                        (rev 0)
+++ manuel/trunk/src/manuel/capture.txt	2009-06-22 12:04:13 UTC (rev 101217)
@@ -0,0 +1,70 @@
+.. _capture:
+
+Capturing Blocks
+================
+
+When writing documentation the need often arrises to describe the contents of
+files or other non-python information.  You may also want to put that
+information under test.  :mod:`manuel.capture` helps with that.
+
+If you were writing the problems for a programming contest, you might want to
+describe the input and output files for each challenge.
+
+You would then show the contestant the expected output of their program.  But
+you want to be sure that your examples are correct.
+
+To do that you might write your document like this:
+
+::
+
+    Given this example input file::
+
+        6
+        1
+        8
+        20
+        11
+        65
+        2
+
+    .. -> input
+
+    Your program should generare this output file::
+
+        1
+        2
+        6
+        8
+        11
+        20
+        65
+
+    .. -> expected
+
+        >>> correct = '\n'.join(
+        ...     map(str, sorted(map(int, input.splitlines())))) + '\n'
+        >>> expected == correct
+        True
+
+.. -> source
+
+    >>> import manuel
+    >>> document = manuel.Document(source)
+    >>> import manuel.capture
+    >>> m = manuel.capture.Manuel()
+    >>> import manuel.doctest
+    >>> m += manuel.doctest.Manuel()
+    >>> document.process_with(m, globs={})
+    >>> print document.formatted(),
+
+This uses the syntax implemented in :mod:`manuel.collect` to capture a block of
+text into a varible (the one named after "->").
+
+Whenever a line of the structure ".. -> VAR" is detected, the text of the
+*previous* block will be stored in the given variable.
+
+.. the paragraph below could be phrased better
+
+Of course, lines that start with ".. " are reST comments, so when the document
+is rendered with docutils or Sphinx, the tests will dissapear and only the
+intended document contents will remain.


Property changes on: manuel/trunk/src/manuel/capture.txt
___________________________________________________________________
Added: svn:eol-style
   + native

Modified: manuel/trunk/src/manuel/codeblock.py
===================================================================
--- manuel/trunk/src/manuel/codeblock.py	2009-06-22 11:36:12 UTC (rev 101216)
+++ manuel/trunk/src/manuel/codeblock.py	2009-06-22 12:04:13 UTC (rev 101217)
@@ -19,7 +19,7 @@
         document.replace_region(region, CodeBlock(code))
 
 
-def execute_code_blocks(region, document, globs):
+def execute_code_block(region, document, globs):
     if not isinstance(region.parsed, CodeBlock):
         return
 
@@ -29,4 +29,4 @@
 
 class Manuel(manuel.Manuel):
     def __init__(self):
-        manuel.Manuel.__init__(self, [find_code_blocks], [execute_code_blocks])
+        manuel.Manuel.__init__(self, [find_code_blocks], [execute_code_block])

Modified: manuel/trunk/src/manuel/doctest.py
===================================================================
--- manuel/trunk/src/manuel/doctest.py	2009-06-22 11:36:12 UTC (rev 101216)
+++ manuel/trunk/src/manuel/doctest.py	2009-06-22 12:04:13 UTC (rev 101217)
@@ -28,10 +28,13 @@
             if split_line_1 > region.lineno:
                 _, region = document.split_region(region, split_line_1)
 
-            if split_line_2 <= region_end:
+            if split_line_2 < region_end:
                 found, region = document.split_region(region, split_line_2)
-                document.replace_region(found, chunk)
+            else:
+                found = region
 
+            document.replace_region(found, chunk)
+
             assert region in document
 
 

Modified: manuel/trunk/src/manuel/footnote.txt
===================================================================
--- manuel/trunk/src/manuel/footnote.txt	2009-06-22 11:36:12 UTC (rev 101216)
+++ manuel/trunk/src/manuel/footnote.txt	2009-06-22 12:04:13 UTC (rev 101217)
@@ -5,48 +5,53 @@
 =========
 
 The :mod:`manuel.footnote` module provides an implementation of reST footnote
-handling.
+handling, but instead of just plain text, the footnotes can contain any syntax
+Manuel can interpred including doctests.
 
     >>> import manuel.footnote
     >>> m = manuel.footnote.Manuel()
 
-We'll try the footnotes out by combining them with doctests.
+Here's an example of combining footnotes with doctests:
 
+.. so we also need the doctest Manuel plug-in
+
     >>> import manuel.doctest
     >>> m += manuel.doctest.Manuel()
+
+::
+
+    Here we reference a footnote. [1]_
+
+        >>> x
+        42
+
+    Here we reference another. [2]_
+
+        >>> x
+        100
+
+    .. [1] This is a test footnote definition.
+
+        >>> x = 42
+
+    .. [2] This is another test footnote definition.
+
+        >>> x = 100
+
+    .. [3] This is a footnote that will never be executed.
+
+        >>> raise RuntimeError('nooooo!')
+
+.. -> source
+
     >>> import manuel
-    >>> document = manuel.Document("""\
-    ... Here we reference a footnote. [1]_
-    ...
-    ...     >>> x
-    ...     42
-    ...
-    ... Here we reference another. [2]_
-    ...
-    ...     >>> x
-    ...     100
-    ...
-    ... .. [1] This is a test footnote definition.
-    ...
-    ...     >>> x = 42
-    ...
-    ... .. [2] This is another test footnote definition.
-    ...
-    ...     >>> x = 100
-    ...
-    ... .. [3] This is a footnote that should never be executed.
-    ...
-    ...     >>> raise RuntimeError('nooooo!')
-    ... """)
+    >>> document = manuel.Document(source)
     >>> document.process_with(m, globs={})
-
-Since all the examples in the doctest above are correct, we expect no errors.
-
     >>> print document.formatted(),
 
-The order of examples in footnotes is preserved.  If not, the document below
-will generate an error because "a" won't be defined when "b = a + 1" is
-evaluated.
+.. The order of examples in footnotes is preserved.  If not, the document below
+   would generate an error because "a" won't be defined when "b = a + 1" is
+   evaluated.
 
     >>> document = manuel.Document("""
     ... Here we want some imports to be done. [foo]_
@@ -61,35 +66,38 @@
     ...     >>> a = 1
     ...
     ...     >>> b = a + 1
-    ...     
+    ...
     ... """)
     >>> document.process_with(m, globs={})
     >>> print document.formatted()
 
 It is possible to reference more than one footnote on a single line.
 
-    >>> document = manuel.Document("""
-    ... Here we want some imports to be done. [1]_ [2]_ [3]_
-    ...
-    ...     >>> z
-    ...     105
-    ...
-    ... A little prose to separate the examples.
-    ...
-    ... .. [1] Do something
-    ...
-    ...     >>> w = 3
-    ...
-    ... .. [2] Do something
-    ...
-    ...     >>> x = 5
-    ...
-    ... .. [3] Do something
-    ...
-    ...     >>> y = 7
-    ...
-    ...     >>> z = w * x * y
-    ...     
-    ... """)
+::
+
+    This line has several footnotes on it. [1]_ [2]_ [3]_
+
+        >>> z
+        105
+
+    A little prose to separate the examples.
+
+    .. [1] Do something
+
+        >>> w = 3
+
+    .. [2] Do something
+
+        >>> x = 5
+
+    .. [3] Do something
+
+        >>> y = 7
+
+        >>> z = w * x * y
+
+.. -> source2
+
+    >>> document = manuel.Document(source)
     >>> document.process_with(m, globs={})
     >>> print document.formatted()

Modified: manuel/trunk/src/manuel/ignore.py
===================================================================
--- manuel/trunk/src/manuel/ignore.py	2009-06-22 11:36:12 UTC (rev 101216)
+++ manuel/trunk/src/manuel/ignore.py	2009-06-22 12:04:13 UTC (rev 101217)
@@ -9,7 +9,7 @@
 
 def find_ignores(document):
     for region in document.find_regions(IGNORE_START, IGNORE_END):
-        document.replace_region(region, None)
+        document.replace_region(region, object())
         document.remove_region(region)
 
 

Modified: manuel/trunk/src/manuel/testing.py
===================================================================
--- manuel/trunk/src/manuel/testing.py	2009-06-22 11:36:12 UTC (rev 101216)
+++ manuel/trunk/src/manuel/testing.py	2009-06-22 12:04:13 UTC (rev 101217)
@@ -1,8 +1,9 @@
-from zope.testing import doctest
 import manuel
 import os.path
 import unittest
 
+#doctest = manuel.absolute_import('doctest')
+from zope.testing import doctest
 
 __all__ = ['TestSuite']
 

Modified: manuel/trunk/src/manuel/tests.py
===================================================================
--- manuel/trunk/src/manuel/tests.py	2009-06-22 11:36:12 UTC (rev 101216)
+++ manuel/trunk/src/manuel/tests.py	2009-06-22 12:04:13 UTC (rev 101217)
@@ -1,5 +1,6 @@
 from zope.testing import renormalizing
 import manuel
+import manuel.capture
 import manuel.codeblock
 import manuel.doctest
 import manuel.ignore
@@ -28,21 +29,19 @@
     checker = renormalizing.RENormalizing([
         (re.compile(r'<zope\.testing\.doctest\.'), '<doctest.'),
         ])
-    suite = unittest.TestSuite()
 
     tests = ['README.txt', 'footnote.txt', 'bugs.txt', 'codeblock.txt',
         'isolation.txt', 'table-example.txt', '../getting-started.txt',
-        'ignore.txt']
+        'ignore.txt', 'capture.txt']
 
     tests = map(get_abs_path, tests)
 
     m = manuel.ignore.Manuel()
     m += manuel.doctest.Manuel(optionflags=optionflags, checker=checker)
     m += manuel.codeblock.Manuel()
-    suite.addTest(manuel.testing.TestSuite(m, *tests))
+    m += manuel.capture.Manuel()
+    return manuel.testing.TestSuite(m, *tests)
 
-    return suite
 
-
 if __name__ == '__main__':
     unittest.TextTestRunner().run(test_suite())



More information about the Checkins mailing list