summaryrefslogtreecommitdiff
path: root/sandbox/edloper
diff options
context:
space:
mode:
Diffstat (limited to 'sandbox/edloper')
-rw-r--r--sandbox/edloper/docpy/asyncore.rst295
-rwxr-xr-xsandbox/edloper/docpy/docpy.py323
-rw-r--r--sandbox/edloper/epytext/epytext.py95
-rw-r--r--sandbox/edloper/epytext/epytext.test26
-rwxr-xr-xsandbox/edloper/epytext/publish_epytext.py26
-rw-r--r--sandbox/edloper/roles/README.txt83
-rw-r--r--sandbox/edloper/roles/roles.py296
-rw-r--r--sandbox/edloper/roles/states.py2840
8 files changed, 0 insertions, 3984 deletions
diff --git a/sandbox/edloper/docpy/asyncore.rst b/sandbox/edloper/docpy/asyncore.rst
deleted file mode 100644
index 2eda1647c..000000000
--- a/sandbox/edloper/docpy/asyncore.rst
+++ /dev/null
@@ -1,295 +0,0 @@
-==========
- asyncore
-==========
-
------------------------------
- Asynchronous socket handler
------------------------------
-
-Literal block::
- Synopsis: A base class for developing asynchronous socket
- handling services.
- Type: module builtin
- Module-Author: Sam Rushing <rushing@nightmare.com>
- Author: Christopher Petrilli <petrilli@amber.org>
- Author: Steve Holden <sholden@holdenweb.com>
-
-.. Type: ... builtin, standard, various others: any specific usages required?
-..
-.. Heavily adapted from original documentation by Sam Rushing.
-..
-.. ............................................
-.. This is the (first) RFC822-reader strawman
-.. ............................................
-.. Presumes a custom reader appropriate to docpy
-.. RFC822 continuation IS allowed (see Synopsis)
-.. Needtocheck: RFC822-readers and multiple entities? (Author lines)
-.. Dunno about implication of \section in the original
-.. Dunno about comments (#?); "Credit: Sam Rushing?"
-.. Note in passing: names of new roles and directives made similar to
-.. the existing docpy macros on purpose (for existing corpus & community)
-..
-.. Markups needed, used, and existing in rst:
-.. *emphasis*
-..
-.. Markups needed, used, and modified by this strawman:
-.. ``code``
-..
-.. Roles needed below by this strawman:
-.. :cfunction:``
-.. :module:``
-.. :refmodule:``
-.. :class:``
-.. :function:``
-.. :var:``
-.. :label:``
-..
-.. Directives needed below by this strawman:
-.. .. funcdesc::
-.. need to parse for optional argumnents shown as [...]
-.. .. classdesc::
-.. .. datadesc::
-..
-.. TBS - formals, e.g., funcdesc - several alternatives proposed
-.. below (see funcdesc) in this draft
-.. the one shown first seems on track for consensus 04.3.20
-.. (the directive will parse brackets, etc. - easier to use!)
-
-This module provides the basic infrastructure for writing asynchronous
-socket service clients and servers.
-
-There are only two ways to have a program on a single processor do
-"more than one thing at a time." Multi-threaded programming is the
-simplest and most popular way to do it, but there is another very
-different technique, that lets you have nearly all the advantages of
-multi-threading, without actually using multiple threads. It's really
-only practical if your program is largely I/O bound. If your program
-is processor bound, then pre-emptive scheduled threads are probably what
-you really need. Network servers are rarely processor bound, however.
-
-If your operating system supports the :cfunction:`select()` system call
-in its I/O library (and nearly all do), then you can use it to juggle
-multiple communication channels at once; doing other work while your
-I/O is taking place in the "background." Although this strategy can
-seem strange and complex, especially at first, it is in many ways
-easier to understand and control than multi-threaded programming.
-The :module:`asyncore` module solves many of the difficult problems for
-you, making the task of building sophisticated high-performance
-network servers and clients a snap. For "conversational" applications
-and protocols the companion :refmodule:`asynchat` module is invaluable.
-
-The basic idea behind both modules is to create one or more network
-*channels*, instances of class :class:`asyncore.dispatcher` and
-:class:`asynchat.async_chat`. Creating the channels adds them to a global
-map, used by the :function:`loop()` function if you do not provide it
-with your own :var:`map`.
-
-Once the initial channel(s) is(are) created, calling the :function:`loop()`
-function activates channel service, which continues until the last
-channel (including any that have been added to the map during asynchronous
-service) is closed.
-
-.. funcdesc:: loop([timeout [, use_poll [, map]]])
-
- Enter a polling loop that only terminates after all open channels
- have been closed. All arguments are optional. The :var:`timeout`
- argument sets the timeout parameter for the appropriate
- :function:`select()` or :function:`poll()` call, measured in seconds;
- the default is 30 seconds. The :var:`use_poll` parameter, if true,
- indicates that :function:`poll()` should be used in preference to
- :function:`select()` (the default is ``False``). The :var:`map` parameter
- is a dictionary whose items are the channels to watch. As channels
- are closed they are deleted from their map. If :var:`map` is
- omitted, a global map is used (this map is updated by the default
- class :method:`__init__()`
- -- make sure you extend, rather than override, :method:`__init__()`
- if you want to retain this behavior).
-
- Channels (instances of :class:`asyncore.dispatcher`, :class:`asynchat.async_chat`
- and subclasses thereof) can freely be mixed in the map.
-
-.. classdesc:: dispatcher()
-
- The :class:`dispatcher` class is a thin wrapper around a low-level socket object.
- To make it more useful, it has a few methods for event-handling which are called
- from the asynchronous loop.
- Otherwise, it can be treated as a normal non-blocking socket object.
-
- Two class attributes can be modified, to improve performance,
- or possibly even to conserve memory.
-
- .. datadesc:: ac_in_buffer_size
-
- The asynchronous input buffer size (default ``4096``).
-
- .. datadesc:: ac_out_buffer_size
-
- The asynchronous output buffer size (default ``4096``).
-
- The firing of low-level events at certain times or in certain connection
- states tells the asynchronous loop that certain higher-level events have
- taken place. For example, if we have asked for a socket to connect to
- another host, we know that the connection has been made when the socket
- becomes writable for the first time (at this point you know that you may
- write to it with the expectation of success). The implied higher-level
- events are:
-
- ===================== ===============================================
- ``Event`` Description
- --------------------- -----------------------------------------------
- ``handle_connect()`` Implied by the first write event
- ``handle_close()`` Implied by a read event with no data available
- ``handle_accept()`` Implied by a read event on a listening socket
- ===================== ===============================================
-
-
- During asynchronous processing, each mapped channel's :method:`readable()`
- and :method:`writable()` methods are used to determine whether the channel's
- socket should be added to the list of channels :cfunction:`select()`\ ed or
- :cfunction:`poll()`\ ed for read and write events.
-
-Thus, the set of channel events is larger than the basic socket events.
-The full set of methods that can be overridden in your subclass follows:
-
-.. methoddesc:: handle_read()
-
- Called when the asynchronous loop detects that a :method:`read()`
- call on the channel's socket will succeed.
-
-.. methoddesc:: handle_write()
-
- Called when the asynchronous loop detects that a writable socket
- can be written.
- Often this method will implement the necessary buffering for
- performance. For example::
-
-
- def handle_write(self):
- sent = self.send(self.buffer)
- self.buffer = self.buffer[sent:]
-
-.. methoddesc:: handle_expt()
-
- Called when there is out of band (OOB) data for a socket
- connection. This will almost never happen, as OOB is
- tenuously supported and rarely used.
-
-.. methoddesc:: handle_connect()
-
- Called when the active opener's socket actually makes a connection.
- Might send a "welcome" banner, or initiate a protocol
- negotiation with the remote endpoint, for example.
-
-.. methoddesc:: handle_close()
-
- Called when the socket is closed.
-
-.. methoddesc:: handle_error()
-
- Called when an exception is raised and not otherwise handled. The default
- version prints a condensed traceback.
-
-.. methoddesc:: handle_accept()
-
- Called on listening channels (passive openers) when a
- connection can be established with a new remote endpoint that
- has issued a :method:`connect()` call for the local endpoint.
-
-.. methoddesc:: readable()
-
- Called each time around the asynchronous loop to determine whether a
- channel's socket should be added to the list on which read events can
- occur. The default method simply returns ``True``,
- indicating that by default, all channels will be interested in
- read events.
-
-.. methoddesc:: writable()
-
- Called each time around the asynchronous loop to determine whether a
- channel's socket should be added to the list on which write events can
- occur. The default method simply returns ``True``,
- indicating that by default, all channels will be interested in
- write events.
-
-In addition, each channel delegates or extends many of the socket methods.
-Most of these are nearly identical to their socket partners.
-
-.. methoddesc:: create_socket(family, type)
-
- This is identical to the creation of a normal socket, and
- will use the same options for creation. Refer to the
- :refmodule:`socket` documentation for information on creating
- sockets.
-
-.. methoddesc:: connect(address)
-
- As with the normal socket object, :var:`address` is a
- tuple with the first element the host to connect to, and the
- second the port number.
-
-.. methoddesc:: send(data)
-
- Send :var:`data` to the remote end-point of the socket.
-
-.. methoddesc:: recv(buffer_size)
-
- Read at most :var:`buffer_size` bytes from the socket's remote end-point.
- An empty string implies that the channel has been closed from the other
- end.
-
-.. methoddesc:: listen(backlog)
-
- Listen for connections made to the socket. The :var:`backlog`
- argument specifies the maximum number of queued connections
- and should be at least 1; the maximum value is
- system-dependent (usually 5).
-
-.. methoddesc:: bind(address)
-
- Bind the socket to :var:`address`. The socket must not already
- be bound. (The format of :var:`address` depends on the address
- family --- see above.)
-
-.. methoddesc:: accept()
-
- Accept a connection. The socket must be bound to an address
- and listening for connections. The return value is a pair
- ``(conn , address)`` where :var:`conn` is a
- *new* socket object usable to send and receive data on
- the connection, and :var:`address` is the address bound to the
- socket on the other end of the connection.
-
-.. methoddesc:: close()
-
- Close the socket. All future operations on the socket object
- will fail. The remote end-point will receive no more data (after
- queued data is flushed). Sockets are automatically closed
- when they are garbage-collected.
-
-
-asyncore Example basic HTTP client :label:`asyncore-example`
-------------------------------------------------------------
-As a basic example, below is a very basic HTTP client that uses the
-:class:`dispatcher` class to implement its socket handling::
-
- class http_client(asyncore.dispatcher):
- def __init__(self, host,path):
- asyncore.dispatcher.__init__(self)
- self.path = path
- self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
- self.connect( (host, 80) )
- self.buffer = 'GET %s HTTP/1.0\r\n\r\n' % self.path
-
- def handle_connect(self):
- pass
-
- def handle_read(self):
- data = self.recv(8192)
- print data
-
- def writable(self):
- return (len(self.buffer) > 0)
-
- def handle_write(self):
- sent = self.send(self.buffer)
- self.buffer = self.buffer[sent:]
diff --git a/sandbox/edloper/docpy/docpy.py b/sandbox/edloper/docpy/docpy.py
deleted file mode 100755
index 450a9b735..000000000
--- a/sandbox/edloper/docpy/docpy.py
+++ /dev/null
@@ -1,323 +0,0 @@
-#!/usr/bin/env python
-
-# Author: David Goodger
-# Contact: goodger@users.sourceforge.net
-# Revision: $Revision$
-# Date: $Date$
-# Copyright: This module has been placed in the public domain.
-
-"""
-:todo: role-labeled inline text
-:todo: generate output (subclass LaTeXTransformer)
-:todo later: macros (susbtitution refs)
-"""
-from docutils import nodes
-from docutils.parsers.rst import directives
-from docutils.parsers.rst.directives import admonitions
-from docutils.parsers.rst import states
-from docutils.writers import latex2e
-import re, tokenize
-
-######################################################################
-# New nodes
-######################################################################
-
-class funcdesc(nodes.Admonition, nodes.Element): pass
-class classdesc(nodes.Admonition, nodes.Element): pass
-class methoddesc(nodes.Admonition, nodes.Element): pass
-class datadesc(nodes.Admonition, nodes.Element): pass
-class desc_name(nodes.Part, nodes.Inline, nodes.TextElement): pass
-
-# We might use pynodes instead.
-class func_signature(nodes.Part, nodes.Inline, nodes.TextElement): pass
-class func_name(nodes.Part, nodes.Inline, nodes.TextElement): pass
-class func_parameterlist(nodes.Part, nodes.Inline, nodes.TextElement): pass
-class func_parameter(nodes.Part, nodes.Inline, nodes.TextElement): pass
-class func_optional(nodes.Part, nodes.Inline, nodes.TextElement): pass
-
-# These are the inline things.
-class docpy_function(nodes.Inline, nodes.TextElement): pass
-class docpy_manpage(nodes.Inline, nodes.TextElement): pass
-class docpy_regexp(nodes.Inline, nodes.TextElement): pass
-class docpy_file(nodes.Inline, nodes.TextElement): pass
-class docpy_label(nodes.Inline, nodes.TextElement): pass
-class docpy_class(nodes.Inline, nodes.TextElement): pass
-class docpy_method(nodes.Inline, nodes.TextElement): pass
-class docpy_cfunction(nodes.Inline, nodes.TextElement): pass
-class docpy_refmodule(nodes.Inline, nodes.TextElement): pass
-class docpy_module(nodes.Inline, nodes.TextElement): pass
-class docpy_var(nodes.Inline, nodes.TextElement): pass
-inline_docpy_elements = { # Maps roles to entitites
- 'function':docpy_function,
- 'manpage':docpy_manpage,
- 'regexp':docpy_regexp,
- 'file':docpy_file,
- 'label': docpy_label,
- 'class': docpy_class,
- 'method': docpy_method,
- 'cfunction': docpy_cfunction,
- 'refmodule': docpy_refmodule,
- 'module': docpy_module,
- 'var': docpy_var,
- }
-
-for (role, element) in inline_docpy_elements.items():
- states.register_inliner_role(role, element)
-
-
-######################################################################
-# Directives
-######################################################################
-
-# Transform a python signature into RST.
-def parse_signature(s):
- """
- A straw-man implementation. (Might be sufficient)
- """
- s = s.strip()
- m = re.match(r'^(\w+)\s*\((.*)\)$', s)
- if m is None: raise ValueError(`s`)
- name, arglist = m.groups()
-
- sig = func_signature(s,'')
-
- sig.append(func_name(name,name))
- sig.append(func_parameterlist())
-
- stack = [sig[-1]]
- for token in re.split(r'(\*{0,2}\w+|[\[\],])', arglist):
- #print `token`, stack
- if token == '[':
- opt = func_optional()
- stack[-1].append(opt)
- stack.append(opt)
- elif token == ']':
- stack.pop()
- elif token == ',':
- pass
- elif re.match(r'^\s*$', token):
- pass
- elif re.match(r'^\*{0,2}\w+$', token):
- stack[-1].append(func_parameter(token,token))
- else:
- raise ValueError(s)
- if len(stack) != 1: raise ValueError(s)
- return sig
-
-def funcdesc_directive(name, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
- rv = admonitions.make_admonition(funcdesc, name, [], options, content,
- lineno, content_offset, block_text,
- state, state_machine)
- rv[0].insert(0, parse_signature(arguments[0]))
- return rv
-funcdesc_directive.content = 1
-funcdesc_directive.arguments = (1,0,1) # 1 required arg with spaces.
-
-def methoddesc_directive(name, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
- rv = admonitions.make_admonition(methoddesc, name, [], options, content,
- lineno, content_offset, block_text,
- state, state_machine)
- rv[0].insert(0, parse_signature(arguments[0]))
- return rv
-methoddesc_directive.content = 1
-methoddesc_directive.arguments = (1,0,1) # 1 required arg with spaces.
-
-def classdesc_directive(name, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
- rv = admonitions.make_admonition(classdesc, name, [], options, content,
- lineno, content_offset, block_text,
- state, state_machine)
- rv[0].insert(0, parse_signature(arguments[0]))
- return rv
-classdesc_directive.content = 1
-classdesc_directive.arguments = (1,0,1) # 1 required arg with spaces.
-
-def datadesc_directive(name, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
- rv = admonitions.make_admonition(datadesc, name, [], options, content,
- lineno, content_offset, block_text,
- state, state_machine)
- rv[0].insert(0, desc_name(arguments[0], arguments[0]))
- return rv
-datadesc_directive.content = 1
-datadesc_directive.arguments = (1,0,1) # 1 required arg with spaces.
-
-# Register the directives
-directives.register_directive('funcdesc', funcdesc_directive)
-directives.register_directive('methoddesc', methoddesc_directive)
-directives.register_directive('classdesc', classdesc_directive)
-directives.register_directive('datadesc', datadesc_directive)
-
-######################################################################
-# Writer
-######################################################################
-
-class DocpyWriter(latex2e.Writer):
- def translate(self):
- visitor = DocpyTranslator(self.document)
- self.document.walkabout(visitor)
- self.output = visitor.astext()
- self.head_prefix = visitor.head_prefix
- self.head = visitor.head
- self.body_prefix = visitor.body_prefix
- self.body = visitor.body
- self.body_suffix = visitor.body_suffix
-
-class DocpyTranslator(latex2e.LaTeXTranslator):
- """
- Incompatibilities:
- - latex docs uses \subsection, we generate \subsection*
- (e.g., \subsection{asyncore Exampe ...})
- - we generate header & footer info that we don't need
- - we don't handle RFC822 stuff & generate a top-level \section
- - in a function signature, we escape underscores but latex docs
- don't. e.g., funcdesc (line 46 of asyncore).
- - in role-labeled text, we escape underscore but latex docs
- don't.
- - table rendering is completely different.
- """
-
- def __init__(self, *args):
- latex2e.LaTeXTranslator.__init__(self, *args)
- self.section_level = 1
- self.first_paramter_visited = 0
-
-
- #------------------------------------------------------------
- # Directives
- #------------------------------------------------------------
- def visit_funcdesc(self, node):
- self.body.append('\n'+r'\begin{funcdesc}')
- def depart_funcdesc(self, node):
- self.body.append(r'\end{funcdesc}'+'\n')
-
- def visit_methoddesc(self, node):
- self.body.append('\n'+r'\begin{methoddesc}')
- def depart_methoddesc(self, node):
- self.body.append(r'\end{methoddesc}'+'\n')
-
- def visit_classdesc(self, node):
- self.body.append('\n'+r'\begin{classdesc}')
- def depart_classdesc(self, node):
- self.body.append(r'\end{classdesc}'+'\n')
-
- def visit_datadesc(self, node):
- self.body.append('\n'+r'\begin{datadesc}')
- def depart_datadesc(self, node):
- self.body.append(r'\end{datadesc}'+'\n')
-
- def visit_desc_name(self, node):
- self.body.append('{')
- def depart_desc_name(self, node):
- self.body.append('}')
-
- def visit_func_name(self, node):
- self.body.append('{')
- def depart_func_name(self, node):
- self.body.append('}')
-
- def visit_func_signature(self, node): pass
- def depart_func_signature(self, node): pass
-
- def visit_func_parameterlist(self, node):
- self.body.append('{')
- self.first_parameter_visited = 0
- def depart_func_parameterlist(self, node):
- self.body.append('}')
-
- def visit_func_parameter(self, node):
- if self.first_parameter_visited:
- self.body.append(', ')
- self.first_parameter_visited = 1
- def depart_func_parameter(self, node): pass
-
- def visit_func_optional(self, node):
- self.body.append(r'\optional{')
- if self.first_paramter_visited:
- self.body.append(',')
- def depart_func_optional(self, node):
- self.body.append('}')
-
-
- #------------------------------------------------------------
- # Inline Roles
- #------------------------------------------------------------
- def visit_docpy_function(self, node):
- self.body.append(r'\function{')
- def depart_docpy_function(self, node):
- self.body.append(r'}')
-
- def visit_docpy_manpage(self, node):
- self.body.append(r'\manpage{')
- def depart_docpy_manpage(self, node):
- self.body.append(r'}')
-
- def visit_docpy_regexp(self, node):
- self.body.append(r'\regexp{')
- def depart_docpy_regexp(self, node):
- self.body.append(r'}')
-
- def visit_docpy_file(self, node):
- self.body.append(r'\file{')
- def depart_docpy_file(self, node):
- self.body.append(r'}')
-
- def visit_docpy_label(self, node):
- self.body.append(r'\label{')
- def depart_docpy_label(self, node):
- self.body.append(r'}')
-
- def visit_docpy_class(self, node):
- self.body.append(r'\class{')
- def depart_docpy_class(self, node):
- self.body.append(r'}')
-
- def visit_docpy_method(self, node):
- self.body.append(r'\method{')
- def depart_docpy_method(self, node):
- self.body.append(r'}')
-
- def visit_docpy_cfunction(self, node):
- self.body.append(r'\cfunction{')
- def depart_docpy_cfunction(self, node):
- self.body.append(r'}')
-
- def visit_docpy_refmodule(self, node):
- self.body.append(r'\refmodule{')
- def depart_docpy_refmodule(self, node):
- self.body.append(r'}')
-
- def visit_docpy_module(self, node):
- self.body.append(r'\module{')
- def depart_docpy_module(self, node):
- self.body.append(r'}')
-
- def visit_docpy_var(self, node):
- self.body.append(r'\var{')
- def depart_docpy_var(self, node):
- self.body.append(r'}')
-
- #------------------------------------------------------------
- # Etc.
- #------------------------------------------------------------
- def visit_literal(self, node):
- self.literal = 1
- self.body.append('\\code{')
-
-######################################################################
-# Front-end code
-######################################################################
-if __name__ == '__main__':
- import locale
- try:
- locale.setlocale(locale.LC_ALL, '')
- except:
- pass
-
- from docutils.core import publish_cmdline, default_description
- description = default_description
- publish_cmdline(writer=DocpyWriter(), description=description)
- #publish_cmdline(writer_name='pseudoxml', description=description)
-
diff --git a/sandbox/edloper/epytext/epytext.py b/sandbox/edloper/epytext/epytext.py
deleted file mode 100644
index 52071542a..000000000
--- a/sandbox/edloper/epytext/epytext.py
+++ /dev/null
@@ -1,95 +0,0 @@
-
-import epydoc.markup.epytext as epytext
-import xml.dom.minidom as minidom
-import docutils.nodes as nodes
-import docutils.parsers
-
-class Parser(docutils.parsers.Parser):
- """The epytext parser."""
-
- supported = ('epytext')
-
- settings_spec = (
- 'epytext Parser Options',
- None,
- ())
-
- def __init__(self):
- pass
-
- def parse(self, inputstring, document):
- errors = []
- epytext_tree = epytext.parse(inputstring)
-
- self.setup_parse(inputstring, document)
- for child in self._parse(epytext_tree):
- self.document.append(child)
- self.finish_parse()
-
- def _parse(self, tree):
- if isinstance(tree, minidom.Document):
- return self._parse(tree.childNodes[0])
- if isinstance(tree, minidom.Text):
- return nodes.Text(tree.data)
-
- # Get children.
- children = [self._parse(c) for c in tree.childNodes]
-
- if tree.tagName == 'epytext':
- return children
- if tree.tagName == 'para':
- return nodes.paragraph('','', *children)
- if tree.tagName == 'section':
- return nodes.section('', *children)
- if tree.tagName == 'heading':
- return nodes.title('','', *children)
- if tree.tagName == 'fieldlist':
- return nodes.field_list('', *children)
- if tree.tagName == 'field':
- return nodes.field('', *self._parse_field(tree, children))
- if tree.tagName == 'literalblock':
- return nodes.literal_block('','', *children)
- if tree.tagName == 'doctestblock':
- return nodes.doctest_block('','', *children)
- if tree.tagName == 'ulist':
- return nodes.bullet_list('', *children)
- if tree.tagName == 'olist':
- return nodes.enumerated_list('', *children)
- if tree.tagName == 'li':
- return nodes.list_item('', *children)
- if tree.tagName == 'link':
- # [XX] discards link target.
- name, target = children
- return nodes.title_reference('','', name)
- if tree.tagName == 'uri':
- name, target = children
- return nodes.reference('','', name, refuid=target.astext())
- if tree.tagName == 'code':
- return nodes.literal('','', *children)
- if tree.tagName == 'math':
- return nodes.emphasis('','', *children)
- if tree.tagName == 'italic':
- return nodes.emphasis('','', *children)
- if tree.tagName == 'bold':
- return nodes.strong('','', *children)
- if tree.tagName == 'indexed':
- # [XX] doesn't mark the fact that it's indexedd
- return nodes.emphasis('','', *children)
- if tree.tagName == 'symbol':
- # use substitutions.
- # [XX] this needs to be fixed!
- return nodes.Text(children[0])
- elif tree.tagName in ('tag', 'arg', 'name', 'target'):
- return children[0]
- else:
- raise ValueError, ('unknown %s' % tree.tagName)
-
- def _parse_field(self, tree, children):
- numargs = 0
- while tree.childNodes[numargs+1].tagName == 'arg': numargs += 1
- tag = children[0]
- args = children[1:1+numargs]
- body = children[1+numargs:]
- name = nodes.field_name('','', *children[:1+numargs])
- body = nodes.field_body('', *children[1+numargs:])
- return (name, body)
diff --git a/sandbox/edloper/epytext/epytext.test b/sandbox/edloper/epytext/epytext.test
deleted file mode 100644
index 4d23f3dbd..000000000
--- a/sandbox/edloper/epytext/epytext.test
+++ /dev/null
@@ -1,26 +0,0 @@
-This is a
-paragraph.
-
-
-Heading
-=======
- A new section
- - A bulleted list
- 1. An enumerated list
-
- A B{paragraph} I{with} C{various} X{inline} M{formatting}
-
- Symbols are not supported yet: S{alpha}
-
- A literal block::
-
- A /
- / B
-
- A doctest block:
-
- >>> print 1
- 1
-
- @field: this is a field
- @field arg: this is another field.
diff --git a/sandbox/edloper/epytext/publish_epytext.py b/sandbox/edloper/epytext/publish_epytext.py
deleted file mode 100755
index ce4b7fa40..000000000
--- a/sandbox/edloper/epytext/publish_epytext.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env python
-
-# Author: David Goodger
-# Contact: goodger@users.sourceforge.net
-# Revision: $Revision$
-# Date: $Date$
-# Copyright: This module has been placed in the public domain.
-
-"""
-A minimal front end to the Docutils Publisher, producing pseudo-XML.
-"""
-
-import epytext
-import locale
-try:
- locale.setlocale(locale.LC_ALL, '')
-except:
- pass
-
-from docutils.core import publish_cmdline, default_description
-
-
-description = ('Generates pseudo-XML from standalone reStructuredText '
- 'sources (for testing purposes). ' + default_description)
-
-publish_cmdline(parser=epytext.Parser(), description=description)
diff --git a/sandbox/edloper/roles/README.txt b/sandbox/edloper/roles/README.txt
deleted file mode 100644
index 79fdb6cd3..000000000
--- a/sandbox/edloper/roles/README.txt
+++ /dev/null
@@ -1,83 +0,0 @@
-==========================================
-reStructuredText Interpreted Role Registry
-==========================================
-
-This directory contains my implementation of an extensible interpreted
-role registry, based largely on the existing directive registry. It
-contains two modules: `states.py` and `roles.py`. Both are intended
-to go in `docutils/docutils/parsers/rst/`. (`roles.py` is a new
-module; `states.py` is a replacement module).
-
-Defining Roles
-~~~~~~~~~~~~~~
-Interpreted roles are implemented as functions with the following
-signature::
-
- def role_fn(name, rawtext, text, lineno, inliner):
- """
- Given an interpreted text construct, return a tuple of two lists:
- document tree nodes, and system messages (may be empty).
-
- :Parameters:
- - `name`: The canonical name of the interpreted role.
- - `rawtext`: A string containing the enitre interpreted
- text input (including the role). This should be
- included as the content of a system message if a
- problem is encountered.
- - `text`: The interpreted text content.
- - `lineno`: The line number where the interpreted text begins.
- - `inliner`: The inliner object that called role_fn. This
- can be used for error reporting & nested parsing.
- """
-
-See the module docstring in roles.py for more information.
-
-Registering Roles
-~~~~~~~~~~~~~~~~~
-To register a new role, use docutils.parsers.rst.roles.register_role().
-Roles should be registered using canonial names; non-canonical names
-can be specified via the language submodule.
-
-Default Role
-~~~~~~~~~~~~
-The default interpreted role is defined by the DEFAULT_INTERPRETED_ROLE
-variable in docutils.parsers.rst.roles. It should contain a canonical
-role name.
-
-Differences from Directive System
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-There are some design differences between the roles system and the
-directives system:
-
-- All roles are registered via register_role(); there is no
- equivalant to _directive_registry, which contains a centralized
- list of directives to register.
-
-- Instead, each directive is registered immediately following
- its definition.
-
-- All standard roles are registered at import time; there is no
- mechanism for delaying imports until roles are actually used.
-
-Changes to states.py
-~~~~~~~~~~~~~~~~~~~~
-A diff on the old states.py (revision 1.69) and my revised version will
-show that I mostly removed code (the old role handling system) from
-inside the Inliner class. The only new code in states.py is the new
-Inliner.interpreted(), and some simplifying changes to the part of
-Inliner.interpreted_or_phrase_ref() that calls Inliner.interpreted().
-
-Future Work
-~~~~~~~~~~~
-- It would be nice to add a directive to create new roles, based on
- existing ones. Given that roles are implemented as functions,
- this would probably involve creating a wrapper function that:
-
- - calls the base role_fn function
- - modifies its output
- - returns the modified output
-
- It seems difficult to do this in a general way, though, since
- role functions are free to return any number of nodes, of any
- type. So we might need to limit the extension mechanism to
- specific base role_fn's.
diff --git a/sandbox/edloper/roles/roles.py b/sandbox/edloper/roles/roles.py
deleted file mode 100644
index 32f697333..000000000
--- a/sandbox/edloper/roles/roles.py
+++ /dev/null
@@ -1,296 +0,0 @@
-# Author: Edward Loper
-# Contact: edloper@gradient.cis.upenn.edu
-# Revision: $Revision$
-# Date: $Date$
-# Copyright: This module has been placed in the public domain.
-
-"""
-This module defines standard interpreted text role functions, a registry for
-interpreted text roles, and an API for adding to and retrieving from the
-registry.
-
-The interface for interpreted role functions is as follows::
-
- def role_fn(name, rawtext, text, lineno, inliner, attributes={}):
- code...
-
-Parameters:
-
-- ``name`` is the local name of the interpreted text role, the role name
- actually used in the document.
-
-- ``rawtext`` is a string containing the entire interpreted text construct.
- Include it as a literal block in a system message if there is a problem.
-
-- ``text`` is the interpreted text content.
-
-- ``lineno`` is the line number where the interpreted text beings.
-
-- ``inliner`` is the Inliner object that called the role function.
- It defines the following useful attributes: ``reporter``,
- ``problematic``, ``memo``, ``parent``, ``document``.
-
-- ``attributes``: A dictionary of additional attributes for the generated
- elements, used for customization.
-
-Interpreted role functions return a tuple of two values:
-
-- A list of nodes which will be inserted into the document tree at the
- point where the interpreted role was encountered (can be an empty
- list).
-
-- A list of system messages, which will be inserted into the document tree
- immediately after the end of the current inline block (can also be empty).
-"""
-
-__docformat__ = 'reStructuredText'
-
-import re
-from docutils import nodes
-from docutils.parsers.rst.languages import en as _fallback_language_module
-
-DEFAULT_INTERPRETED_ROLE = 'title-reference'
-"""
-The canonical name of the default interpreted role. This role is used
-when no role is specified for a piece of interpreted text.
-"""
-
-_role_registry = {}
-"""Mapping of canonical role names to role functions. Language-dependent role
-names are defined in the ``language`` subpackage."""
-
-_roles = {}
-"""Mapping of local or language-dependent interpreted text role names to role
-functions."""
-
-def role(role_name, language_module, lineno, inliner):
- """
- Locate and return a role function from its language-dependent name, along
- with a list of system messages. If the role is not found in the current
- language, check English. Return None if the named role cannot be found.
- """
- normname = role_name.lower()
- messages = []
- msg_text = []
-
- if _roles.has_key(normname):
- return _roles[normname], messages
-
- if role_name:
- canonicalname = None
- try:
- canonicalname = language_module.roles[normname]
- except AttributeError, error:
- msg_text.append('Problem retrieving role entry from language '
- 'module %r: %s.' % (language_module, error))
- except KeyError:
- msg_text.append('No role entry for "%s" in module "%s".'
- % (role_name, language_module.__name__))
- else:
- canonicalname = DEFAULT_INTERPRETED_ROLE
-
- # If we didn't find it, try English as a fallback.
- if not canonicalname:
- try:
- canonicalname = _fallback_language_module.roles[normname]
- msg_text.append('Using English fallback for role "%s".'
- % role_name)
- except KeyError:
- msg_text.append('Trying "%s" as canonical role name.'
- % role_name)
- # The canonical name should be an English name, but just in case:
- canonicalname = normname
-
- # Collect any messages that we generated.
- if msg_text:
- message = inliner.reporter.info('\n'.join(msg_text), line=lineno)
- messages.append(message)
-
- # Look the role up in the registry, and return it.
- if _role_registry.has_key(canonicalname):
- role_fn = _role_registry[canonicalname]
- register_local_role(normname, role_fn)
- return role_fn, messages
- else:
- return None, messages # Error message will be generated by caller.
-
-def register_canonical_role(name, role_fn):
- """
- Register an interpreted text role by its canonical name.
-
- :Parameters:
- - `name`: The canonical name of the interpreted role.
- - `role_fn`: The role function. See the module docstring.
- """
- _role_registry[name] = role_fn
-
-def register_local_role(name, role_fn):
- """
- Register an interpreted text role by its local or language-dependent name.
-
- :Parameters:
- - `name`: The local or language-dependent name of the interpreted role.
- - `role_fn`: The role function. See the module docstring.
- """
- _roles[name] = role_fn
-
-def register_generic_role(canonical_name, node_class):
- """For roles which simply wrap a given `node_class` around the text."""
- # Dynamically define a role function:
- def role_fn(role, rawtext, text, lineno, inliner, nc=node_class):
- return generic_role_helper(nc, role, rawtext, text, lineno, inliner)
- # Register the role:
- register_canonical_role(canonical_name, role_fn)
-
-def generic_role_helper(node_class, role, rawtext, text, lineno, inliner,
- attributes={}):
- # If we wanted to, we could recursively call inliner.nested_parse
- # to interpret the text contents here (after appropriately
- # refactoring Inliner.parse).
- return [node_class(rawtext, text, **attributes)], []
-
-def register_custom_role(local_name, attributes):
- """For roles defined in a document."""
- def role_fn(role, rawtext, text, lineno, inliner, atts=attributes):
- return generic_role_helper(
- nodes.inline, role, rawtext, text, lineno, inliner, attributes=atts)
- register_local_role(local_name, role_fn)
-
-######################################################################
-# Define and register the standard roles:
-######################################################################
-
-register_generic_role('abbreviation', nodes.abbreviation)
-register_generic_role('acronym', nodes.acronym)
-register_generic_role('emphasis', nodes.emphasis)
-register_generic_role('literal', nodes.literal)
-register_generic_role('strong', nodes.strong)
-register_generic_role('subscript', nodes.subscript)
-register_generic_role('superscript', nodes.superscript)
-register_generic_role('title-reference', nodes.title_reference)
-
-class URIReferenceRole:
- """
- An Abstract base class for specialized URI reference roles. A URI
- reference role is an interpreted text role that creates a single
- referencenode, whose `label` and `refuri` are based on the
- contents of the interpreted text.
-
- Each subclass should override `label` and `uri`, which map the
- intepreted text into a label and a uri, respectively. If the
- subclass needs to check the validity of the interpreted text, then
- it should also override `verify`, which raises a ValueError if the
- interpreted text is badly formatted.
- """
- def __call__(self, role, rawtext, text, lineno, inliner):
- try:
- self.verify(text)
- except ValueError, e:
- msg = inliner.reporter.error(str(e), line=lineno)
- prb = inliner.problematic(text, text, msg)
- return [prb], [msg]
- label = self.label(text)
- uri = self.uri(text)
- return [nodes.reference(rawtext, label, refuri=uri)], []
-
- def verify(self, text):
- """
- Check that `text` contains valid interpreted text for this uri
- reference role. If `text` is invalid, then raise a ValueError
- whose text describes the problem. If the text is valid, then
- return.
- """
- return
-
- def label(self, text):
- """Return the reference label corresponding to `text`"""
- raise NotImplementedError
-
- def uri(self, text):
- """Return the reference target corresponding to `text`"""
- raise NotImplementedError
-
-class PEPReferenceRole(URIReferenceRole):
- """An interpreted text role for referencing PEPs."""
- def __init__(self, base_url):
- """
- :Parameters:
- - `base_url`: The URL of the directory where PEPs are
- stored.
- """
- if base_url == '' or base_url.endswith('/'):
- self.pep_url = base_url+'pep-%04d.html'
- else:
- self.pep_url = base_url+'/pep-%04d.html'
-
- _PEP_RE = re.compile(r'\d{1,4}')
- def verify(self, text):
- if not self._PEP_RE.match(text):
- raise ValueError('PEP number must be a number from 0 '+
- 'to 9999; "%s" is invalid.' % text)
-
- def label(self, text):
- return 'PEP %s' % text
-
- def uri(self, text):
- return self.pep_url % int(text)
-
-# Define both remote & local versions, to make it easier for the
-# user to swap the other in (with register_canonical_role). Use
-# the remote version by default.
-pep_reference_role = PEPReferenceRole('http://www.python.org/peps/')
-local_pep_reference_role = PEPReferenceRole('')
-register_canonical_role('pep-reference', pep_reference_role)
-
-class RFCReferenceRole(URIReferenceRole):
- """An interpreted text role for referencing RFCs."""
- def __init__(self, base_url):
- """
- :Parameters:
- - `base_url`: The URL of the directory where RFCs are
- stored.
- """
- if base_url == '' or base_url.endswith('/'):
- self.rfc_url = base_url+'rfc%d.html'
- else:
- self.rfc_url = base_url+'/rfc%d.html'
-
- _RFC_RE = re.compile(r'[1-9]\d*')
- def verify(self, text):
- if not self._RFC_RE.match(text):
- raise ValueError('RFC number must be a number greater than '
- 'or equal to 1; "%s" is invalid.' % text)
-
- def label(self, text):
- return 'RFC %s' % text
-
- def uri(self, text):
- return self.rfc_url % int(text)
-
-# Define both remote & local versions, to make it easier for the
-# user to swap the other in (with register_canonical_role). Use
-# the remote version by default.
-rfc_reference_role = RFCReferenceRole('http://www.faqs.org/rfcs/')
-local_rfc_reference_role = RFCReferenceRole('')
-register_canonical_role('rfc-reference', rfc_reference_role)
-
-######################################################################
-# Register roles that are currently unimplemented.
-######################################################################
-
-def unimplemented_role(role, rawtext, text, lineno, inliner):
- msg = inliner.reporter.error(
- 'Interpreted text role "%s" not implemented.' % role, line=lineno)
- prb = inliner.problematic(rawtext, rawtext, msg)
- return [prb], [msg]
-
-register_canonical_role('index', unimplemented_role)
-register_canonical_role('named-reference', unimplemented_role)
-register_canonical_role('anonymous-reference', unimplemented_role)
-register_canonical_role('uri-reference', unimplemented_role)
-register_canonical_role('footnote-reference', unimplemented_role)
-register_canonical_role('citation-reference', unimplemented_role)
-register_canonical_role('substitution-reference', unimplemented_role)
-register_canonical_role('target', unimplemented_role)
-# This one should remain unimplemented, for testing purposes:
-register_canonical_role('restructuredtext-unimplemented-role', unimplemented_role)
diff --git a/sandbox/edloper/roles/states.py b/sandbox/edloper/roles/states.py
deleted file mode 100644
index 80512f4ca..000000000
--- a/sandbox/edloper/roles/states.py
+++ /dev/null
@@ -1,2840 +0,0 @@
-# Author: David Goodger
-# Contact: goodger@users.sourceforge.net
-# Revision: $Revision$
-# Date: $Date$
-# Copyright: This module has been placed in the public domain.
-
-"""
-This is the ``docutils.parsers.restructuredtext.states`` module, the core of
-the reStructuredText parser. It defines the following:
-
-:Classes:
- - `RSTStateMachine`: reStructuredText parser's entry point.
- - `NestedStateMachine`: recursive StateMachine.
- - `RSTState`: reStructuredText State superclass.
- - `Inliner`: For parsing inline markup.
- - `Body`: Generic classifier of the first line of a block.
- - `SpecializedBody`: Superclass for compound element members.
- - `BulletList`: Second and subsequent bullet_list list_items
- - `DefinitionList`: Second+ definition_list_items.
- - `EnumeratedList`: Second+ enumerated_list list_items.
- - `FieldList`: Second+ fields.
- - `OptionList`: Second+ option_list_items.
- - `RFC2822List`: Second+ RFC2822-style fields.
- - `ExtensionOptions`: Parses directive option fields.
- - `Explicit`: Second+ explicit markup constructs.
- - `SubstitutionDef`: For embedded directives in substitution definitions.
- - `Text`: Classifier of second line of a text block.
- - `SpecializedText`: Superclass for continuation lines of Text-variants.
- - `Definition`: Second line of potential definition_list_item.
- - `Line`: Second line of overlined section title or transition marker.
- - `Struct`: An auxiliary collection class.
-
-:Exception classes:
- - `MarkupError`
- - `ParserError`
- - `MarkupMismatch`
-
-:Functions:
- - `escape2null()`: Return a string, escape-backslashes converted to nulls.
- - `unescape()`: Return a string, nulls removed or restored to backslashes.
-
-:Attributes:
- - `state_classes`: set of State classes used with `RSTStateMachine`.
-
-Parser Overview
-===============
-
-The reStructuredText parser is implemented as a recursive state machine,
-examining its input one line at a time. To understand how the parser works,
-please first become familiar with the `docutils.statemachine` module. In the
-description below, references are made to classes defined in this module;
-please see the individual classes for details.
-
-Parsing proceeds as follows:
-
-1. The state machine examines each line of input, checking each of the
- transition patterns of the state `Body`, in order, looking for a match.
- The implicit transitions (blank lines and indentation) are checked before
- any others. The 'text' transition is a catch-all (matches anything).
-
-2. The method associated with the matched transition pattern is called.
-
- A. Some transition methods are self-contained, appending elements to the
- document tree (`Body.doctest` parses a doctest block). The parser's
- current line index is advanced to the end of the element, and parsing
- continues with step 1.
-
- B. Other transition methods trigger the creation of a nested state machine,
- whose job is to parse a compound construct ('indent' does a block quote,
- 'bullet' does a bullet list, 'overline' does a section [first checking
- for a valid section header], etc.).
-
- - In the case of lists and explicit markup, a one-off state machine is
- created and run to parse contents of the first item.
-
- - A new state machine is created and its initial state is set to the
- appropriate specialized state (`BulletList` in the case of the
- 'bullet' transition; see `SpecializedBody` for more detail). This
- state machine is run to parse the compound element (or series of
- explicit markup elements), and returns as soon as a non-member element
- is encountered. For example, the `BulletList` state machine ends as
- soon as it encounters an element which is not a list item of that
- bullet list. The optional omission of inter-element blank lines is
- enabled by this nested state machine.
-
- - The current line index is advanced to the end of the elements parsed,
- and parsing continues with step 1.
-
- C. The result of the 'text' transition depends on the next line of text.
- The current state is changed to `Text`, under which the second line is
- examined. If the second line is:
-
- - Indented: The element is a definition list item, and parsing proceeds
- similarly to step 2.B, using the `DefinitionList` state.
-
- - A line of uniform punctuation characters: The element is a section
- header; again, parsing proceeds as in step 2.B, and `Body` is still
- used.
-
- - Anything else: The element is a paragraph, which is examined for
- inline markup and appended to the parent element. Processing
- continues with step 1.
-"""
-
-__docformat__ = 'reStructuredText'
-
-
-import sys
-import re
-import roman
-from types import TupleType
-from docutils import nodes, statemachine, utils, urischemes
-from docutils import ApplicationError, DataError
-from docutils.statemachine import StateMachineWS, StateWS
-from docutils.nodes import fully_normalize_name as normalize_name
-from docutils.nodes import whitespace_normalize_name
-from docutils.parsers.rst import directives, languages, tableparser, roles
-from docutils.parsers.rst.languages import en as _fallback_language_module
-
-
-class MarkupError(DataError): pass
-class UnknownInterpretedRoleError(DataError): pass
-class InterpretedRoleNotImplementedError(DataError): pass
-class ParserError(ApplicationError): pass
-class MarkupMismatch(Exception): pass
-
-
-class Struct:
-
- """Stores data attributes for dotted-attribute access."""
-
- def __init__(self, **keywordargs):
- self.__dict__.update(keywordargs)
-
-
-class RSTStateMachine(StateMachineWS):
-
- """
- reStructuredText's master StateMachine.
-
- The entry point to reStructuredText parsing is the `run()` method.
- """
-
- def run(self, input_lines, document, input_offset=0, match_titles=1,
- inliner=None):
- """
- Parse `input_lines` and return a `docutils.nodes.document` instance.
-
- Extend `StateMachineWS.run()`: set up parse-global data, run the
- StateMachine, and return the resulting
- document.
- """
- self.language = languages.get_language(
- document.settings.language_code)
- self.match_titles = match_titles
- if inliner is None:
- inliner = Inliner()
- inliner.init_customizations(document.settings)
- self.memo = Struct(document=document,
- reporter=document.reporter,
- language=self.language,
- title_styles=[],
- section_level=0,
- section_bubble_up_kludge=0,
- inliner=inliner)
- self.document = document
- self.attach_observer(document.note_source)
- self.reporter = self.memo.reporter
- self.node = document
- results = StateMachineWS.run(self, input_lines, input_offset,
- input_source=document['source'])
- assert results == [], 'RSTStateMachine.run() results should be empty!'
- self.check_document()
- self.node = self.memo = None # remove unneeded references
-
- def check_document(self):
- """Check for illegal structure: empty document."""
- if len(self.document) == 0:
- error = self.reporter.error(
- 'Document empty; must have contents.', line=0)
- self.document += error
-
-
-class NestedStateMachine(StateMachineWS):
-
- """
- StateMachine run from within other StateMachine runs, to parse nested
- document structures.
- """
-
- def run(self, input_lines, input_offset, memo, node, match_titles=1):
- """
- Parse `input_lines` and populate a `docutils.nodes.document` instance.
-
- Extend `StateMachineWS.run()`: set up document-wide data.
- """
- self.match_titles = match_titles
- self.memo = memo
- self.document = memo.document
- self.attach_observer(self.document.note_source)
- self.reporter = memo.reporter
- self.node = node
- results = StateMachineWS.run(self, input_lines, input_offset)
- assert results == [], ('NestedStateMachine.run() results should be '
- 'empty!')
- return results
-
-
-class RSTState(StateWS):
-
- """
- reStructuredText State superclass.
-
- Contains methods used by all State subclasses.
- """
-
- nested_sm = NestedStateMachine
-
- def __init__(self, state_machine, debug=0):
- self.nested_sm_kwargs = {'state_classes': state_classes,
- 'initial_state': 'Body'}
- StateWS.__init__(self, state_machine, debug)
-
- def runtime_init(self):
- StateWS.runtime_init(self)
- memo = self.state_machine.memo
- self.memo = memo
- self.reporter = memo.reporter
- self.inliner = memo.inliner
- self.document = memo.document
- self.parent = self.state_machine.node
-
- def goto_line(self, abs_line_offset):
- """
- Jump to input line `abs_line_offset`, ignoring jumps past the end.
- """
- try:
- self.state_machine.goto_line(abs_line_offset)
- except EOFError:
- pass
-
- def no_match(self, context, transitions):
- """
- Override `StateWS.no_match` to generate a system message.
-
- This code should never be run.
- """
- self.reporter.severe(
- 'Internal error: no transition pattern match. State: "%s"; '
- 'transitions: %s; context: %s; current line: %r.'
- % (self.__class__.__name__, transitions, context,
- self.state_machine.line),
- line=self.state_machine.abs_line_number())
- return context, None, []
-
- def bof(self, context):
- """Called at beginning of file."""
- return [], []
-
- def nested_parse(self, block, input_offset, node, match_titles=0,
- state_machine_class=None, state_machine_kwargs=None):
- """
- Create a new StateMachine rooted at `node` and run it over the input
- `block`.
- """
- if state_machine_class is None:
- state_machine_class = self.nested_sm
- if state_machine_kwargs is None:
- state_machine_kwargs = self.nested_sm_kwargs
- block_length = len(block)
- state_machine = state_machine_class(debug=self.debug,
- **state_machine_kwargs)
- state_machine.run(block, input_offset, memo=self.memo,
- node=node, match_titles=match_titles)
- state_machine.unlink()
- new_offset = state_machine.abs_line_offset()
- # No `block.parent` implies disconnected -- lines aren't in sync:
- if block.parent and (len(block) - block_length) != 0:
- # Adjustment for block if modified in nested parse:
- self.state_machine.next_line(len(block) - block_length)
- return new_offset
-
- def nested_list_parse(self, block, input_offset, node, initial_state,
- blank_finish,
- blank_finish_state=None,
- extra_settings={},
- match_titles=0,
- state_machine_class=None,
- state_machine_kwargs=None):
- """
- Create a new StateMachine rooted at `node` and run it over the input
- `block`. Also keep track of optional intermdediate blank lines and the
- required final one.
- """
- if state_machine_class is None:
- state_machine_class = self.nested_sm
- if state_machine_kwargs is None:
- state_machine_kwargs = self.nested_sm_kwargs.copy()
- state_machine_kwargs['initial_state'] = initial_state
- state_machine = state_machine_class(debug=self.debug,
- **state_machine_kwargs)
- if blank_finish_state is None:
- blank_finish_state = initial_state
- state_machine.states[blank_finish_state].blank_finish = blank_finish
- for key, value in extra_settings.items():
- setattr(state_machine.states[initial_state], key, value)
- state_machine.run(block, input_offset, memo=self.memo,
- node=node, match_titles=match_titles)
- blank_finish = state_machine.states[blank_finish_state].blank_finish
- state_machine.unlink()
- return state_machine.abs_line_offset(), blank_finish
-
- def section(self, title, source, style, lineno, messages):
- """Check for a valid subsection and create one if it checks out."""
- if self.check_subsection(source, style, lineno):
- self.new_subsection(title, lineno, messages)
-
- def check_subsection(self, source, style, lineno):
- """
- Check for a valid subsection header. Return 1 (true) or None (false).
-
- When a new section is reached that isn't a subsection of the current
- section, back up the line count (use ``previous_line(-x)``), then
- ``raise EOFError``. The current StateMachine will finish, then the
- calling StateMachine can re-examine the title. This will work its way
- back up the calling chain until the correct section level isreached.
-
- @@@ Alternative: Evaluate the title, store the title info & level, and
- back up the chain until that level is reached. Store in memo? Or
- return in results?
-
- :Exception: `EOFError` when a sibling or supersection encountered.
- """
- memo = self.memo
- title_styles = memo.title_styles
- mylevel = memo.section_level
- try: # check for existing title style
- level = title_styles.index(style) + 1
- except ValueError: # new title style
- if len(title_styles) == memo.section_level: # new subsection
- title_styles.append(style)
- return 1
- else: # not at lowest level
- self.parent += self.title_inconsistent(source, lineno)
- return None
- if level <= mylevel: # sibling or supersection
- memo.section_level = level # bubble up to parent section
- if len(style) == 2:
- memo.section_bubble_up_kludge = 1
- # back up 2 lines for underline title, 3 for overline title
- self.state_machine.previous_line(len(style) + 1)
- raise EOFError # let parent section re-evaluate
- if level == mylevel + 1: # immediate subsection
- return 1
- else: # invalid subsection
- self.parent += self.title_inconsistent(source, lineno)
- return None
-
- def title_inconsistent(self, sourcetext, lineno):
- error = self.reporter.severe(
- 'Title level inconsistent:', nodes.literal_block('', sourcetext),
- line=lineno)
- return error
-
- def new_subsection(self, title, lineno, messages):
- """Append new subsection to document tree. On return, check level."""
- memo = self.memo
- mylevel = memo.section_level
- memo.section_level += 1
- section_node = nodes.section()
- self.parent += section_node
- textnodes, title_messages = self.inline_text(title, lineno)
- titlenode = nodes.title(title, '', *textnodes)
- name = normalize_name(titlenode.astext())
- section_node['name'] = name
- section_node += titlenode
- section_node += messages
- section_node += title_messages
- self.document.note_implicit_target(section_node, section_node)
- offset = self.state_machine.line_offset + 1
- absoffset = self.state_machine.abs_line_offset() + 1
- newabsoffset = self.nested_parse(
- self.state_machine.input_lines[offset:], input_offset=absoffset,
- node=section_node, match_titles=1)
- self.goto_line(newabsoffset)
- self.check_section(section_node)
- if memo.section_level <= mylevel: # can't handle next section?
- raise EOFError # bubble up to supersection
- # reset section_level; next pass will detect it properly
- memo.section_level = mylevel
-
- def check_section(self, section):
- """
- Check for illegal structure: empty section, misplaced transitions.
- """
- lineno = section.line
- if len(section) <= 1:
- error = self.reporter.error(
- 'Section empty; must have contents.', line=lineno)
- section += error
- return
- if not isinstance(section[0], nodes.title): # shouldn't ever happen
- error = self.reporter.error(
- 'First element of section must be a title.', line=lineno)
- section.insert(0, error)
- if isinstance(section[1], nodes.transition):
- error = self.reporter.error(
- 'Section may not begin with a transition.',
- line=section[1].line)
- section.insert(1, error)
- if len(section) > 2 and isinstance(section[-1], nodes.transition):
- error = self.reporter.error(
- 'Section may not end with a transition.',
- line=section[-1].line)
- section += error
-
- def paragraph(self, lines, lineno):
- """
- Return a list (paragraph & messages) & a boolean: literal_block next?
- """
- data = '\n'.join(lines).rstrip()
- if data[-2:] == '::':
- if len(data) == 2:
- return [], 1
- elif data[-3] in ' \n':
- text = data[:-3].rstrip()
- else:
- text = data[:-1]
- literalnext = 1
- else:
- text = data
- literalnext = 0
- textnodes, messages = self.inline_text(text, lineno)
- p = nodes.paragraph(data, '', *textnodes)
- p.line = lineno
- return [p] + messages, literalnext
-
- def inline_text(self, text, lineno):
- """
- Return 2 lists: nodes (text and inline elements), and system_messages.
- """
- return self.inliner.parse(text, lineno, self.memo, self.parent)
-
- def unindent_warning(self, node_name):
- return self.reporter.warning(
- '%s ends without a blank line; unexpected unindent.' % node_name,
- line=(self.state_machine.abs_line_number() + 1))
-
-
-def build_regexp(definition, compile=1):
- """
- Build, compile and return a regular expression based on `definition`.
-
- :Parameter: `definition`: a 4-tuple (group name, prefix, suffix, parts),
- where "parts" is a list of regular expressions and/or regular
- expression definitions to be joined into an or-group.
- """
- name, prefix, suffix, parts = definition
- part_strings = []
- for part in parts:
- if type(part) is TupleType:
- part_strings.append(build_regexp(part, None))
- else:
- part_strings.append(part)
- or_group = '|'.join(part_strings)
- regexp = '%(prefix)s(?P<%(name)s>%(or_group)s)%(suffix)s' % locals()
- if compile:
- return re.compile(regexp, re.UNICODE)
- else:
- return regexp
-
-
-class Inliner:
-
- """
- Parse inline markup; call the `parse()` method.
- """
-
- def __init__(self, roles=None):
- """
- `roles` is a mapping of canonical role name to role function or bound
- method, which enables additional interpreted text roles.
- """
-
- self.implicit_dispatch = [(self.patterns.uri, self.standalone_uri),]
- """List of (pattern, bound method) tuples, used by
- `self.implicit_inline`."""
-
- def init_customizations(self, settings):
- """Setting-based customizations; run when parsing begins."""
- if settings.pep_references:
- self.implicit_dispatch.append((self.patterns.pep,
- self.pep_reference))
- if settings.rfc_references:
- self.implicit_dispatch.append((self.patterns.rfc,
- self.rfc_reference))
-
- def parse(self, text, lineno, memo, parent):
- """
- Return 2 lists: nodes (text and inline elements), and system_messages.
-
- Using `self.patterns.initial`, a pattern which matches start-strings
- (emphasis, strong, interpreted, phrase reference, literal,
- substitution reference, and inline target) and complete constructs
- (simple reference, footnote reference), search for a candidate. When
- one is found, check for validity (e.g., not a quoted '*' character).
- If valid, search for the corresponding end string if applicable, and
- check it for validity. If not found or invalid, generate a warning
- and ignore the start-string. Implicit inline markup (e.g. standalone
- URIs) is found last.
- """
- self.reporter = memo.reporter
- self.document = memo.document
- self.language = memo.language
- self.parent = parent
- pattern_search = self.patterns.initial.search
- dispatch = self.dispatch
- remaining = escape2null(text)
- processed = []
- unprocessed = []
- messages = []
- while remaining:
- match = pattern_search(remaining)
- if match:
- groups = match.groupdict()
- method = dispatch[groups['start'] or groups['backquote']
- or groups['refend'] or groups['fnend']]
- before, inlines, remaining, sysmessages = method(self, match,
- lineno)
- unprocessed.append(before)
- messages += sysmessages
- if inlines:
- processed += self.implicit_inline(''.join(unprocessed),
- lineno)
- processed += inlines
- unprocessed = []
- else:
- break
- remaining = ''.join(unprocessed) + remaining
- if remaining:
- processed += self.implicit_inline(remaining, lineno)
- return processed, messages
-
- openers = '\'"([{<'
- closers = '\'")]}>'
- start_string_prefix = (r'((?<=^)|(?<=[-/: \n%s]))' % re.escape(openers))
- end_string_suffix = (r'((?=$)|(?=[-/:.,;!? \n\x00%s]))'
- % re.escape(closers))
- non_whitespace_before = r'(?<![ \n])'
- non_whitespace_escape_before = r'(?<![ \n\x00])'
- non_whitespace_after = r'(?![ \n])'
- # Alphanumerics with isolated internal [-._] chars (i.e. not 2 together):
- simplename = r'(?:(?!_)\w)+(?:[-._](?:(?!_)\w)+)*'
- # Valid URI characters (see RFC 2396 & RFC 2732);
- # final \x00 allows backslash escapes in URIs:
- uric = r"""[-_.!~*'()[\];/:@&=+$,%a-zA-Z0-9\x00]"""
- # Last URI character; same as uric but no punctuation:
- urilast = r"""[_~/a-zA-Z0-9]"""
- emailc = r"""[-_!~*'{|}/#?^`&=+$%a-zA-Z0-9\x00]"""
- email_pattern = r"""
- %(emailc)s+(?:\.%(emailc)s+)* # name
- @ # at
- %(emailc)s+(?:\.%(emailc)s*)* # host
- %(urilast)s # final URI char
- """
- parts = ('initial_inline', start_string_prefix, '',
- [('start', '', non_whitespace_after, # simple start-strings
- [r'\*\*', # strong
- r'\*(?!\*)', # emphasis but not strong
- r'``', # literal
- r'_`', # inline internal target
- r'\|(?!\|)'] # substitution reference
- ),
- ('whole', '', end_string_suffix, # whole constructs
- [# reference name & end-string
- r'(?P<refname>%s)(?P<refend>__?)' % simplename,
- ('footnotelabel', r'\[', r'(?P<fnend>\]_)',
- [r'[0-9]+', # manually numbered
- r'\#(%s)?' % simplename, # auto-numbered (w/ label?)
- r'\*', # auto-symbol
- r'(?P<citationlabel>%s)' % simplename] # citation reference
- )
- ]
- ),
- ('backquote', # interpreted text or phrase reference
- '(?P<role>(:%s:)?)' % simplename, # optional role
- non_whitespace_after,
- ['`(?!`)'] # but not literal
- )
- ]
- )
- patterns = Struct(
- initial=build_regexp(parts),
- emphasis=re.compile(non_whitespace_escape_before
- + r'(\*)' + end_string_suffix),
- strong=re.compile(non_whitespace_escape_before
- + r'(\*\*)' + end_string_suffix),
- interpreted_or_phrase_ref=re.compile(
- r"""
- %(non_whitespace_escape_before)s
- (
- `
- (?P<suffix>
- (?P<role>:%(simplename)s:)?
- (?P<refend>__?)?
- )
- )
- %(end_string_suffix)s
- """ % locals(), re.VERBOSE | re.UNICODE),
- embedded_uri=re.compile(
- r"""
- (
- (?:[ \n]+|^) # spaces or beginning of line/string
- < # open bracket
- %(non_whitespace_after)s
- ([^<>\x00]+) # anything but angle brackets & nulls
- %(non_whitespace_before)s
- > # close bracket w/o whitespace before
- )
- $ # end of string
- """ % locals(), re.VERBOSE),
- literal=re.compile(non_whitespace_before + '(``)'
- + end_string_suffix),
- target=re.compile(non_whitespace_escape_before
- + r'(`)' + end_string_suffix),
- substitution_ref=re.compile(non_whitespace_escape_before
- + r'(\|_{0,2})'
- + end_string_suffix),
- email=re.compile(email_pattern % locals() + '$', re.VERBOSE),
- uri=re.compile(
- (r"""
- %(start_string_prefix)s
- (?P<whole>
- (?P<absolute> # absolute URI
- (?P<scheme> # scheme (http, ftp, mailto)
- [a-zA-Z][a-zA-Z0-9.+-]*
- )
- :
- (
- ( # either:
- (//?)? # hierarchical URI
- %(uric)s* # URI characters
- %(urilast)s # final URI char
- )
- ( # optional query
- \?%(uric)s*
- %(urilast)s
- )?
- ( # optional fragment
- \#%(uric)s*
- %(urilast)s
- )?
- )
- )
- | # *OR*
- (?P<email> # email address
- """ + email_pattern + r"""
- )
- )
- %(end_string_suffix)s
- """) % locals(), re.VERBOSE),
- pep=re.compile(
- r"""
- %(start_string_prefix)s
- (
- (pep-(?P<pepnum1>\d+)(.txt)?) # reference to source file
- |
- (PEP\s+(?P<pepnum2>\d+)) # reference by name
- )
- %(end_string_suffix)s""" % locals(), re.VERBOSE),
- rfc=re.compile(
- r"""
- %(start_string_prefix)s
- (RFC(-|\s+)?(?P<rfcnum>\d+))
- %(end_string_suffix)s""" % locals(), re.VERBOSE))
-
- def quoted_start(self, match):
- """Return 1 if inline markup start-string is 'quoted', 0 if not."""
- string = match.string
- start = match.start()
- end = match.end()
- if start == 0: # start-string at beginning of text
- return 0
- prestart = string[start - 1]
- try:
- poststart = string[end]
- if self.openers.index(prestart) \
- == self.closers.index(poststart): # quoted
- return 1
- except IndexError: # start-string at end of text
- return 1
- except ValueError: # not quoted
- pass
- return 0
-
- def inline_obj(self, match, lineno, end_pattern, nodeclass,
- restore_backslashes=0):
- string = match.string
- matchstart = match.start('start')
- matchend = match.end('start')
- if self.quoted_start(match):
- return (string[:matchend], [], string[matchend:], [], '')
- endmatch = end_pattern.search(string[matchend:])
- if endmatch and endmatch.start(1): # 1 or more chars
- text = unescape(endmatch.string[:endmatch.start(1)],
- restore_backslashes)
- textend = matchend + endmatch.end(1)
- rawsource = unescape(string[matchstart:textend], 1)
- return (string[:matchstart], [nodeclass(rawsource, text)],
- string[textend:], [], endmatch.group(1))
- msg = self.reporter.warning(
- 'Inline %s start-string without end-string.'
- % nodeclass.__name__, line=lineno)
- text = unescape(string[matchstart:matchend], 1)
- rawsource = unescape(string[matchstart:matchend], 1)
- prb = self.problematic(text, rawsource, msg)
- return string[:matchstart], [prb], string[matchend:], [msg], ''
-
- def problematic(self, text, rawsource, message):
- msgid = self.document.set_id(message, self.parent)
- problematic = nodes.problematic(rawsource, text, refid=msgid)
- prbid = self.document.set_id(problematic)
- message.add_backref(prbid)
- return problematic
-
- def emphasis(self, match, lineno):
- before, inlines, remaining, sysmessages, endstring = self.inline_obj(
- match, lineno, self.patterns.emphasis, nodes.emphasis)
- return before, inlines, remaining, sysmessages
-
- def strong(self, match, lineno):
- before, inlines, remaining, sysmessages, endstring = self.inline_obj(
- match, lineno, self.patterns.strong, nodes.strong)
- return before, inlines, remaining, sysmessages
-
- def interpreted_or_phrase_ref(self, match, lineno):
- end_pattern = self.patterns.interpreted_or_phrase_ref
- string = match.string
- matchstart = match.start('backquote')
- matchend = match.end('backquote')
- rolestart = match.start('role')
- role = match.group('role')
- position = ''
- if role:
- role = role[1:-1]
- position = 'prefix'
- elif self.quoted_start(match):
- return (string[:matchend], [], string[matchend:], [])
- endmatch = end_pattern.search(string[matchend:])
- if endmatch and endmatch.start(1): # 1 or more chars
- textend = matchend + endmatch.end()
- if endmatch.group('role'):
- if role:
- msg = self.reporter.warning(
- 'Multiple roles in interpreted text (both '
- 'prefix and suffix present; only one allowed).',
- line=lineno)
- text = unescape(string[rolestart:textend], 1)
- prb = self.problematic(text, text, msg)
- return string[:rolestart], [prb], string[textend:], [msg]
- role = endmatch.group('suffix')[1:-1]
- position = 'suffix'
- escaped = endmatch.string[:endmatch.start(1)]
- text = unescape(escaped, 0)
- rawsource = unescape(string[matchstart:textend], 1)
- if rawsource[-1:] == '_':
- if role:
- msg = self.reporter.warning(
- 'Mismatch: both interpreted text role %s and '
- 'reference suffix.' % position, line=lineno)
- text = unescape(string[rolestart:textend], 1)
- prb = self.problematic(text, text, msg)
- return string[:rolestart], [prb], string[textend:], [msg]
- return self.phrase_ref(string[:matchstart], string[textend:],
- rawsource, escaped, text)
- else:
- rawsource = unescape(string[rolestart:textend], 1)
- nodelist, messages = self.interpreted(rawsource, text, role,
- lineno)
- return (string[:rolestart], nodelist,
- string[textend:], messages)
- msg = self.reporter.warning(
- 'Inline interpreted text or phrase reference start-string '
- 'without end-string.', line=lineno)
- text = unescape(string[matchstart:matchend], 1)
- prb = self.problematic(text, text, msg)
- return string[:matchstart], [prb], string[matchend:], [msg]
-
- def phrase_ref(self, before, after, rawsource, escaped, text):
- match = self.patterns.embedded_uri.search(escaped)
- if match:
- text = unescape(escaped[:match.start(0)])
- uri_text = match.group(2)
- uri = ''.join(uri_text.split())
- uri = self.adjust_uri(uri)
- if uri:
- target = nodes.target(match.group(1), refuri=uri)
- else:
- raise ApplicationError('problem with URI: %r' % uri_text)
- if not text:
- text = uri
- else:
- target = None
- refname = normalize_name(text)
- reference = nodes.reference(rawsource, text,
- name=whitespace_normalize_name(text))
- node_list = [reference]
- if rawsource[-2:] == '__':
- if target:
- reference['refuri'] = uri
- else:
- reference['anonymous'] = 1
- self.document.note_anonymous_ref(reference)
- else:
- if target:
- reference['refuri'] = uri
- target['name'] = refname
- self.document.note_external_target(target)
- self.document.note_explicit_target(target, self.parent)
- node_list.append(target)
- else:
- reference['refname'] = refname
- self.document.note_refname(reference)
- return before, node_list, after, []
-
- def adjust_uri(self, uri):
- match = self.patterns.email.match(uri)
- if match:
- return 'mailto:' + uri
- else:
- return uri
-
- def interpreted(self, rawsource, text, role, lineno):
- role_fn, messages = roles.role(role, self.language, lineno, self)
- if role_fn:
- nodes, messages2 = role_fn(role, rawsource, text, lineno, self)
- return nodes, messages + messages2
- else:
- msg = self.reporter.error(
- 'Unknown interpreted text role "%s".' % role,
- line=lineno)
- return ([self.problematic(rawsource, rawsource, msg)],
- messages + [msg])
-
- def literal(self, match, lineno):
- before, inlines, remaining, sysmessages, endstring = self.inline_obj(
- match, lineno, self.patterns.literal, nodes.literal,
- restore_backslashes=1)
- return before, inlines, remaining, sysmessages
-
- def inline_internal_target(self, match, lineno):
- before, inlines, remaining, sysmessages, endstring = self.inline_obj(
- match, lineno, self.patterns.target, nodes.target)
- if inlines and isinstance(inlines[0], nodes.target):
- assert len(inlines) == 1
- target = inlines[0]
- name = normalize_name(target.astext())
- target['name'] = name
- self.document.note_explicit_target(target, self.parent)
- return before, inlines, remaining, sysmessages
-
- def substitution_reference(self, match, lineno):
- before, inlines, remaining, sysmessages, endstring = self.inline_obj(
- match, lineno, self.patterns.substitution_ref,
- nodes.substitution_reference)
- if len(inlines) == 1:
- subref_node = inlines[0]
- if isinstance(subref_node, nodes.substitution_reference):
- subref_text = subref_node.astext()
- self.document.note_substitution_ref(subref_node, subref_text)
- if endstring[-1:] == '_':
- reference_node = nodes.reference(
- '|%s%s' % (subref_text, endstring), '')
- if endstring[-2:] == '__':
- reference_node['anonymous'] = 1
- self.document.note_anonymous_ref(
- reference_node)
- else:
- reference_node['refname'] = normalize_name(subref_text)
- self.document.note_refname(reference_node)
- reference_node += subref_node
- inlines = [reference_node]
- return before, inlines, remaining, sysmessages
-
- def footnote_reference(self, match, lineno):
- """
- Handles `nodes.footnote_reference` and `nodes.citation_reference`
- elements.
- """
- label = match.group('footnotelabel')
- refname = normalize_name(label)
- string = match.string
- before = string[:match.start('whole')]
- remaining = string[match.end('whole'):]
- if match.group('citationlabel'):
- refnode = nodes.citation_reference('[%s]_' % label,
- refname=refname)
- refnode += nodes.Text(label)
- self.document.note_citation_ref(refnode)
- else:
- refnode = nodes.footnote_reference('[%s]_' % label)
- if refname[0] == '#':
- refname = refname[1:]
- refnode['auto'] = 1
- self.document.note_autofootnote_ref(refnode)
- elif refname == '*':
- refname = ''
- refnode['auto'] = '*'
- self.document.note_symbol_footnote_ref(
- refnode)
- else:
- refnode += nodes.Text(label)
- if refname:
- refnode['refname'] = refname
- self.document.note_footnote_ref(refnode)
- if self.document.settings.trim_footnote_reference_space:
- before = before.rstrip()
- return (before, [refnode], remaining, [])
-
- def reference(self, match, lineno, anonymous=None):
- referencename = match.group('refname')
- refname = normalize_name(referencename)
- referencenode = nodes.reference(
- referencename + match.group('refend'), referencename,
- name=whitespace_normalize_name(referencename))
- if anonymous:
- referencenode['anonymous'] = 1
- self.document.note_anonymous_ref(referencenode)
- else:
- referencenode['refname'] = refname
- self.document.note_refname(referencenode)
- string = match.string
- matchstart = match.start('whole')
- matchend = match.end('whole')
- return (string[:matchstart], [referencenode], string[matchend:], [])
-
- def anonymous_reference(self, match, lineno):
- return self.reference(match, lineno, anonymous=1)
-
- def standalone_uri(self, match, lineno):
- if not match.group('scheme') or urischemes.schemes.has_key(
- match.group('scheme').lower()):
- if match.group('email'):
- addscheme = 'mailto:'
- else:
- addscheme = ''
- text = match.group('whole')
- unescaped = unescape(text, 0)
- return [nodes.reference(unescape(text, 1), unescaped,
- refuri=addscheme + unescaped)]
- else: # not a valid scheme
- raise MarkupMismatch
-
- def implicit_inline(self, text, lineno):
- """
- Check each of the patterns in `self.implicit_dispatch` for a match,
- and dispatch to the stored method for the pattern. Recursively check
- the text before and after the match. Return a list of `nodes.Text`
- and inline element nodes.
- """
- if not text:
- return []
- for pattern, method in self.implicit_dispatch:
- match = pattern.search(text)
- if match:
- try:
- # Must recurse on strings before *and* after the match;
- # there may be multiple patterns.
- return (self.implicit_inline(text[:match.start()], lineno)
- + method(match, lineno) +
- self.implicit_inline(text[match.end():], lineno))
- except MarkupMismatch:
- pass
- return [nodes.Text(unescape(text), rawsource=unescape(text, 1))]
-
- dispatch = {'*': emphasis,
- '**': strong,
- '`': interpreted_or_phrase_ref,
- '``': literal,
- '_`': inline_internal_target,
- ']_': footnote_reference,
- '|': substitution_reference,
- '_': reference,
- '__': anonymous_reference}
-
-
-class Body(RSTState):
-
- """
- Generic classifier of the first line of a block.
- """
-
- enum = Struct()
- """Enumerated list parsing information."""
-
- enum.formatinfo = {
- 'parens': Struct(prefix='(', suffix=')', start=1, end=-1),
- 'rparen': Struct(prefix='', suffix=')', start=0, end=-1),
- 'period': Struct(prefix='', suffix='.', start=0, end=-1)}
- enum.formats = enum.formatinfo.keys()
- enum.sequences = ['arabic', 'loweralpha', 'upperalpha',
- 'lowerroman', 'upperroman'] # ORDERED!
- enum.sequencepats = {'arabic': '[0-9]+',
- 'loweralpha': '[a-z]',
- 'upperalpha': '[A-Z]',
- 'lowerroman': '[ivxlcdm]+',
- 'upperroman': '[IVXLCDM]+',}
- enum.converters = {'arabic': int,
- 'loweralpha':
- lambda s, zero=(ord('a')-1): ord(s) - zero,
- 'upperalpha':
- lambda s, zero=(ord('A')-1): ord(s) - zero,
- 'lowerroman':
- lambda s: roman.fromRoman(s.upper()),
- 'upperroman': roman.fromRoman}
-
- enum.sequenceregexps = {}
- for sequence in enum.sequences:
- enum.sequenceregexps[sequence] = re.compile(
- enum.sequencepats[sequence] + '$')
-
- grid_table_top_pat = re.compile(r'\+-[-+]+-\+ *$')
- """Matches the top (& bottom) of a full table)."""
-
- simple_table_top_pat = re.compile('=+( +=+)+ *$')
- """Matches the top of a simple table."""
-
- simple_table_border_pat = re.compile('=+[ =]*$')
- """Matches the bottom & header bottom of a simple table."""
-
- pats = {}
- """Fragments of patterns used by transitions."""
-
- pats['nonalphanum7bit'] = '[!-/:-@[-`{-~]'
- pats['alpha'] = '[a-zA-Z]'
- pats['alphanum'] = '[a-zA-Z0-9]'
- pats['alphanumplus'] = '[a-zA-Z0-9_-]'
- pats['enum'] = ('(%(arabic)s|%(loweralpha)s|%(upperalpha)s|%(lowerroman)s'
- '|%(upperroman)s)' % enum.sequencepats)
- pats['optname'] = '%(alphanum)s%(alphanumplus)s*' % pats
- # @@@ Loosen up the pattern? Allow Unicode?
- pats['optarg'] = '(%(alpha)s%(alphanumplus)s*|<%(alphanum)s[^ <>]+>)' % pats
- pats['shortopt'] = r'(-|\+)%(alphanum)s( ?%(optarg)s)?' % pats
- pats['longopt'] = r'(--|/)%(optname)s([ =]%(optarg)s)?' % pats
- pats['option'] = r'(%(shortopt)s|%(longopt)s)' % pats
-
- for format in enum.formats:
- pats[format] = '(?P<%s>%s%s%s)' % (
- format, re.escape(enum.formatinfo[format].prefix),
- pats['enum'], re.escape(enum.formatinfo[format].suffix))
-
- patterns = {
- 'bullet': r'[-+*]( +|$)',
- 'enumerator': r'(%(parens)s|%(rparen)s|%(period)s)( +|$)' % pats,
- 'field_marker': r':[^: ]([^:]*[^: ])?:( +|$)',
- 'option_marker': r'%(option)s(, %(option)s)*( +| ?$)' % pats,
- 'doctest': r'>>>( +|$)',
- 'grid_table_top': grid_table_top_pat,
- 'simple_table_top': simple_table_top_pat,
- 'explicit_markup': r'\.\.( +|$)',
- 'anonymous': r'__( +|$)',
- 'line': r'(%(nonalphanum7bit)s)\1* *$' % pats,
- 'text': r''}
- initial_transitions = (
- 'bullet',
- 'enumerator',
- 'field_marker',
- 'option_marker',
- 'doctest',
- 'grid_table_top',
- 'simple_table_top',
- 'explicit_markup',
- 'anonymous',
- 'line',
- 'text')
-
- def indent(self, match, context, next_state):
- """Block quote."""
- indented, indent, line_offset, blank_finish = \
- self.state_machine.get_indented()
- blockquote, messages = self.block_quote(indented, line_offset)
- self.parent += blockquote
- self.parent += messages
- if not blank_finish:
- self.parent += self.unindent_warning('Block quote')
- return context, next_state, []
-
- def block_quote(self, indented, line_offset):
- blockquote_lines, attribution_lines, attribution_offset = \
- self.check_attribution(indented, line_offset)
- blockquote = nodes.block_quote()
- self.nested_parse(blockquote_lines, line_offset, blockquote)
- messages = []
- if attribution_lines:
- attribution, messages = self.parse_attribution(attribution_lines,
- attribution_offset)
- blockquote += attribution
- return blockquote, messages
-
- # u'\u2014' is an em-dash:
- attribution_pattern = re.compile(ur'(---?(?!-)|\u2014) *(?=[^ \n])')
-
- def check_attribution(self, indented, line_offset):
- """
- Check for an attribution in the last contiguous block of `indented`.
-
- * First line after last blank line must begin with "--" (etc.).
- * Every line after that must have consistent indentation.
-
- Return a 3-tuple: (block quote lines, attribution lines,
- attribution offset).
- """
- blank = None
- nonblank_seen = None
- indent = 0
- for i in range(len(indented) - 1, 0, -1): # don't check first line
- this_line_blank = not indented[i].strip()
- if nonblank_seen and this_line_blank:
- match = self.attribution_pattern.match(indented[i + 1])
- if match:
- blank = i
- break
- elif not this_line_blank:
- nonblank_seen = 1
- if blank and len(indented) - blank > 2: # multi-line attribution
- indent = (len(indented[blank + 2])
- - len(indented[blank + 2].lstrip()))
- for j in range(blank + 3, len(indented)):
- if indent != (len(indented[j])
- - len(indented[j].lstrip())): # bad shape
- blank = None
- break
- if blank:
- a_lines = indented[blank + 1:]
- a_lines.trim_left(match.end(), end=1)
- a_lines.trim_left(indent, start=1)
- return (indented[:blank], a_lines, line_offset + blank + 1)
- else:
- return (indented, None, None)
-
- def parse_attribution(self, indented, line_offset):
- text = '\n'.join(indented).rstrip()
- lineno = self.state_machine.abs_line_number() + line_offset
- textnodes, messages = self.inline_text(text, lineno)
- node = nodes.attribution(text, '', *textnodes)
- node.line = lineno
- return node, messages
-
- def bullet(self, match, context, next_state):
- """Bullet list item."""
- bulletlist = nodes.bullet_list()
- self.parent += bulletlist
- bulletlist['bullet'] = match.string[0]
- i, blank_finish = self.list_item(match.end())
- bulletlist += i
- offset = self.state_machine.line_offset + 1 # next line
- newline_offset, blank_finish = self.nested_list_parse(
- self.state_machine.input_lines[offset:],
- input_offset=self.state_machine.abs_line_offset() + 1,
- node=bulletlist, initial_state='BulletList',
- blank_finish=blank_finish)
- self.goto_line(newline_offset)
- if not blank_finish:
- self.parent += self.unindent_warning('Bullet list')
- return [], next_state, []
-
- def list_item(self, indent):
- indented, line_offset, blank_finish = \
- self.state_machine.get_known_indented(indent)
- listitem = nodes.list_item('\n'.join(indented))
- if indented:
- self.nested_parse(indented, input_offset=line_offset,
- node=listitem)
- return listitem, blank_finish
-
- def enumerator(self, match, context, next_state):
- """Enumerated List Item"""
- format, sequence, text, ordinal = self.parse_enumerator(match)
- if not self.is_enumerated_list_item(ordinal, sequence, format):
- raise statemachine.TransitionCorrection('text')
- if ordinal != 1:
- msg = self.reporter.info(
- 'Enumerated list start value not ordinal-1: "%s" (ordinal %s)'
- % (text, ordinal), line=self.state_machine.abs_line_number())
- self.parent += msg
- enumlist = nodes.enumerated_list()
- self.parent += enumlist
- enumlist['enumtype'] = sequence
- if ordinal != 1:
- enumlist['start'] = ordinal
- enumlist['prefix'] = self.enum.formatinfo[format].prefix
- enumlist['suffix'] = self.enum.formatinfo[format].suffix
- listitem, blank_finish = self.list_item(match.end())
- enumlist += listitem
- offset = self.state_machine.line_offset + 1 # next line
- newline_offset, blank_finish = self.nested_list_parse(
- self.state_machine.input_lines[offset:],
- input_offset=self.state_machine.abs_line_offset() + 1,
- node=enumlist, initial_state='EnumeratedList',
- blank_finish=blank_finish,
- extra_settings={'lastordinal': ordinal, 'format': format})
- self.goto_line(newline_offset)
- if not blank_finish:
- self.parent += self.unindent_warning('Enumerated list')
- return [], next_state, []
-
- def parse_enumerator(self, match, expected_sequence=None):
- """
- Analyze an enumerator and return the results.
-
- :Return:
- - the enumerator format ('period', 'parens', or 'rparen'),
- - the sequence used ('arabic', 'loweralpha', 'upperroman', etc.),
- - the text of the enumerator, stripped of formatting, and
- - the ordinal value of the enumerator ('a' -> 1, 'ii' -> 2, etc.;
- ``None`` is returned for invalid enumerator text).
-
- The enumerator format has already been determined by the regular
- expression match. If `expected_sequence` is given, that sequence is
- tried first. If not, we check for Roman numeral 1. This way,
- single-character Roman numerals (which are also alphabetical) can be
- matched. If no sequence has been matched, all sequences are checked in
- order.
- """
- groupdict = match.groupdict()
- sequence = ''
- for format in self.enum.formats:
- if groupdict[format]: # was this the format matched?
- break # yes; keep `format`
- else: # shouldn't happen
- raise ParserError('enumerator format not matched')
- text = groupdict[format][self.enum.formatinfo[format].start
- :self.enum.formatinfo[format].end]
- if expected_sequence:
- try:
- if self.enum.sequenceregexps[expected_sequence].match(text):
- sequence = expected_sequence
- except KeyError: # shouldn't happen
- raise ParserError('unknown enumerator sequence: %s'
- % sequence)
- elif text == 'i':
- sequence = 'lowerroman'
- elif text == 'I':
- sequence = 'upperroman'
- if not sequence:
- for sequence in self.enum.sequences:
- if self.enum.sequenceregexps[sequence].match(text):
- break
- else: # shouldn't happen
- raise ParserError('enumerator sequence not matched')
- try:
- ordinal = self.enum.converters[sequence](text)
- except roman.InvalidRomanNumeralError:
- ordinal = None
- return format, sequence, text, ordinal
-
- def is_enumerated_list_item(self, ordinal, sequence, format):
- """
- Check validity based on the ordinal value and the second line.
-
- Return true iff the ordinal is valid and the second line is blank,
- indented, or starts with the next enumerator.
- """
- if ordinal is None:
- return None
- try:
- next_line = self.state_machine.next_line()
- except EOFError: # end of input lines
- self.state_machine.previous_line()
- return 1
- else:
- self.state_machine.previous_line()
- if not next_line[:1].strip(): # blank or indented
- return 1
- next_enumerator = self.make_enumerator(ordinal + 1, sequence, format)
- try:
- if next_line.startswith(next_enumerator):
- return 1
- except TypeError:
- pass
- return None
-
- def make_enumerator(self, ordinal, sequence, format):
- """
- Construct and return an enumerated list item marker.
-
- Return ``None`` for invalid (out of range) ordinals.
- """
- if sequence == 'arabic':
- enumerator = str(ordinal)
- else:
- if sequence.endswith('alpha'):
- if ordinal > 26:
- return None
- enumerator = chr(ordinal + ord('a') - 1)
- elif sequence.endswith('roman'):
- try:
- enumerator = roman.toRoman(ordinal)
- except roman.RomanError:
- return None
- else: # shouldn't happen
- raise ParserError('unknown enumerator sequence: "%s"'
- % sequence)
- if sequence.startswith('lower'):
- enumerator = enumerator.lower()
- elif sequence.startswith('upper'):
- enumerator = enumerator.upper()
- else: # shouldn't happen
- raise ParserError('unknown enumerator sequence: "%s"'
- % sequence)
- formatinfo = self.enum.formatinfo[format]
- return formatinfo.prefix + enumerator + formatinfo.suffix + ' '
-
- def field_marker(self, match, context, next_state):
- """Field list item."""
- field_list = nodes.field_list()
- self.parent += field_list
- field, blank_finish = self.field(match)
- field_list += field
- offset = self.state_machine.line_offset + 1 # next line
- newline_offset, blank_finish = self.nested_list_parse(
- self.state_machine.input_lines[offset:],
- input_offset=self.state_machine.abs_line_offset() + 1,
- node=field_list, initial_state='FieldList',
- blank_finish=blank_finish)
- self.goto_line(newline_offset)
- if not blank_finish:
- self.parent += self.unindent_warning('Field list')
- return [], next_state, []
-
- def field(self, match):
- name = self.parse_field_marker(match)
- lineno = self.state_machine.abs_line_number()
- indented, indent, line_offset, blank_finish = \
- self.state_machine.get_first_known_indented(match.end())
- field_node = nodes.field()
- field_node.line = lineno
- name_nodes, name_messages = self.inline_text(name, lineno)
- field_node += nodes.field_name(name, '', *name_nodes)
- field_body = nodes.field_body('\n'.join(indented), *name_messages)
- field_node += field_body
- if indented:
- self.parse_field_body(indented, line_offset, field_body)
- return field_node, blank_finish
-
- def parse_field_marker(self, match):
- """Extract & return field name from a field marker match."""
- field = match.string[1:] # strip off leading ':'
- field = field[:field.find(':')] # strip off trailing ':' etc.
- return field
-
- def parse_field_body(self, indented, offset, node):
- self.nested_parse(indented, input_offset=offset, node=node)
-
- def option_marker(self, match, context, next_state):
- """Option list item."""
- optionlist = nodes.option_list()
- try:
- listitem, blank_finish = self.option_list_item(match)
- except MarkupError, (message, lineno):
- # This shouldn't happen; pattern won't match.
- msg = self.reporter.error(
- 'Invalid option list marker: %s' % message, line=lineno)
- self.parent += msg
- indented, indent, line_offset, blank_finish = \
- self.state_machine.get_first_known_indented(match.end())
- blockquote, messages = self.block_quote(indented, line_offset)
- self.parent += blockquote
- self.parent += messages
- if not blank_finish:
- self.parent += self.unindent_warning('Option list')
- return [], next_state, []
- self.parent += optionlist
- optionlist += listitem
- offset = self.state_machine.line_offset + 1 # next line
- newline_offset, blank_finish = self.nested_list_parse(
- self.state_machine.input_lines[offset:],
- input_offset=self.state_machine.abs_line_offset() + 1,
- node=optionlist, initial_state='OptionList',
- blank_finish=blank_finish)
- self.goto_line(newline_offset)
- if not blank_finish:
- self.parent += self.unindent_warning('Option list')
- return [], next_state, []
-
- def option_list_item(self, match):
- offset = self.state_machine.abs_line_offset()
- options = self.parse_option_marker(match)
- indented, indent, line_offset, blank_finish = \
- self.state_machine.get_first_known_indented(match.end())
- if not indented: # not an option list item
- self.goto_line(offset)
- raise statemachine.TransitionCorrection('text')
- option_group = nodes.option_group('', *options)
- description = nodes.description('\n'.join(indented))
- option_list_item = nodes.option_list_item('', option_group,
- description)
- if indented:
- self.nested_parse(indented, input_offset=line_offset,
- node=description)
- return option_list_item, blank_finish
-
- def parse_option_marker(self, match):
- """
- Return a list of `node.option` and `node.option_argument` objects,
- parsed from an option marker match.
-
- :Exception: `MarkupError` for invalid option markers.
- """
- optlist = []
- optionstrings = match.group().rstrip().split(', ')
- for optionstring in optionstrings:
- tokens = optionstring.split()
- delimiter = ' '
- firstopt = tokens[0].split('=')
- if len(firstopt) > 1:
- tokens[:1] = firstopt
- delimiter = '='
- elif (len(tokens[0]) > 2
- and ((tokens[0].startswith('-')
- and not tokens[0].startswith('--'))
- or tokens[0].startswith('+'))):
- tokens[:1] = [tokens[0][:2], tokens[0][2:]]
- delimiter = ''
- if 0 < len(tokens) <= 2:
- option = nodes.option(optionstring)
- option += nodes.option_string(tokens[0], tokens[0])
- if len(tokens) > 1:
- option += nodes.option_argument(tokens[1], tokens[1],
- delimiter=delimiter)
- optlist.append(option)
- else:
- raise MarkupError(
- 'wrong numer of option tokens (=%s), should be 1 or 2: '
- '"%s"' % (len(tokens), optionstring),
- self.state_machine.abs_line_number() + 1)
- return optlist
-
- def doctest(self, match, context, next_state):
- data = '\n'.join(self.state_machine.get_text_block())
- self.parent += nodes.doctest_block(data, data)
- return [], next_state, []
-
- def grid_table_top(self, match, context, next_state):
- """Top border of a full table."""
- return self.table_top(match, context, next_state,
- self.isolate_grid_table,
- tableparser.GridTableParser)
-
- def simple_table_top(self, match, context, next_state):
- """Top border of a simple table."""
- return self.table_top(match, context, next_state,
- self.isolate_simple_table,
- tableparser.SimpleTableParser)
-
- def table_top(self, match, context, next_state,
- isolate_function, parser_class):
- """Top border of a generic table."""
- nodelist, blank_finish = self.table(isolate_function, parser_class)
- self.parent += nodelist
- if not blank_finish:
- msg = self.reporter.warning(
- 'Blank line required after table.',
- line=self.state_machine.abs_line_number() + 1)
- self.parent += msg
- return [], next_state, []
-
- def table(self, isolate_function, parser_class):
- """Parse a table."""
- block, messages, blank_finish = isolate_function()
- if block:
- try:
- parser = parser_class()
- tabledata = parser.parse(block)
- tableline = (self.state_machine.abs_line_number() - len(block)
- + 1)
- table = self.build_table(tabledata, tableline)
- nodelist = [table] + messages
- except tableparser.TableMarkupError, detail:
- nodelist = self.malformed_table(block, str(detail)) + messages
- else:
- nodelist = messages
- return nodelist, blank_finish
-
- def isolate_grid_table(self):
- messages = []
- blank_finish = 1
- try:
- block = self.state_machine.get_text_block(flush_left=1)
- except statemachine.UnexpectedIndentationError, instance:
- block, source, lineno = instance.args
- messages.append(self.reporter.error('Unexpected indentation.',
- source=source, line=lineno))
- blank_finish = 0
- block.disconnect()
- width = len(block[0].strip())
- for i in range(len(block)):
- block[i] = block[i].strip()
- if block[i][0] not in '+|': # check left edge
- blank_finish = 0
- self.state_machine.previous_line(len(block) - i)
- del block[i:]
- break
- if not self.grid_table_top_pat.match(block[-1]): # find bottom
- blank_finish = 0
- # from second-last to third line of table:
- for i in range(len(block) - 2, 1, -1):
- if self.grid_table_top_pat.match(block[i]):
- self.state_machine.previous_line(len(block) - i + 1)
- del block[i+1:]
- break
- else:
- messages.extend(self.malformed_table(block))
- return [], messages, blank_finish
- for i in range(len(block)): # check right edge
- if len(block[i]) != width or block[i][-1] not in '+|':
- messages.extend(self.malformed_table(block))
- return [], messages, blank_finish
- return block, messages, blank_finish
-
- def isolate_simple_table(self):
- start = self.state_machine.line_offset
- lines = self.state_machine.input_lines
- limit = len(lines) - 1
- toplen = len(lines[start].strip())
- pattern_match = self.simple_table_border_pat.match
- found = 0
- found_at = None
- i = start + 1
- while i <= limit:
- line = lines[i]
- match = pattern_match(line)
- if match:
- if len(line.strip()) != toplen:
- self.state_machine.next_line(i - start)
- messages = self.malformed_table(
- lines[start:i+1], 'Bottom/header table border does '
- 'not match top border.')
- return [], messages, i == limit or not lines[i+1].strip()
- found += 1
- found_at = i
- if found == 2 or i == limit or not lines[i+1].strip():
- end = i
- break
- i += 1
- else: # reached end of input_lines
- if found:
- extra = ' or no blank line after table bottom'
- self.state_machine.next_line(found_at - start)
- block = lines[start:found_at+1]
- else:
- extra = ''
- self.state_machine.next_line(i - start - 1)
- block = lines[start:]
- messages = self.malformed_table(
- block, 'No bottom table border found%s.' % extra)
- return [], messages, not extra
- self.state_machine.next_line(end - start)
- block = lines[start:end+1]
- return block, [], end == limit or not lines[end+1].strip()
-
- def malformed_table(self, block, detail=''):
- data = '\n'.join(block)
- message = 'Malformed table.'
- lineno = self.state_machine.abs_line_number() - len(block) + 1
- if detail:
- message += '\n' + detail
- error = self.reporter.error(message, nodes.literal_block(data, data),
- line=lineno)
- return [error]
-
- def build_table(self, tabledata, tableline):
- colspecs, headrows, bodyrows = tabledata
- table = nodes.table()
- tgroup = nodes.tgroup(cols=len(colspecs))
- table += tgroup
- for colspec in colspecs:
- tgroup += nodes.colspec(colwidth=colspec)
- if headrows:
- thead = nodes.thead()
- tgroup += thead
- for row in headrows:
- thead += self.build_table_row(row, tableline)
- tbody = nodes.tbody()
- tgroup += tbody
- for row in bodyrows:
- tbody += self.build_table_row(row, tableline)
- return table
-
- def build_table_row(self, rowdata, tableline):
- row = nodes.row()
- for cell in rowdata:
- if cell is None:
- continue
- morerows, morecols, offset, cellblock = cell
- attributes = {}
- if morerows:
- attributes['morerows'] = morerows
- if morecols:
- attributes['morecols'] = morecols
- entry = nodes.entry(**attributes)
- row += entry
- if ''.join(cellblock):
- self.nested_parse(cellblock, input_offset=tableline+offset,
- node=entry)
- return row
-
-
- explicit = Struct()
- """Patterns and constants used for explicit markup recognition."""
-
- explicit.patterns = Struct(
- target=re.compile(r"""
- (
- _ # anonymous target
- | # *OR*
- (?P<quote>`?) # optional open quote
- (?![ `]) # first char. not space or
- # backquote
- (?P<name> # reference name
- .+?
- )
- %(non_whitespace_escape_before)s
- (?P=quote) # close quote if open quote used
- )
- %(non_whitespace_escape_before)s
- [ ]? # optional space
- : # end of reference name
- ([ ]+|$) # followed by whitespace
- """ % vars(Inliner), re.VERBOSE),
- reference=re.compile(r"""
- (
- (?P<simple>%(simplename)s)_
- | # *OR*
- ` # open backquote
- (?![ ]) # not space
- (?P<phrase>.+?) # hyperlink phrase
- %(non_whitespace_escape_before)s
- `_ # close backquote,
- # reference mark
- )
- $ # end of string
- """ % vars(Inliner), re.VERBOSE | re.UNICODE),
- substitution=re.compile(r"""
- (
- (?![ ]) # first char. not space
- (?P<name>.+?) # substitution text
- %(non_whitespace_escape_before)s
- \| # close delimiter
- )
- ([ ]+|$) # followed by whitespace
- """ % vars(Inliner), re.VERBOSE),)
-
- def footnote(self, match):
- lineno = self.state_machine.abs_line_number()
- indented, indent, offset, blank_finish = \
- self.state_machine.get_first_known_indented(match.end())
- label = match.group(1)
- name = normalize_name(label)
- footnote = nodes.footnote('\n'.join(indented))
- footnote.line = lineno
- if name[0] == '#': # auto-numbered
- name = name[1:] # autonumber label
- footnote['auto'] = 1
- if name:
- footnote['name'] = name
- self.document.note_autofootnote(footnote)
- elif name == '*': # auto-symbol
- name = ''
- footnote['auto'] = '*'
- self.document.note_symbol_footnote(footnote)
- else: # manually numbered
- footnote += nodes.label('', label)
- footnote['name'] = name
- self.document.note_footnote(footnote)
- if name:
- self.document.note_explicit_target(footnote, footnote)
- else:
- self.document.set_id(footnote, footnote)
- if indented:
- self.nested_parse(indented, input_offset=offset, node=footnote)
- return [footnote], blank_finish
-
- def citation(self, match):
- lineno = self.state_machine.abs_line_number()
- indented, indent, offset, blank_finish = \
- self.state_machine.get_first_known_indented(match.end())
- label = match.group(1)
- name = normalize_name(label)
- citation = nodes.citation('\n'.join(indented))
- citation.line = lineno
- citation += nodes.label('', label)
- citation['name'] = name
- self.document.note_citation(citation)
- self.document.note_explicit_target(citation, citation)
- if indented:
- self.nested_parse(indented, input_offset=offset, node=citation)
- return [citation], blank_finish
-
- def hyperlink_target(self, match):
- pattern = self.explicit.patterns.target
- lineno = self.state_machine.abs_line_number()
- block, indent, offset, blank_finish = \
- self.state_machine.get_first_known_indented(
- match.end(), until_blank=1, strip_indent=0)
- blocktext = match.string[:match.end()] + '\n'.join(block)
- block = [escape2null(line) for line in block]
- escaped = block[0]
- blockindex = 0
- while 1:
- targetmatch = pattern.match(escaped)
- if targetmatch:
- break
- blockindex += 1
- try:
- escaped += block[blockindex]
- except IndexError:
- raise MarkupError('malformed hyperlink target.', lineno)
- del block[:blockindex]
- block[0] = (block[0] + ' ')[targetmatch.end()-len(escaped)-1:].strip()
- target = self.make_target(block, blocktext, lineno,
- targetmatch.group('name'))
- return [target], blank_finish
-
- def make_target(self, block, block_text, lineno, target_name):
- target_type, data = self.parse_target(block, block_text, lineno)
- if target_type == 'refname':
- target = nodes.target(block_text, '', refname=normalize_name(data))
- target.indirect_reference_name = data
- self.add_target(target_name, '', target, lineno)
- self.document.note_indirect_target(target)
- return target
- elif target_type == 'refuri':
- target = nodes.target(block_text, '')
- self.add_target(target_name, data, target, lineno)
- return target
- else:
- return data
-
- def parse_target(self, block, block_text, lineno):
- """
- Determine the type of reference of a target.
-
- :Return: A 2-tuple, one of:
-
- - 'refname' and the indirect reference name
- - 'refuri' and the URI
- - 'malformed' and a system_message node
- """
- if block and block[-1].strip()[-1:] == '_': # possible indirect target
- reference = ' '.join([line.strip() for line in block])
- refname = self.is_reference(reference)
- if refname:
- return 'refname', refname
- reference = ''.join([line.strip() for line in block])
- if reference.find(' ') == -1:
- return 'refuri', unescape(reference)
- else:
- warning = self.reporter.warning(
- 'Hyperlink target contains whitespace. Perhaps a footnote '
- 'was intended?',
- nodes.literal_block(block_text, block_text), line=lineno)
- return 'malformed', warning
-
- def is_reference(self, reference):
- match = self.explicit.patterns.reference.match(
- whitespace_normalize_name(reference))
- if not match:
- return None
- return unescape(match.group('simple') or match.group('phrase'))
-
- def add_target(self, targetname, refuri, target, lineno):
- target.line = lineno
- if targetname:
- name = normalize_name(unescape(targetname))
- target['name'] = name
- if refuri:
- uri = self.inliner.adjust_uri(refuri)
- if uri:
- target['refuri'] = uri
- self.document.note_external_target(target)
- else:
- raise ApplicationError('problem with URI: %r' % refuri)
- else:
- self.document.note_internal_target(target)
- self.document.note_explicit_target(target, self.parent)
- else: # anonymous target
- if refuri:
- target['refuri'] = refuri
- target['anonymous'] = 1
- self.document.note_anonymous_target(target)
-
- def substitution_def(self, match):
- pattern = self.explicit.patterns.substitution
- lineno = self.state_machine.abs_line_number()
- block, indent, offset, blank_finish = \
- self.state_machine.get_first_known_indented(match.end(),
- strip_indent=0)
- blocktext = (match.string[:match.end()] + '\n'.join(block))
- block.disconnect()
- escaped = escape2null(block[0].rstrip())
- blockindex = 0
- while 1:
- subdefmatch = pattern.match(escaped)
- if subdefmatch:
- break
- blockindex += 1
- try:
- escaped = escaped + ' ' + escape2null(block[blockindex].strip())
- except IndexError:
- raise MarkupError('malformed substitution definition.',
- lineno)
- del block[:blockindex] # strip out the substitution marker
- block[0] = (block[0].strip() + ' ')[subdefmatch.end()-len(escaped)-1:-1]
- if not block[0]:
- del block[0]
- offset += 1
- while block and not block[-1].strip():
- block.pop()
- subname = subdefmatch.group('name')
- substitution_node = nodes.substitution_definition(blocktext)
- substitution_node.line = lineno
- self.document.note_substitution_def(
- substitution_node,subname, self.parent)
- if block:
- block[0] = block[0].strip()
- new_abs_offset, blank_finish = self.nested_list_parse(
- block, input_offset=offset, node=substitution_node,
- initial_state='SubstitutionDef', blank_finish=blank_finish)
- i = 0
- for node in substitution_node[:]:
- if not (isinstance(node, nodes.Inline) or
- isinstance(node, nodes.Text)):
- self.parent += substitution_node[i]
- del substitution_node[i]
- else:
- i += 1
- if len(substitution_node) == 0:
- msg = self.reporter.warning(
- 'Substitution definition "%s" empty or invalid.'
- % subname,
- nodes.literal_block(blocktext, blocktext), line=lineno)
- return [msg], blank_finish
- else:
- return [substitution_node], blank_finish
- else:
- msg = self.reporter.warning(
- 'Substitution definition "%s" missing contents.' % subname,
- nodes.literal_block(blocktext, blocktext), line=lineno)
- return [msg], blank_finish
-
- def directive(self, match, **option_presets):
- type_name = match.group(1)
- directive_function, messages = directives.directive(
- type_name, self.memo.language, self.document)
- self.parent += messages
- if directive_function:
- return self.parse_directive(
- directive_function, match, type_name, option_presets)
- else:
- return self.unknown_directive(type_name)
-
- def parse_directive(self, directive_fn, match, type_name, option_presets):
- """
- Parse a directive then run its directive function.
-
- Parameters:
-
- - `directive_fn`: The function implementing the directive. Uses
- function attributes ``arguments``, ``options``, and/or ``content``
- if present.
-
- - `match`: A regular expression match object which matched the first
- line of the directive.
-
- - `type_name`: The directive name, as used in the source text.
-
- - `option_presets`: A dictionary of preset options, defaults for the
- directive options. Currently, only an "alt" option is passed by
- substitution definitions (value: the substitution name), which may
- be used by an embedded image directive.
-
- Returns a 2-tuple: list of nodes, and a "blank finish" boolean.
- """
- arguments = []
- options = {}
- argument_spec = getattr(directive_fn, 'arguments', None)
- if argument_spec and argument_spec[:2] == (0, 0):
- argument_spec = None
- option_spec = getattr(directive_fn, 'options', None)
- content_spec = getattr(directive_fn, 'content', None)
- lineno = self.state_machine.abs_line_number()
- initial_line_offset = self.state_machine.line_offset
- indented, indent, line_offset, blank_finish \
- = self.state_machine.get_first_known_indented(match.end(),
- strip_top=0)
- block_text = '\n'.join(self.state_machine.input_lines[
- initial_line_offset : self.state_machine.line_offset + 1])
- if indented and not indented[0].strip():
- indented.trim_start()
- line_offset += 1
- while indented and not indented[-1].strip():
- indented.trim_end()
- if indented and (argument_spec or option_spec):
- for i in range(len(indented)):
- if not indented[i].strip():
- break
- else:
- i += 1
- arg_block = indented[:i]
- content = indented[i+1:]
- content_offset = line_offset + i + 1
- else:
- content = indented
- content_offset = line_offset
- arg_block = []
- while content and not content[0].strip():
- content.trim_start()
- content_offset += 1
- try:
- if option_spec:
- options, arg_block = self.parse_directive_options(
- option_presets, option_spec, arg_block)
- if argument_spec:
- arguments = self.parse_directive_arguments(argument_spec,
- arg_block)
- if content and not content_spec:
- raise MarkupError('no content permitted')
- except MarkupError, detail:
- error = self.reporter.error(
- 'Error in "%s" directive:\n%s.' % (type_name, detail),
- nodes.literal_block(block_text, block_text), line=lineno)
- return [error], blank_finish
- result = directive_fn(
- type_name, arguments, options, content, lineno, content_offset,
- block_text, self, self.state_machine)
- return result, blank_finish or self.state_machine.is_next_line_blank()
-
- def parse_directive_options(self, option_presets, option_spec, arg_block):
- options = option_presets.copy()
- for i in range(len(arg_block)):
- if arg_block[i][:1] == ':':
- opt_block = arg_block[i:]
- arg_block = arg_block[:i]
- break
- else:
- opt_block = []
- if opt_block:
- success, data = self.parse_extension_options(option_spec,
- opt_block)
- if success: # data is a dict of options
- options.update(data)
- else: # data is an error string
- raise MarkupError(data)
- return options, arg_block
-
- def parse_directive_arguments(self, argument_spec, arg_block):
- required, optional, last_whitespace = argument_spec
- arg_text = '\n'.join(arg_block)
- arguments = arg_text.split()
- if len(arguments) < required:
- raise MarkupError('%s argument(s) required, %s supplied'
- % (required, len(arguments)))
- elif len(arguments) > required + optional:
- if last_whitespace:
- arguments = arg_text.split(None, required + optional - 1)
- else:
- raise MarkupError(
- 'maximum %s argument(s) allowed, %s supplied'
- % (required + optional, len(arguments)))
- return arguments
-
- def parse_extension_options(self, option_spec, datalines):
- """
- Parse `datalines` for a field list containing extension options
- matching `option_spec`.
-
- :Parameters:
- - `option_spec`: a mapping of option name to conversion
- function, which should raise an exception on bad input.
- - `datalines`: a list of input strings.
-
- :Return:
- - Success value, 1 or 0.
- - An option dictionary on success, an error string on failure.
- """
- node = nodes.field_list()
- newline_offset, blank_finish = self.nested_list_parse(
- datalines, 0, node, initial_state='ExtensionOptions',
- blank_finish=1)
- if newline_offset != len(datalines): # incomplete parse of block
- return 0, 'invalid option block'
- try:
- options = utils.extract_extension_options(node, option_spec)
- except KeyError, detail:
- return 0, ('unknown option: "%s"' % detail.args[0])
- except (ValueError, TypeError), detail:
- return 0, ('invalid option value: %s' % detail)
- except utils.ExtensionOptionError, detail:
- return 0, ('invalid option data: %s' % detail)
- if blank_finish:
- return 1, options
- else:
- return 0, 'option data incompletely parsed'
-
- def unknown_directive(self, type_name):
- lineno = self.state_machine.abs_line_number()
- indented, indent, offset, blank_finish = \
- self.state_machine.get_first_known_indented(0, strip_indent=0)
- text = '\n'.join(indented)
- error = self.reporter.error(
- 'Unknown directive type "%s".' % type_name,
- nodes.literal_block(text, text), line=lineno)
- return [error], blank_finish
-
- def comment(self, match):
- if not match.string[match.end():].strip() \
- and self.state_machine.is_next_line_blank(): # an empty comment?
- return [nodes.comment()], 1 # "A tiny but practical wart."
- indented, indent, offset, blank_finish = \
- self.state_machine.get_first_known_indented(match.end())
- while indented and not indented[-1].strip():
- indented.trim_end()
- text = '\n'.join(indented)
- return [nodes.comment(text, text)], blank_finish
-
- explicit.constructs = [
- (footnote,
- re.compile(r"""
- \.\.[ ]+ # explicit markup start
- \[
- ( # footnote label:
- [0-9]+ # manually numbered footnote
- | # *OR*
- \# # anonymous auto-numbered footnote
- | # *OR*
- \#%s # auto-number ed?) footnote label
- | # *OR*
- \* # auto-symbol footnote
- )
- \]
- ([ ]+|$) # whitespace or end of line
- """ % Inliner.simplename, re.VERBOSE | re.UNICODE)),
- (citation,
- re.compile(r"""
- \.\.[ ]+ # explicit markup start
- \[(%s)\] # citation label
- ([ ]+|$) # whitespace or end of line
- """ % Inliner.simplename, re.VERBOSE | re.UNICODE)),
- (hyperlink_target,
- re.compile(r"""
- \.\.[ ]+ # explicit markup start
- _ # target indicator
- (?![ ]) # first char. not space
- """, re.VERBOSE)),
- (substitution_def,
- re.compile(r"""
- \.\.[ ]+ # explicit markup start
- \| # substitution indicator
- (?![ ]) # first char. not space
- """, re.VERBOSE)),
- (directive,
- re.compile(r"""
- \.\.[ ]+ # explicit markup start
- (%s) # directive name
- [ ]? # optional space
- :: # directive delimiter
- ([ ]+|$) # whitespace or end of line
- """ % Inliner.simplename, re.VERBOSE | re.UNICODE))]
-
- def explicit_markup(self, match, context, next_state):
- """Footnotes, hyperlink targets, directives, comments."""
- nodelist, blank_finish = self.explicit_construct(match)
- self.parent += nodelist
- self.explicit_list(blank_finish)
- return [], next_state, []
-
- def explicit_construct(self, match):
- """Determine which explicit construct this is, parse & return it."""
- errors = []
- for method, pattern in self.explicit.constructs:
- expmatch = pattern.match(match.string)
- if expmatch:
- try:
- return method(self, expmatch)
- except MarkupError, (message, lineno): # never reached?
- errors.append(self.reporter.warning(message, line=lineno))
- break
- nodelist, blank_finish = self.comment(match)
- return nodelist + errors, blank_finish
-
- def explicit_list(self, blank_finish):
- """
- Create a nested state machine for a series of explicit markup
- constructs (including anonymous hyperlink targets).
- """
- offset = self.state_machine.line_offset + 1 # next line
- newline_offset, blank_finish = self.nested_list_parse(
- self.state_machine.input_lines[offset:],
- input_offset=self.state_machine.abs_line_offset() + 1,
- node=self.parent, initial_state='Explicit',
- blank_finish=blank_finish,
- match_titles=self.state_machine.match_titles)
- self.goto_line(newline_offset)
- if not blank_finish:
- self.parent += self.unindent_warning('Explicit markup')
-
- def anonymous(self, match, context, next_state):
- """Anonymous hyperlink targets."""
- nodelist, blank_finish = self.anonymous_target(match)
- self.parent += nodelist
- self.explicit_list(blank_finish)
- return [], next_state, []
-
- def anonymous_target(self, match):
- lineno = self.state_machine.abs_line_number()
- block, indent, offset, blank_finish \
- = self.state_machine.get_first_known_indented(match.end(),
- until_blank=1)
- blocktext = match.string[:match.end()] + '\n'.join(block)
- block = [escape2null(line) for line in block]
- target = self.make_target(block, blocktext, lineno, '')
- return [target], blank_finish
-
- def line(self, match, context, next_state):
- """Section title overline or transition marker."""
- if self.state_machine.match_titles:
- return [match.string], 'Line', []
- elif match.string.strip() == '::':
- raise statemachine.TransitionCorrection('text')
- elif len(match.string.strip()) < 4:
- msg = self.reporter.info(
- 'Unexpected possible title overline or transition.\n'
- "Treating it as ordinary text because it's so short.",
- line=self.state_machine.abs_line_number())
- self.parent += msg
- raise statemachine.TransitionCorrection('text')
- else:
- blocktext = self.state_machine.line
- msg = self.reporter.severe(
- 'Unexpected section title or transition.',
- nodes.literal_block(blocktext, blocktext),
- line=self.state_machine.abs_line_number())
- self.parent += msg
- return [], next_state, []
-
- def text(self, match, context, next_state):
- """Titles, definition lists, paragraphs."""
- return [match.string], 'Text', []
-
-
-class RFC2822Body(Body):
-
- """
- RFC2822 headers are only valid as the first constructs in documents. As
- soon as anything else appears, the `Body` state should take over.
- """
-
- patterns = Body.patterns.copy() # can't modify the original
- patterns['rfc2822'] = r'[!-9;-~]+:( +|$)'
- initial_transitions = [(name, 'Body')
- for name in Body.initial_transitions]
- initial_transitions.insert(-1, ('rfc2822', 'Body')) # just before 'text'
-
- def rfc2822(self, match, context, next_state):
- """RFC2822-style field list item."""
- fieldlist = nodes.field_list(CLASS='rfc2822')
- self.parent += fieldlist
- field, blank_finish = self.rfc2822_field(match)
- fieldlist += field
- offset = self.state_machine.line_offset + 1 # next line
- newline_offset, blank_finish = self.nested_list_parse(
- self.state_machine.input_lines[offset:],
- input_offset=self.state_machine.abs_line_offset() + 1,
- node=fieldlist, initial_state='RFC2822List',
- blank_finish=blank_finish)
- self.goto_line(newline_offset)
- if not blank_finish:
- self.parent += self.unindent_warning(
- 'RFC2822-style field list')
- return [], next_state, []
-
- def rfc2822_field(self, match):
- name = match.string[:match.string.find(':')]
- indented, indent, line_offset, blank_finish = \
- self.state_machine.get_first_known_indented(match.end(),
- until_blank=1)
- fieldnode = nodes.field()
- fieldnode += nodes.field_name(name, name)
- fieldbody = nodes.field_body('\n'.join(indented))
- fieldnode += fieldbody
- if indented:
- self.nested_parse(indented, input_offset=line_offset,
- node=fieldbody)
- return fieldnode, blank_finish
-
-
-class SpecializedBody(Body):
-
- """
- Superclass for second and subsequent compound element members. Compound
- elements are lists and list-like constructs.
-
- All transition methods are disabled (redefined as `invalid_input`).
- Override individual methods in subclasses to re-enable.
-
- For example, once an initial bullet list item, say, is recognized, the
- `BulletList` subclass takes over, with a "bullet_list" node as its
- container. Upon encountering the initial bullet list item, `Body.bullet`
- calls its ``self.nested_list_parse`` (`RSTState.nested_list_parse`), which
- starts up a nested parsing session with `BulletList` as the initial state.
- Only the ``bullet`` transition method is enabled in `BulletList`; as long
- as only bullet list items are encountered, they are parsed and inserted
- into the container. The first construct which is *not* a bullet list item
- triggers the `invalid_input` method, which ends the nested parse and
- closes the container. `BulletList` needs to recognize input that is
- invalid in the context of a bullet list, which means everything *other
- than* bullet list items, so it inherits the transition list created in
- `Body`.
- """
-
- def invalid_input(self, match=None, context=None, next_state=None):
- """Not a compound element member. Abort this state machine."""
- self.state_machine.previous_line() # back up so parent SM can reassess
- raise EOFError
-
- indent = invalid_input
- bullet = invalid_input
- enumerator = invalid_input
- field_marker = invalid_input
- option_marker = invalid_input
- doctest = invalid_input
- grid_table_top = invalid_input
- simple_table_top = invalid_input
- explicit_markup = invalid_input
- anonymous = invalid_input
- line = invalid_input
- text = invalid_input
-
-
-class BulletList(SpecializedBody):
-
- """Second and subsequent bullet_list list_items."""
-
- def bullet(self, match, context, next_state):
- """Bullet list item."""
- if match.string[0] != self.parent['bullet']:
- # different bullet: new list
- self.invalid_input()
- listitem, blank_finish = self.list_item(match.end())
- self.parent += listitem
- self.blank_finish = blank_finish
- return [], next_state, []
-
-
-class DefinitionList(SpecializedBody):
-
- """Second and subsequent definition_list_items."""
-
- def text(self, match, context, next_state):
- """Definition lists."""
- return [match.string], 'Definition', []
-
-
-class EnumeratedList(SpecializedBody):
-
- """Second and subsequent enumerated_list list_items."""
-
- def enumerator(self, match, context, next_state):
- """Enumerated list item."""
- format, sequence, text, ordinal = self.parse_enumerator(
- match, self.parent['enumtype'])
- if (sequence != self.parent['enumtype'] or
- format != self.format or
- ordinal != (self.lastordinal + 1) or
- not self.is_enumerated_list_item(ordinal, sequence, format)):
- # different enumeration: new list
- self.invalid_input()
- listitem, blank_finish = self.list_item(match.end())
- self.parent += listitem
- self.blank_finish = blank_finish
- self.lastordinal = ordinal
- return [], next_state, []
-
-
-class FieldList(SpecializedBody):
-
- """Second and subsequent field_list fields."""
-
- def field_marker(self, match, context, next_state):
- """Field list field."""
- field, blank_finish = self.field(match)
- self.parent += field
- self.blank_finish = blank_finish
- return [], next_state, []
-
-
-class OptionList(SpecializedBody):
-
- """Second and subsequent option_list option_list_items."""
-
- def option_marker(self, match, context, next_state):
- """Option list item."""
- try:
- option_list_item, blank_finish = self.option_list_item(match)
- except MarkupError, (message, lineno):
- self.invalid_input()
- self.parent += option_list_item
- self.blank_finish = blank_finish
- return [], next_state, []
-
-
-class RFC2822List(SpecializedBody, RFC2822Body):
-
- """Second and subsequent RFC2822-style field_list fields."""
-
- patterns = RFC2822Body.patterns
- initial_transitions = RFC2822Body.initial_transitions
-
- def rfc2822(self, match, context, next_state):
- """RFC2822-style field list item."""
- field, blank_finish = self.rfc2822_field(match)
- self.parent += field
- self.blank_finish = blank_finish
- return [], 'RFC2822List', []
-
- blank = SpecializedBody.invalid_input
-
-
-class ExtensionOptions(FieldList):
-
- """
- Parse field_list fields for extension options.
-
- No nested parsing is done (including inline markup parsing).
- """
-
- def parse_field_body(self, indented, offset, node):
- """Override `Body.parse_field_body` for simpler parsing."""
- lines = []
- for line in list(indented) + ['']:
- if line.strip():
- lines.append(line)
- elif lines:
- text = '\n'.join(lines)
- node += nodes.paragraph(text, text)
- lines = []
-
-
-class Explicit(SpecializedBody):
-
- """Second and subsequent explicit markup construct."""
-
- def explicit_markup(self, match, context, next_state):
- """Footnotes, hyperlink targets, directives, comments."""
- nodelist, blank_finish = self.explicit_construct(match)
- self.parent += nodelist
- self.blank_finish = blank_finish
- return [], next_state, []
-
- def anonymous(self, match, context, next_state):
- """Anonymous hyperlink targets."""
- nodelist, blank_finish = self.anonymous_target(match)
- self.parent += nodelist
- self.blank_finish = blank_finish
- return [], next_state, []
-
- blank = SpecializedBody.invalid_input
-
-
-class SubstitutionDef(Body):
-
- """
- Parser for the contents of a substitution_definition element.
- """
-
- patterns = {
- 'embedded_directive': re.compile(r'(%s)::( +|$)'
- % Inliner.simplename, re.UNICODE),
- 'text': r''}
- initial_transitions = ['embedded_directive', 'text']
-
- def embedded_directive(self, match, context, next_state):
- nodelist, blank_finish = self.directive(match,
- alt=self.parent['name'])
- self.parent += nodelist
- if not self.state_machine.at_eof():
- self.blank_finish = blank_finish
- raise EOFError
-
- def text(self, match, context, next_state):
- if not self.state_machine.at_eof():
- self.blank_finish = self.state_machine.is_next_line_blank()
- raise EOFError
-
-
-class Text(RSTState):
-
- """
- Classifier of second line of a text block.
-
- Could be a paragraph, a definition list item, or a title.
- """
-
- patterns = {'underline': Body.patterns['line'],
- 'text': r''}
- initial_transitions = [('underline', 'Body'), ('text', 'Body')]
-
- def blank(self, match, context, next_state):
- """End of paragraph."""
- paragraph, literalnext = self.paragraph(
- context, self.state_machine.abs_line_number() - 1)
- self.parent += paragraph
- if literalnext:
- self.parent += self.literal_block()
- return [], 'Body', []
-
- def eof(self, context):
- if context:
- self.blank(None, context, None)
- return []
-
- def indent(self, match, context, next_state):
- """Definition list item."""
- definitionlist = nodes.definition_list()
- definitionlistitem, blank_finish = self.definition_list_item(context)
- definitionlist += definitionlistitem
- self.parent += definitionlist
- offset = self.state_machine.line_offset + 1 # next line
- newline_offset, blank_finish = self.nested_list_parse(
- self.state_machine.input_lines[offset:],
- input_offset=self.state_machine.abs_line_offset() + 1,
- node=definitionlist, initial_state='DefinitionList',
- blank_finish=blank_finish, blank_finish_state='Definition')
- self.goto_line(newline_offset)
- if not blank_finish:
- self.parent += self.unindent_warning('Definition list')
- return [], 'Body', []
-
- def underline(self, match, context, next_state):
- """Section title."""
- lineno = self.state_machine.abs_line_number()
- title = context[0].rstrip()
- underline = match.string.rstrip()
- source = title + '\n' + underline
- messages = []
- if len(title) > len(underline):
- if len(underline) < 4:
- if self.state_machine.match_titles:
- msg = self.reporter.info(
- 'Possible title underline, too short for the title.\n'
- "Treating it as ordinary text because it's so short.",
- line=lineno)
- self.parent += msg
- raise statemachine.TransitionCorrection('text')
- else:
- blocktext = context[0] + '\n' + self.state_machine.line
- msg = self.reporter.warning(
- 'Title underline too short.',
- nodes.literal_block(blocktext, blocktext), line=lineno)
- messages.append(msg)
- if not self.state_machine.match_titles:
- blocktext = context[0] + '\n' + self.state_machine.line
- msg = self.reporter.severe(
- 'Unexpected section title.',
- nodes.literal_block(blocktext, blocktext), line=lineno)
- self.parent += messages
- self.parent += msg
- return [], next_state, []
- style = underline[0]
- context[:] = []
- self.section(title, source, style, lineno - 1, messages)
- return [], next_state, []
-
- def text(self, match, context, next_state):
- """Paragraph."""
- startline = self.state_machine.abs_line_number() - 1
- msg = None
- try:
- block = self.state_machine.get_text_block(flush_left=1)
- except statemachine.UnexpectedIndentationError, instance:
- block, source, lineno = instance.args
- msg = self.reporter.error('Unexpected indentation.',
- source=source, line=lineno)
- lines = context + list(block)
- paragraph, literalnext = self.paragraph(lines, startline)
- self.parent += paragraph
- self.parent += msg
- if literalnext:
- try:
- self.state_machine.next_line()
- except EOFError:
- pass
- self.parent += self.literal_block()
- return [], next_state, []
-
- def literal_block(self):
- """Return a list of nodes."""
- indented, indent, offset, blank_finish = \
- self.state_machine.get_indented()
- while indented and not indented[-1].strip():
- indented.trim_end()
- if not indented:
- return self.quoted_literal_block()
- nodelist = []
- data = '\n'.join(indented)
- nodelist.append(nodes.literal_block(data, data))
- if not blank_finish:
- nodelist.append(self.unindent_warning('Literal block'))
- return nodelist
-
- def quoted_literal_block(self):
- abs_line_offset = self.state_machine.abs_line_offset()
- offset = self.state_machine.line_offset
- parent_node = nodes.Element()
- new_abs_offset = self.nested_parse(
- self.state_machine.input_lines[offset:],
- input_offset=abs_line_offset, node=parent_node, match_titles=0,
- state_machine_kwargs={'state_classes': (QuotedLiteralBlock,),
- 'initial_state': 'QuotedLiteralBlock'})
- self.goto_line(new_abs_offset)
- return parent_node.children
-
- def definition_list_item(self, termline):
- indented, indent, line_offset, blank_finish = \
- self.state_machine.get_indented()
- definitionlistitem = nodes.definition_list_item(
- '\n'.join(termline + list(indented)))
- lineno = self.state_machine.abs_line_number() - 1
- definitionlistitem.line = lineno
- termlist, messages = self.term(termline, lineno)
- definitionlistitem += termlist
- definition = nodes.definition('', *messages)
- definitionlistitem += definition
- if termline[0][-2:] == '::':
- definition += self.reporter.info(
- 'Blank line missing before literal block (after the "::")? '
- 'Interpreted as a definition list item.', line=line_offset+1)
- self.nested_parse(indented, input_offset=line_offset, node=definition)
- return definitionlistitem, blank_finish
-
- def term(self, lines, lineno):
- """Return a definition_list's term and optional classifier."""
- assert len(lines) == 1
- text_nodes, messages = self.inline_text(lines[0], lineno)
- term_node = nodes.term()
- node_list = [term_node]
- for i in range(len(text_nodes)):
- node = text_nodes[i]
- if isinstance(node, nodes.Text):
- parts = node.rawsource.split(' : ', 1)
- if len(parts) == 1:
- term_node += node
- else:
- term_node += nodes.Text(parts[0].rstrip())
- classifier_node = nodes.classifier('', parts[1])
- classifier_node += text_nodes[i+1:]
- node_list.append(classifier_node)
- break
- else:
- term_node += node
- return node_list, messages
-
-
-class SpecializedText(Text):
-
- """
- Superclass for second and subsequent lines of Text-variants.
-
- All transition methods are disabled. Override individual methods in
- subclasses to re-enable.
- """
-
- def eof(self, context):
- """Incomplete construct."""
- return []
-
- def invalid_input(self, match=None, context=None, next_state=None):
- """Not a compound element member. Abort this state machine."""
- raise EOFError
-
- blank = invalid_input
- indent = invalid_input
- underline = invalid_input
- text = invalid_input
-
-
-class Definition(SpecializedText):
-
- """Second line of potential definition_list_item."""
-
- def eof(self, context):
- """Not a definition."""
- self.state_machine.previous_line(2) # so parent SM can reassess
- return []
-
- def indent(self, match, context, next_state):
- """Definition list item."""
- definitionlistitem, blank_finish = self.definition_list_item(context)
- self.parent += definitionlistitem
- self.blank_finish = blank_finish
- return [], 'DefinitionList', []
-
-
-class Line(SpecializedText):
-
- """
- Second line of over- & underlined section title or transition marker.
- """
-
- eofcheck = 1 # @@@ ???
- """Set to 0 while parsing sections, so that we don't catch the EOF."""
-
- def eof(self, context):
- """Transition marker at end of section or document."""
- marker = context[0].strip()
- if self.memo.section_bubble_up_kludge:
- self.memo.section_bubble_up_kludge = 0
- elif len(marker) < 4:
- self.state_correction(context)
- if self.eofcheck: # ignore EOFError with sections
- lineno = self.state_machine.abs_line_number() - 1
- transition = nodes.transition(context[0])
- transition.line = lineno
- self.parent += transition
- msg = self.reporter.error(
- 'Document or section may not end with a transition.',
- line=lineno)
- self.parent += msg
- self.eofcheck = 1
- return []
-
- def blank(self, match, context, next_state):
- """Transition marker."""
- lineno = self.state_machine.abs_line_number() - 1
- marker = context[0].strip()
- if len(marker) < 4:
- self.state_correction(context)
- transition = nodes.transition(marker)
- transition.line = lineno
- if len(self.parent) == 0:
- msg = self.reporter.error(
- 'Document or section may not begin with a transition.',
- line=lineno)
- self.parent += msg
- elif isinstance(self.parent[-1], nodes.transition):
- msg = self.reporter.error(
- 'At least one body element must separate transitions; '
- 'adjacent transitions not allowed.',
- line=lineno)
- self.parent += msg
- self.parent += transition
- return [], 'Body', []
-
- def text(self, match, context, next_state):
- """Potential over- & underlined title."""
- lineno = self.state_machine.abs_line_number() - 1
- overline = context[0]
- title = match.string
- underline = ''
- try:
- underline = self.state_machine.next_line()
- except EOFError:
- blocktext = overline + '\n' + title
- if len(overline.rstrip()) < 4:
- self.short_overline(context, blocktext, lineno, 2)
- else:
- msg = self.reporter.severe(
- 'Incomplete section title.',
- nodes.literal_block(blocktext, blocktext), line=lineno)
- self.parent += msg
- return [], 'Body', []
- source = '%s\n%s\n%s' % (overline, title, underline)
- overline = overline.rstrip()
- underline = underline.rstrip()
- if not self.transitions['underline'][0].match(underline):
- blocktext = overline + '\n' + title + '\n' + underline
- if len(overline.rstrip()) < 4:
- self.short_overline(context, blocktext, lineno, 2)
- else:
- msg = self.reporter.severe(
- 'Missing matching underline for section title overline.',
- nodes.literal_block(source, source), line=lineno)
- self.parent += msg
- return [], 'Body', []
- elif overline != underline:
- blocktext = overline + '\n' + title + '\n' + underline
- if len(overline.rstrip()) < 4:
- self.short_overline(context, blocktext, lineno, 2)
- else:
- msg = self.reporter.severe(
- 'Title overline & underline mismatch.',
- nodes.literal_block(source, source), line=lineno)
- self.parent += msg
- return [], 'Body', []
- title = title.rstrip()
- messages = []
- if len(title) > len(overline):
- blocktext = overline + '\n' + title + '\n' + underline
- if len(overline.rstrip()) < 4:
- self.short_overline(context, blocktext, lineno, 2)
- else:
- msg = self.reporter.warning(
- 'Title overline too short.',
- nodes.literal_block(source, source), line=lineno)
- messages.append(msg)
- style = (overline[0], underline[0])
- self.eofcheck = 0 # @@@ not sure this is correct
- self.section(title.lstrip(), source, style, lineno + 1, messages)
- self.eofcheck = 1
- return [], 'Body', []
-
- indent = text # indented title
-
- def underline(self, match, context, next_state):
- overline = context[0]
- blocktext = overline + '\n' + self.state_machine.line
- lineno = self.state_machine.abs_line_number() - 1
- if len(overline.rstrip()) < 4:
- self.short_overline(context, blocktext, lineno, 1)
- msg = self.reporter.error(
- 'Invalid section title or transition marker.',
- nodes.literal_block(blocktext, blocktext), line=lineno)
- self.parent += msg
- return [], 'Body', []
-
- def short_overline(self, context, blocktext, lineno, lines=1):
- msg = self.reporter.info(
- 'Possible incomplete section title.\nTreating the overline as '
- "ordinary text because it's so short.", line=lineno)
- self.parent += msg
- self.state_correction(context, lines)
-
- def state_correction(self, context, lines=1):
- self.state_machine.previous_line(lines)
- context[:] = []
- raise statemachine.StateCorrection('Body', 'text')
-
-
-class QuotedLiteralBlock(RSTState):
-
- """
- Nested parse handler for quoted (unindented) literal blocks.
-
- Special-purpose. Not for inclusion in `state_classes`.
- """
-
- patterns = {'initial_quoted': r'(%(nonalphanum7bit)s)' % Body.pats,
- 'text': r''}
- initial_transitions = ('initial_quoted', 'text')
-
- def __init__(self, state_machine, debug=0):
- RSTState.__init__(self, state_machine, debug)
- self.messages = []
- self.initial_lineno = None
-
- def blank(self, match, context, next_state):
- if context:
- raise EOFError
- else:
- return context, next_state, []
-
- def eof(self, context):
- if context:
- text = '\n'.join(context)
- literal_block = nodes.literal_block(text, text)
- literal_block.line = self.initial_lineno
- self.parent += literal_block
- else:
- self.parent += self.reporter.warning(
- 'Literal block expected; none found.',
- line=self.state_machine.abs_line_number())
- self.state_machine.previous_line()
- self.parent += self.messages
- return []
-
- def indent(self, match, context, next_state):
- assert context, ('QuotedLiteralBlock.indent: context should not '
- 'be empty!')
- self.messages.append(
- self.reporter.error('Unexpected indentation.',
- line=self.state_machine.abs_line_number()))
- self.state_machine.previous_line()
- raise EOFError
-
- def initial_quoted(self, match, context, next_state):
- """Match arbitrary quote character on the first line only."""
- self.remove_transition('initial_quoted')
- quote = match.string[0]
- pattern = re.compile(re.escape(quote))
- # New transition matches consistent quotes only:
- self.add_transition('quoted',
- (pattern, self.quoted, self.__class__.__name__))
- self.initial_lineno = self.state_machine.abs_line_number()
- return [match.string], next_state, []
-
- def quoted(self, match, context, next_state):
- """Match consistent quotes on subsequent lines."""
- context.append(match.string)
- return context, next_state, []
-
- def text(self, match, context, next_state):
- if context:
- self.messages.append(
- self.reporter.error('Inconsistent literal block quoting.',
- line=self.state_machine.abs_line_number()))
- self.state_machine.previous_line()
- raise EOFError
-
-
-state_classes = (Body, BulletList, DefinitionList, EnumeratedList, FieldList,
- OptionList, ExtensionOptions, Explicit, Text, Definition,
- Line, SubstitutionDef, RFC2822Body, RFC2822List)
-"""Standard set of State classes used to start `RSTStateMachine`."""
-
-
-def escape2null(text):
- """Return a string with escape-backslashes converted to nulls."""
- parts = []
- start = 0
- while 1:
- found = text.find('\\', start)
- if found == -1:
- parts.append(text[start:])
- return ''.join(parts)
- parts.append(text[start:found])
- parts.append('\x00' + text[found+1:found+2])
- start = found + 2 # skip character after escape
-
-def unescape(text, restore_backslashes=0):
- """
- Return a string with nulls removed or restored to backslashes.
- Backslash-escaped spaces are also removed.
- """
- if restore_backslashes:
- return text.replace('\x00', '\\')
- else:
- for sep in ['\x00 ', '\x00\n', '\x00']:
- text = ''.join(text.split(sep))
- return text