[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