# HG changeset patch
# User cmlenz
# Date 1165016639 0
# Node ID d7da3fba7faf5d65013e291a77be5adb54857280
# Parent a6c2a9cd2e92ddd04036ebefe5582fd6636a157b
* 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.
diff --git a/doc/Makefile b/doc/Makefile
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)
diff --git a/doc/builder.txt b/doc/builder.txt
--- a/doc/builder.txt
+++ b/doc/builder.txt
@@ -52,7 +52,7 @@
>>> stream = doc.generate()
>>> stream
-
+
>>> print stream
Some text and a link.
@@ -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
>>> print fragment
diff --git a/doc/streams.txt b/doc/streams.txt
--- a/doc/streams.txt
+++ b/doc/streams.txt
@@ -8,7 +8,7 @@
.. contents:: Contents
- :depth: 2
+ :depth: 1
.. sectnum::
@@ -30,7 +30,7 @@
... 'a link.'
... '
')
>>> stream
-
+
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')]) ('', 1, 0)
- TEXT u'Some text and ' ('', 1, 31)
- START (u'a', [(u'href', u'http://example.org/')]) ('', 1, 31)
- TEXT u'a link' ('', 1, 67)
- END u'a' ('', 1, 67)
- TEXT u'.' ('', 1, 72)
- START (u'br', []) ('', 1, 72)
- END u'br' ('', 1, 77)
- END u'p' ('', 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
-
+
>>> print substream
a link
@@ -178,10 +179,126 @@
>>> from genshi import Stream
>>> substream = Stream(list(stream.select('a')))
>>> substream
-
+
>>> print substream
a link
>>> 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
diff --git a/doc/style/edgewall.css b/doc/style/edgewall.css
--- 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; }
diff --git a/genshi/core.py b/genshi/core.py
--- 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)
diff --git a/genshi/template/eval.py b/genshi/template/eval.py
--- 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
diff --git a/genshi/tests/core.py b/genshi/tests/core.py
--- a/genshi/tests/core.py
+++ b/genshi/tests/core.py
@@ -48,7 +48,7 @@
def test_repr(self):
markup = Markup('foo')
- self.assertEquals('', repr(markup))
+ self.assertEquals("", repr(markup))
def test_escape(self):
markup = escape('"&"')
@@ -131,7 +131,7 @@
buf = StringIO()
pickle.dump(markup, buf, 2)
buf.seek(0)
- self.assertEquals('', repr(pickle.load(buf)))
+ self.assertEquals("", repr(pickle.load(buf)))
class NamespaceTestCase(unittest.TestCase):
diff --git a/setup.py b/setup.py
--- 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}
)