# HG changeset patch # User cmlenz # Date 1165016639 0 # Node ID 2682dabbcd04c5dbf83da71c51e3ef80fa1463ea # Parent b9fc7a1f76ca7c82c0cc5d2b8d2001440e341404 * 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} )