changeset 382:2682dabbcd04 trunk

* Added documentation for the various stream event kinds. * Move generation of HTML documentation into a custom distutils command, run by `setup.py build_doc` * Added verification of doctest snippets in documentation, which can be run by `setup.py test_doc` * Fixed `repr` of `Markup` instances.
author cmlenz
date Fri, 01 Dec 2006 23:43:59 +0000
parents b9fc7a1f76ca
children e17b84835b0f
files doc/Makefile doc/builder.txt doc/streams.txt doc/style/edgewall.css genshi/core.py genshi/template/eval.py genshi/tests/core.py setup.py
diffstat 8 files changed, 205 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
deleted file mode 100644
--- a/doc/Makefile
+++ /dev/null
@@ -1,15 +0,0 @@
-HTML_FILES = \
-	builder.html \
-	index.html \
-	streams.html \
-	text-templates.html \
-	xml-templates.html \
-	xpath.html
-
-all: $(HTML_FILES)
-
-%.html: %.txt
-	rst2html.py --exit-status=3 $< $@
-
-clean:
-	rm -rf $(HTML_FILES)
--- a/doc/builder.txt
+++ b/doc/builder.txt
@@ -52,7 +52,7 @@
 
   >>> stream = doc.generate()
   >>> stream
-  <genshi.core.Stream object at 0x72d230>
+  <genshi.core.Stream object at ...>
   >>> print stream
   <p class="intro">Some text and <a href="http://example.org/">a link</a>.<br/></p>
 
@@ -65,7 +65,7 @@
 for creating snippets of markup that are attached to a parent element later (for
 example in a template). Fragments are created by calling the ``tag`` object::
 
-  >>> fragment = tag('Hello, ', tag.em('word'), '!')
+  >>> fragment = tag('Hello, ', tag.em('world'), '!')
   >>> fragment
   <Fragment>
   >>> print fragment
--- a/doc/streams.txt
+++ b/doc/streams.txt
@@ -8,7 +8,7 @@
 
 
 .. contents:: Contents
-   :depth: 2
+   :depth: 1
 .. sectnum::
 
 
@@ -30,7 +30,7 @@
   ...              '<a href="http://example.org/">a link</a>.'
   ...              '<br/></p>')
   >>> stream
-  <genshi.core.Stream object at 0x6bef0>
+  <genshi.core.Stream object at ...>
 
 The stream is the result of parsing the text into events. Each event is a tuple
 of the form ``(kind, data, pos)``, where:
@@ -38,7 +38,7 @@
 * ``kind`` defines what kind of event it is (such as the start of an element,
   text, a comment, etc).
 * ``data`` is the actual data associated with the event. How this looks depends
-  on the event kind.
+  on the event kind (see  `event kinds`_)
 * ``pos`` is a ``(filename, lineno, column)`` tuple that describes where the
   event “comes from”.
 
@@ -47,15 +47,15 @@
   >>> for kind, data, pos in stream:
   ...     print kind, `data`, pos
   ... 
-  START (u'p', [(u'class', u'intro')]) ('<string>', 1, 0)
-  TEXT u'Some text and ' ('<string>', 1, 31)
-  START (u'a', [(u'href', u'http://example.org/')]) ('<string>', 1, 31)
-  TEXT u'a link' ('<string>', 1, 67)
-  END u'a' ('<string>', 1, 67)
-  TEXT u'.' ('<string>', 1, 72)
-  START (u'br', []) ('<string>', 1, 72)
-  END u'br' ('<string>', 1, 77)
-  END u'p' ('<string>', 1, 77)
+  START (QName(u'p'), Attrs([(QName(u'class'), u'intro')])) (None, 1, 0)
+  TEXT u'Some text and ' (None, 1, 17)
+  START (QName(u'a'), Attrs([(QName(u'href'), u'http://example.org/')])) (None, 1, 31)
+  TEXT u'a link' (None, 1, 61)
+  END QName(u'a') (None, 1, 67)
+  TEXT u'.' (None, 1, 71)
+  START (QName(u'br'), Attrs()) (None, 1, 72)
+  END QName(u'br') (None, 1, 77)
+  END QName(u'p') (None, 1, 77)
 
 
 Filtering
@@ -150,7 +150,7 @@
 
   >>> from genshi.filters import HTMLSanitizer
   >>> from genshi.output import TextSerializer
-  >>> print TextSerializer()(HTMLSanitizer()(stream))
+  >>> print ''.join(TextSerializer()(HTMLSanitizer()(stream)))
   Some text and a link.
 
 The pipe operator allows a nicer syntax::
@@ -158,6 +158,7 @@
   >>> print stream | HTMLSanitizer() | TextSerializer()
   Some text and a link.
 
+
 Using XPath
 ===========
 
@@ -166,7 +167,7 @@
 
   >>> substream = stream.select('a')
   >>> substream
-  <genshi.core.Stream object at 0x7118b0>
+  <genshi.core.Stream object at ...>
   >>> print substream
   <a href="http://example.org/">a link</a>
 
@@ -178,10 +179,126 @@
   >>> from genshi import Stream
   >>> substream = Stream(list(stream.select('a')))
   >>> substream
-  <genshi.core.Stream object at 0x7118b0>
+  <genshi.core.Stream object at ...>
   >>> print substream
   <a href="http://example.org/">a link</a>
   >>> print substream.select('@href')
   http://example.org/
   >>> print substream.select('text()')
   a link
+
+See `Using XPath in Genshi`_ for more information about the XPath support in
+Genshi.
+
+.. _`Using XPath in Genshi`: xpath.html
+
+
+.. _`event kinds`:
+
+Event Kinds
+===========
+
+Every event in a stream is of one of several *kinds*, which also determines
+what the ``data`` item of the event tuple looks like. The different kinds of
+events are documented below.
+
+.. note:: The ``data`` item is generally immutable. It the data is to be
+   modified when processing a stream, it must be replaced by a new tuple.
+   Effectively, this means the entire event tuple is immutable.
+
+START
+-----
+The opening tag of an element.
+
+For this kind of event, the ``data`` item is a tuple of the form
+``(tagname, attrs)``, where ``tagname`` is a ``QName`` instance describing the
+qualified name of the tag, and ``attrs`` is an ``Attrs`` instance containing
+the attribute names and values associated with the tag (excluding namespace
+declarations)::
+
+  START, (QName(u'p'), Attrs([(u'class', u'intro')])), pos
+
+END
+---
+The closing tag of an element.
+
+The ``data`` item of end events consists of just a ``QName`` instance
+describing the qualified name of the tag::
+
+  END, QName(u'p'), pos
+
+TEXT
+----
+Character data outside of elements and other nodes.
+
+For text events, the ``data`` item should be a unicode object::
+
+  TEXT, u'Hello, world!', pos
+
+START_NS
+--------
+The start of a namespace mapping, binding a namespace prefix to a URI.
+
+The ``data`` item of this kind of event is a tuple of the form
+``(prefix, uri)``, where ``prefix`` is the namespace prefix and ``uri`` is the
+full URI to which the prefix is bound. Both should be unicode objects. If the
+namespace is not bound to any prefix, the ``prefix`` item is an empty string::
+
+  START_NS, (u'svg', u'http://www.w3.org/2000/svg'), pos
+
+END_NS
+------
+The end of a namespace mapping.
+
+The ``data`` item of such events consists of only the namespace prefix (a
+unicode object)::
+
+  END_NS, u'svg', pos
+
+DOCTYPE
+-------
+A document type declaration.
+
+For this type of event, the ``data`` item is a tuple of the form
+``(name, pubid, sysid)``, where ``name`` is the name of the root element,
+``pubid`` is the public identifier of the DTD (or ``None``), and ``sysid`` is
+the system identifier of the DTD (or ``None``)::
+
+  DOCTYPE, (u'html', u'-//W3C//DTD XHTML 1.0 Transitional//EN', \
+            u'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'), pos
+
+COMMENT
+-------
+A comment.
+
+For such events, the ``data`` item is a unicode object containing all character
+data between the comment delimiters::
+
+  COMMENT, u'Commented out', pos
+
+PI
+--
+A processing instruction.
+
+The ``data`` item is a tuple of the form ``(target, data)`` for processing
+instructions, where ``target`` is the target of the PI (used to identify the
+application by which the instruction should be processed), and ``data`` is text
+following the target (excluding the terminating question mark)::
+
+  PI, (u'php', u'echo "Yo" '), pos
+
+START_CDATA
+-----------
+Marks the beginning of a ``CDATA`` section.
+
+The ``data`` item for such events is always ``None``::
+
+  START_CDATA, None, pos
+
+END_CDATA
+---------
+Marks the end of a ``CDATA`` section.
+
+The ``data`` item for such events is always ``None``::
+
+  END_CDATA, None, pos
--- a/doc/style/edgewall.css
+++ b/doc/style/edgewall.css
@@ -7,13 +7,15 @@
 }
 pre, code, tt { font-size: medium; }
 h1, h2, h3, h4 {
+  border-bottom: 1px solid #ccc;
   font-family: Arial,Verdana,'Bitstream Vera Sans',Helvetica,sans-serif;
   font-weight: bold; letter-spacing: -0.018em;
 }
-h1 { font-size: 19px; margin: 2em 1em 0 0; }
-h2 { font-size: 16px; }
-h3 { font-size: 14px; }
+h1 { font-size: 19px; margin: 2em 0 .5em; }
+h2 { font-size: 16px; margin: 1.5em 0 .5em; }
+h3 { font-size: 14px; margin: 1.2em 0 .5em; }
 hr { border: none;  border-top: 1px solid #ccb; margin: 2em 0; }
+p { margin: 0 0 1em; }
 
 :link, :visited { text-decoration: none; border-bottom: 1px dotted #bbb;
   color: #b00;
@@ -26,21 +28,21 @@
 }
 
 div.document { background: #fff url(shadow.gif) right top repeat-y;
-  border-left: 1px solid #000; margin: 0 auto 0 80px;
+  border-left: 1px solid #000; margin: 0 auto 0 40px;
   min-height: 100%; width: 54em; padding: 0 180px 1px 20px;
 }
-h1.title, div.document#genshi h1 { color: #666; font-size: x-large;
-  margin: 0 -20px 1em; padding: 2em 20px 0;
+h1.title, div.document#genshi h1 { border: none; color: #666;
+  font-size: x-large; margin: 0 -20px 1em; padding: 2em 20px 0;
 }
 h1.title { background: url(vertbars.png) repeat-x; }
 div.document#genshi h1.title { text-indent: -4000px; }
 div.document#genshi h1 { text-align: center; }
-pre.literal-block { background: #f7f7f7; border: 1px solid #d7d7d7;
-  margin: 1em 0; padding: .25em; overflow: auto;
+pre.literal-block { background: #d7d7d7; border: 1px solid #e6e6e6; color: #000;
+  margin: 1em 1em; padding: .25em; overflow: auto;
 }
 
-div.contents { position: absolute; position: fixed; margin-left: 80px;
-  left: 57.3em; top: 30px; right: 0;
+div.contents { font-size: 90%; position: absolute; position: fixed;
+  margin-left: 80px; left: 60em; top: 30px; right: 0;
 }
 div.contents .topic-title { display: none; }
 div.contents ul { list-style: none; padding-left: 0; }
@@ -50,3 +52,6 @@
 div.contents :link:hover, div.contents :visited:hover { background: #000;
   color: #fff;
 }
+
+p.admonition-title { font-weight: bold; margin-bottom: 0; }
+div.note { font-style: italic; margin-left: 2em; margin-right: 2em; }
--- a/genshi/core.py
+++ b/genshi/core.py
@@ -400,7 +400,7 @@
         return Markup(num * unicode(self))
 
     def __repr__(self):
-        return '<%s "%s">' % (self.__class__.__name__, self)
+        return '<%s %r>' % (self.__class__.__name__, unicode(self))
 
     def join(self, seq, escape_quotes=True):
         return Markup(unicode(self).join([escape(item, quotes=escape_quotes)
--- a/genshi/template/eval.py
+++ b/genshi/template/eval.py
@@ -201,14 +201,15 @@
 
 BUILTINS = __builtin__.__dict__.copy()
 BUILTINS['Undefined'] = Undefined
+_UNDEF = Undefined(None)
 
 def _lookup_name(data, name):
     __traceback_hide__ = True
-    val = data.get(name, Undefined)
-    if val is Undefined:
+    val = data.get(name, _UNDEF)
+    if val is _UNDEF:
         val = BUILTINS.get(name, val)
-        if val is Undefined:
-            return val(name)
+        if val is _UNDEF:
+            return Undefined(name)
     return val
 
 def _lookup_attr(data, obj, key):
@@ -232,8 +233,8 @@
         return obj[key]
     except (KeyError, IndexError, TypeError), e:
         if isinstance(key, basestring):
-            val = getattr(obj, key, Undefined)
-            if val is Undefined:
+            val = getattr(obj, key, _UNDEF)
+            if val is _UNDEF:
                 val = Undefined(key)
             return val
         raise
--- a/genshi/tests/core.py
+++ b/genshi/tests/core.py
@@ -48,7 +48,7 @@
 
     def test_repr(self):
         markup = Markup('foo')
-        self.assertEquals('<Markup "foo">', repr(markup))
+        self.assertEquals("<Markup u'foo'>", repr(markup))
 
     def test_escape(self):
         markup = escape('<b>"&"</b>')
@@ -131,7 +131,7 @@
         buf = StringIO()
         pickle.dump(markup, buf, 2)
         buf.seek(0)
-        self.assertEquals('<Markup "foo">', repr(pickle.load(buf)))
+        self.assertEquals("<Markup u'foo'>", repr(pickle.load(buf)))
 
 
 class NamespaceTestCase(unittest.TestCase):
--- a/setup.py
+++ b/setup.py
@@ -12,11 +12,55 @@
 # individuals. For the exact contribution history, see the revision
 # history and logs, available at http://genshi.edgewall.org/log/.
 
+from distutils.cmd import Command
+import doctest
+from glob import glob
+import os
 try:
     from setuptools import setup
 except ImportError:
     from distutils.core import setup
 
+
+class test_doc(Command):
+    description = 'Tests the code examples in the documentation'
+    user_options = []
+
+    def initialize_options(self):
+        pass
+
+    def finalize_options(self):
+        pass
+
+    def run(self):
+        for filename in glob('doc/*.txt'):
+            print 'testing documentation file %s' % filename
+            doctest.testfile(filename, False, optionflags=doctest.ELLIPSIS)
+
+
+class build_doc(Command):
+    description = 'Builds the documentation'
+    user_options = []
+
+    def initialize_options(self):
+        pass
+
+    def finalize_options(self):
+        pass
+
+    def run(self):
+        from docutils.core import publish_cmdline
+        conf = os.path.join('doc', 'docutils.conf')
+
+        for source in glob('doc/*.txt'):
+            dest = os.path.splitext(source)[0] + '.html'
+            if not os.path.exists(dest) or \
+                   os.path.getmtime(dest) < os.path.getmtime(source):
+                print 'building documentation file %s' % dest
+                publish_cmdline(writer_name='html',
+                                argv=['--config=%s' % conf, source, dest])
+
+
 setup(
     name = 'Genshi',
     version = '0.4',
@@ -56,4 +100,6 @@
     genshi-markup = genshi.template.plugin:MarkupTemplateEnginePlugin[plugin]
     genshi-text = genshi.template.plugin:TextTemplateEnginePlugin[plugin]
     """,
+
+    cmdclass={'build_doc': build_doc, 'test_doc': test_doc}
 )
Copyright (C) 2012-2017 Edgewall Software