[Zope-Checkins]
CVS: Zope/lib/python/docutils/parsers/rst/directives
- __init__.py:1.2.10.9 admonitions.py:1.2.10.7
body.py:1.2.10.8 html.py:1.2.10.7 images.py:1.2.10.8
misc.py:1.2.10.8 parts.py:1.2.10.7 references.py:1.2.10.7
tables.py:1.1.2.4
Andreas Jung
andreas at andreas-jung.com
Sun Oct 9 10:44:16 EDT 2005
Update of /cvs-repository/Zope/lib/python/docutils/parsers/rst/directives
In directory cvs.zope.org:/tmp/cvs-serv26422/parsers/rst/directives
Modified Files:
Tag: Zope-2_7-branch
__init__.py admonitions.py body.py html.py images.py misc.py
parts.py references.py tables.py
Log Message:
upgrade to docutils 0.3.9
=== Zope/lib/python/docutils/parsers/rst/directives/__init__.py 1.2.10.8 => 1.2.10.9 ===
--- Zope/lib/python/docutils/parsers/rst/directives/__init__.py:1.2.10.8 Fri Jan 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/__init__.py Sun Oct 9 10:43:45 2005
@@ -113,10 +113,13 @@
#'questions': ('body', 'question_list'),
'table': ('tables', 'table'),
'csv-table': ('tables', 'csv_table'),
+ 'list-table': ('tables', 'list_table'),
'image': ('images', 'image'),
'figure': ('images', 'figure'),
'contents': ('parts', 'contents'),
'sectnum': ('parts', 'sectnum'),
+ 'header': ('parts', 'header'),
+ 'footer': ('parts', 'footer'),
#'footnotes': ('parts', 'footnotes'),
#'citations': ('parts', 'citations'),
'target-notes': ('references', 'target_notes'),
@@ -250,17 +253,26 @@
Return the path argument unwrapped (with newlines removed).
(Directive option conversion function.)
- Raise ``ValueError`` if no argument is found or if the path contains
- internal whitespace.
+ Raise ``ValueError`` if no argument is found.
"""
if argument is None:
raise ValueError('argument required but none supplied')
else:
path = ''.join([s.strip() for s in argument.splitlines()])
- if path.find(' ') == -1:
- return path
- else:
- raise ValueError('path contains whitespace')
+ return path
+
+def uri(argument):
+ """
+ Return the URI argument with whitespace removed.
+ (Directive option conversion function.)
+
+ Raise ``ValueError`` if no argument is found.
+ """
+ if argument is None:
+ raise ValueError('argument required but none supplied')
+ else:
+ uri = ''.join(argument.split())
+ return uri
def nonnegative_int(argument):
"""
@@ -274,7 +286,7 @@
def class_option(argument):
"""
- Convert the argument into an ID-compatible string and return it.
+ Convert the argument into a list of ID-compatible strings and return it.
(Directive option conversion function.)
Raise ``ValueError`` if no argument is found.
@@ -288,7 +300,7 @@
if not class_name:
raise ValueError('cannot make "%s" into a class name' % name)
class_names.append(class_name)
- return ' '.join(class_names)
+ return class_names
unicode_pattern = re.compile(
r'(?:0x|x|\\x|U\+?|\\u)([0-9a-f]+)$|&#x([0-9a-f]+);$', re.IGNORECASE)
@@ -296,10 +308,13 @@
def unicode_code(code):
r"""
Convert a Unicode character code to a Unicode character.
+ (Directive option conversion function.)
Codes may be decimal numbers, hexadecimal numbers (prefixed by ``0x``,
``x``, ``\x``, ``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style
numeric character entities (e.g. ``☮``). Other text remains as-is.
+
+ Raise ValueError for illegal Unicode code values.
"""
try:
if code.isdigit(): # decimal number
@@ -315,6 +330,10 @@
raise ValueError('code too large (%s)' % detail)
def single_char_or_unicode(argument):
+ """
+ A single character is returned as-is. Unicode characters codes are
+ converted as in `unicode_code`. (Directive option conversion function.)
+ """
char = unicode_code(argument)
if len(char) > 1:
raise ValueError('%r invalid; must be a single character or '
@@ -322,6 +341,10 @@
return char
def single_char_or_whitespace_or_unicode(argument):
+ """
+ As with `single_char_or_unicode`, but "tab" and "space" are also supported.
+ (Directive option conversion function.)
+ """
if argument == 'tab':
char = '\t'
elif argument == 'space':
@@ -331,12 +354,23 @@
return char
def positive_int(argument):
+ """
+ Converts the argument into an integer. Raises ValueError for negative,
+ zero, or non-integer values. (Directive option conversion function.)
+ """
value = int(argument)
if value < 1:
raise ValueError('negative or zero value; must be positive')
return value
def positive_int_list(argument):
+ """
+ Converts a space- or comma-separated list of values into a Python list
+ of integers.
+ (Directive option conversion function.)
+
+ Raises ValueError for non-positive-integer values.
+ """
if ',' in argument:
entries = argument.split(',')
else:
@@ -344,6 +378,12 @@
return [positive_int(entry) for entry in entries]
def encoding(argument):
+ """
+ Verfies the encoding argument by lookup.
+ (Directive option conversion function.)
+
+ Raises ValueError for unknown encodings.
+ """
try:
codecs.lookup(argument)
except LookupError:
=== Zope/lib/python/docutils/parsers/rst/directives/admonitions.py 1.2.10.6 => 1.2.10.7 ===
--- Zope/lib/python/docutils/parsers/rst/directives/admonitions.py:1.2.10.6 Fri Jan 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/admonitions.py Sun Oct 9 10:43:45 2005
@@ -30,10 +30,10 @@
admonition_node += nodes.title(title_text, '', *textnodes)
admonition_node += messages
if options.has_key('class'):
- class_value = options['class']
+ classes = options['class']
else:
- class_value = 'admonition-' + nodes.make_id(title_text)
- admonition_node.set_class(class_value)
+ classes = ['admonition-' + nodes.make_id(title_text)]
+ admonition_node['classes'] += classes
state.nested_parse(content, content_offset, admonition_node)
return [admonition_node]
=== Zope/lib/python/docutils/parsers/rst/directives/body.py 1.2.10.7 => 1.2.10.8 ===
--- Zope/lib/python/docutils/parsers/rst/directives/body.py:1.2.10.7 Fri Jan 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/body.py Sun Oct 9 10:43:45 2005
@@ -16,14 +16,16 @@
import sys
from docutils import nodes
from docutils.parsers.rst import directives
+from docutils.parsers.rst.roles import set_classes
def topic(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine,
node_class=nodes.topic):
- if not state_machine.match_titles:
+ if not (state_machine.match_titles
+ or isinstance(state_machine.node, nodes.sidebar)):
error = state_machine.reporter.error(
- 'The "%s" directive may not be used within topics, sidebars, '
+ 'The "%s" directive may not be used within topics '
'or body elements.' % name,
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
@@ -44,8 +46,7 @@
messages.extend(more_messages)
text = '\n'.join(content)
node = node_class(text, *(titles + messages))
- if options.has_key('class'):
- node.set_class(options['class'])
+ node['classes'] += options.get('class', [])
if text:
state.nested_parse(content, content_offset, node)
return [node]
@@ -56,6 +57,11 @@
def sidebar(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
+ if isinstance(state_machine.node, nodes.sidebar):
+ error = state_machine.reporter.error(
+ 'The "%s" directive may not be used within a sidebar element.'
+ % name, nodes.literal_block(block_text, block_text), line=lineno)
+ return [error]
return topic(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine,
node_class=nodes.sidebar)
@@ -72,7 +78,7 @@
'Content block expected for the "%s" directive; none found.'
% name, nodes.literal_block(block_text, block_text), line=lineno)
return [warning]
- block = nodes.line_block()
+ block = nodes.line_block(classes=options.get('class', []))
node_list = [block]
for line_text in content:
text_nodes, messages = state.inline_text(line_text.strip(),
@@ -91,6 +97,7 @@
def parsed_literal(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
+ set_classes(options)
return block(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine,
node_class=nodes.literal_block)
@@ -124,7 +131,7 @@
def epigraph(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
block_quote, messages = state.block_quote(content, content_offset)
- block_quote.set_class('epigraph')
+ block_quote['classes'].append('epigraph')
return [block_quote] + messages
epigraph.content = 1
@@ -132,7 +139,7 @@
def highlights(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
block_quote, messages = state.block_quote(content, content_offset)
- block_quote.set_class('highlights')
+ block_quote['classes'].append('highlights')
return [block_quote] + messages
highlights.content = 1
@@ -140,7 +147,7 @@
def pull_quote(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
block_quote, messages = state.block_quote(content, content_offset)
- block_quote.set_class('pull-quote')
+ block_quote['classes'].append('pull-quote')
return [block_quote] + messages
pull_quote.content = 1
@@ -154,8 +161,7 @@
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
node = nodes.compound(text)
- if options.has_key('class'):
- node.set_class(options['class'])
+ node['classes'] += options.get('class', [])
state.nested_parse(content, content_offset, node)
return [node]
=== Zope/lib/python/docutils/parsers/rst/directives/html.py 1.2.10.6 => 1.2.10.7 ===
--- Zope/lib/python/docutils/parsers/rst/directives/html.py:1.2.10.6 Fri Jan 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/html.py Sun Oct 9 10:43:45 2005
@@ -34,7 +34,7 @@
'Empty meta directive.',
nodes.literal_block(block_text, block_text), line=lineno)
node += error
- return node.get_children()
+ return node.children
meta.content = 1
=== Zope/lib/python/docutils/parsers/rst/directives/images.py 1.2.10.7 => 1.2.10.8 ===
--- Zope/lib/python/docutils/parsers/rst/directives/images.py:1.2.10.7 Fri Jan 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/images.py Sun Oct 9 10:43:45 2005
@@ -14,27 +14,43 @@
import sys
from docutils import nodes, utils
from docutils.parsers.rst import directives, states
-from docutils.nodes import whitespace_normalize_name
+from docutils.nodes import fully_normalize_name
+from docutils.parsers.rst.roles import set_classes
try:
import Image # PIL
except ImportError:
Image = None
-align_values = ('top', 'middle', 'bottom', 'left', 'center', 'right')
+align_h_values = ('left', 'center', 'right')
+align_v_values = ('top', 'middle', 'bottom')
+align_values = align_v_values + align_h_values
def align(argument):
return directives.choice(argument, align_values)
def image(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
+ if options.has_key('align'):
+ # check for align_v values only
+ if isinstance(state, states.SubstitutionDef):
+ if options['align'] not in align_v_values:
+ error = state_machine.reporter.error(
+ 'Error in "%s" directive: "%s" is not a valid value for '
+ 'the "align" option within a substitution definition. '
+ 'Valid values for "align" are: "%s".'
+ % (name, options['align'], '", "'.join(align_v_values)),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [error]
+ elif options['align'] not in align_h_values:
+ error = state_machine.reporter.error(
+ 'Error in "%s" directive: "%s" is not a valid value for '
+ 'the "align" option. Valid values for "align" are: "%s".'
+ % (name, options['align'], '", "'.join(align_h_values)),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [error]
messages = []
- reference = ''.join(arguments[0].split('\n'))
- if reference.find(' ') != -1:
- error = state_machine.reporter.error(
- 'Image URI contains whitespace.',
- nodes.literal_block(block_text, block_text), line=lineno)
- return [error]
+ reference = directives.uri(arguments[0])
options['uri'] = reference
reference_node = None
if options.has_key('target'):
@@ -44,12 +60,13 @@
if target_type == 'refuri':
reference_node = nodes.reference(refuri=data)
elif target_type == 'refname':
- reference_node = nodes.reference(
- refname=data, name=whitespace_normalize_name(options['target']))
+ reference_node = nodes.reference(refname=data,
+ name=fully_normalize_name(options['target']))
state.document.note_refname(reference_node)
else: # malformed target
messages.append(data) # data is a system message
del options['target']
+ set_classes(options)
image_node = nodes.image(block_text, **options)
if reference_node:
reference_node += image_node
@@ -66,31 +83,38 @@
'target': directives.unchanged_required,
'class': directives.class_option}
+def figure_align(argument):
+ return directives.choice(argument, align_h_values)
+
def figure(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
figwidth = options.setdefault('figwidth')
- figclass = options.setdefault('figclass')
+ figclasses = options.setdefault('figclass')
+ align = options.setdefault('align')
del options['figwidth']
del options['figclass']
+ del options['align']
(image_node,) = image(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine)
if isinstance(image_node, nodes.system_message):
return [image_node]
figure_node = nodes.figure('', image_node)
if figwidth == 'image':
- if Image:
+ if Image and state.document.settings.file_insertion_enabled:
# PIL doesn't like Unicode paths:
try:
i = Image.open(str(image_node['uri']))
except (IOError, UnicodeError):
pass
else:
- state.document.settings.record_dependencies.add(reference)
+ state.document.settings.record_dependencies.add(image_node['uri'])
figure_node['width'] = i.size[0]
elif figwidth is not None:
figure_node['width'] = figwidth
- if figclass:
- figure_node.set_class(figclass)
+ if figclasses:
+ figure_node['classes'] += figclasses
+ if align:
+ figure_node['align'] = align
if content:
node = nodes.Element() # anonymous container for parsing
state.nested_parse(content, content_offset, node)
@@ -119,4 +143,5 @@
figure.options = {'figwidth': figwidth_value,
'figclass': directives.class_option}
figure.options.update(image.options)
+figure.options['align'] = figure_align
figure.content = 1
=== Zope/lib/python/docutils/parsers/rst/directives/misc.py 1.2.10.7 => 1.2.10.8 ===
--- Zope/lib/python/docutils/parsers/rst/directives/misc.py:1.2.10.7 Fri Jan 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/misc.py Sun Oct 9 10:43:45 2005
@@ -24,15 +24,15 @@
def include(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
"""Include a reST file as part of the content of this reST file."""
+ if not state.document.settings.file_insertion_enabled:
+ warning = state_machine.reporter.warning(
+ '"%s" directive disabled.' % name,
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [warning]
source = state_machine.input_lines.source(
lineno - state_machine.input_offset - 1)
source_dir = os.path.dirname(os.path.abspath(source))
- path = ''.join(arguments[0].splitlines())
- if path.find(' ') != -1:
- error = state_machine.reporter.error(
- '"%s" directive path contains whitespace.' % name,
- nodes.literal_block(block_text, block_text), line=lineno)
- return [error]
+ path = directives.path(arguments[0])
path = os.path.normpath(os.path.join(source_dir, path))
path = utils.relative_path(None, path)
encoding = options.get('encoding', state.document.settings.input_encoding)
@@ -48,7 +48,14 @@
% (name, error.__class__.__name__, error),
nodes.literal_block(block_text, block_text), line=lineno)
return [severe]
- include_text = include_file.read()
+ try:
+ include_text = include_file.read()
+ except UnicodeError, error:
+ severe = state_machine.reporter.severe(
+ 'Problem with "%s" directive:\n%s: %s'
+ % (name, error.__class__.__name__, error),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [severe]
if options.has_key('literal'):
literal_block = nodes.literal_block(include_text, include_text,
source=path)
@@ -74,6 +81,13 @@
Content may be included inline (content section of directive) or
imported from a file or url.
"""
+ if ( not state.document.settings.raw_enabled
+ or (not state.document.settings.file_insertion_enabled
+ and (options.has_key('file') or options.has_key('url'))) ):
+ warning = state_machine.reporter.warning(
+ '"%s" directive disabled.' % name,
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [warning]
attributes = {'format': ' '.join(arguments[0].lower().split())}
encoding = options.get('encoding', state.document.settings.input_encoding)
if content:
@@ -106,7 +120,14 @@
'Problems with "%s" directive path:\n%s.' % (name, error),
nodes.literal_block(block_text, block_text), line=lineno)
return [severe]
- text = raw_file.read()
+ try:
+ text = raw_file.read()
+ except UnicodeError, error:
+ severe = state_machine.reporter.severe(
+ 'Problem with "%s" directive:\n%s: %s'
+ % (name, error.__class__.__name__, error),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [severe]
attributes['source'] = path
elif options.has_key('url'):
if not urllib2:
@@ -128,7 +149,14 @@
raw_file = io.StringInput(
source=raw_text, source_path=source, encoding=encoding,
error_handler=state.document.settings.input_encoding_error_handler)
- text = raw_file.read()
+ try:
+ text = raw_file.read()
+ except UnicodeError, error:
+ severe = state_machine.reporter.severe(
+ 'Problem with "%s" directive:\n%s: %s'
+ % (name, error.__class__.__name__, error),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [severe]
attributes['source'] = source
else:
error = state_machine.reporter.warning(
@@ -140,7 +168,7 @@
raw.arguments = (1, 0, 1)
raw.options = {'file': directives.path,
- 'url': directives.path,
+ 'url': directives.uri,
'encoding': directives.encoding}
raw.content = 1
@@ -160,8 +188,7 @@
messages = []
for node in element:
if isinstance(node, nodes.system_message):
- if node.has_key('backrefs'):
- del node['backrefs']
+ node['backrefs'] = []
messages.append(node)
error = state_machine.reporter.error(
'Error in "%s" directive: may contain a single paragraph '
=== Zope/lib/python/docutils/parsers/rst/directives/parts.py 1.2.10.6 => 1.2.10.7 ===
--- Zope/lib/python/docutils/parsers/rst/directives/parts.py:1.2.10.6 Fri Jan 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/parts.py Sun Oct 9 10:43:45 2005
@@ -26,10 +26,24 @@
def contents(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
- """Table of contents."""
+ """
+ Table of contents.
+
+ The table of contents is generated in two passes: initial parse and
+ transform. During the initial parse, a 'pending' element is generated
+ which acts as a placeholder, storing the TOC title and any options
+ internally. At a later stage in the processing, the 'pending' element is
+ replaced by a 'topic' element, a title and the table of contents proper.
+ """
+ if not (state_machine.match_titles
+ or isinstance(state_machine.node, nodes.sidebar)):
+ error = state_machine.reporter.error(
+ 'The "%s" directive may not be used within topics '
+ 'or body elements.' % name,
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [error]
document = state_machine.document
language = languages.get_language(document.settings.language_code)
-
if arguments:
title_text = arguments[0]
text_nodes, messages = state.inline_text(title_text, lineno)
@@ -40,24 +54,17 @@
title = None
else:
title = nodes.title('', language.labels['contents'])
-
- topic = nodes.topic(CLASS='contents')
-
- cls = options.get('class')
- if cls:
- topic.set_class(cls)
-
+ topic = nodes.topic(classes=['contents'])
+ topic['classes'] += options.get('class', [])
if title:
name = title.astext()
topic += title
else:
name = language.labels['contents']
-
name = nodes.fully_normalize_name(name)
if not document.has_name(name):
- topic['name'] = name
+ topic['names'].append(name)
document.note_implicit_target(topic)
-
pending = nodes.pending(parts.Contents, rawsource=block_text)
pending.details.update(options)
document.note_pending(pending)
@@ -82,3 +89,36 @@
'start': int,
'prefix': directives.unchanged_required,
'suffix': directives.unchanged_required}
+
+def header_footer(node, name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ """Contents of document header or footer."""
+ if not content:
+ warning = state_machine.reporter.warning(
+ 'Content block expected for the "%s" directive; none found.'
+ % name, nodes.literal_block(block_text, block_text),
+ line=lineno)
+ node.append(nodes.paragraph(
+ '', 'Problem with the "%s" directive: no content supplied.' % name))
+ return [warning]
+ text = '\n'.join(content)
+ state.nested_parse(content, content_offset, node)
+ return []
+
+def header(name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ decoration = state_machine.document.get_decoration()
+ node = decoration.get_header()
+ return header_footer(node, name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine)
+
+header.content = 1
+
+def footer(name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ decoration = state_machine.document.get_decoration()
+ node = decoration.get_footer()
+ return header_footer(node, name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine)
+
+footer.content = 1
=== Zope/lib/python/docutils/parsers/rst/directives/references.py 1.2.10.6 => 1.2.10.7 ===
=== Zope/lib/python/docutils/parsers/rst/directives/tables.py 1.1.2.3 => 1.1.2.4 ===
--- Zope/lib/python/docutils/parsers/rst/directives/tables.py:1.1.2.3 Fri Jan 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/tables.py Sun Oct 9 10:43:45 2005
@@ -44,7 +44,6 @@
return [warning]
title, messages = make_title(arguments, state, lineno)
node = nodes.Element() # anonymous container for parsing
- text = '\n'.join(content)
state.nested_parse(content, content_offset, node)
if len(node) != 1 or not isinstance(node[0], nodes.table):
error = state_machine.reporter.error(
@@ -54,8 +53,7 @@
line=lineno)
return [error]
table_node = node[0]
- if options.has_key('class'):
- table_node.set_class(options['class'])
+ table_node['classes'] += options.get('class', [])
if title:
table_node.insert(0, title)
return [table_node] + messages
@@ -116,6 +114,12 @@
def csv_table(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
try:
+ if ( not state.document.settings.file_insertion_enabled
+ and (options.has_key('file') or options.has_key('url')) ):
+ warning = state_machine.reporter.warning(
+ '"%s" directive disabled.' % name,
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [warning]
check_requirements(name, lineno, block_text, state_machine)
title, messages = make_title(arguments, state, lineno)
csv_data, source = get_csv_data(
@@ -126,8 +130,10 @@
csv_data, DocutilsDialect(options), source, options)
max_cols = max(max_cols, max_header_cols)
header_rows = options.get('header-rows', 0) # default 0
+ stub_columns = options.get('stub-columns', 0) # default 0
check_table_dimensions(
- rows, header_rows, name, lineno, block_text, state_machine)
+ rows, header_rows, stub_columns, name, lineno,
+ block_text, state_machine)
table_head.extend(rows[:header_rows])
table_body = rows[header_rows:]
col_widths = get_column_widths(
@@ -141,19 +147,19 @@
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
table = (col_widths, table_head, table_body)
- table_node = state.build_table(table, content_offset)
- if options.has_key('class'):
- table_node.set_class(options['class'])
+ table_node = state.build_table(table, content_offset, stub_columns)
+ table_node['classes'] += options.get('class', [])
if title:
table_node.insert(0, title)
return [table_node] + messages
csv_table.arguments = (0, 1, 1)
csv_table.options = {'header-rows': directives.nonnegative_int,
+ 'stub-columns': directives.nonnegative_int,
'header': directives.unchanged,
'widths': directives.positive_int_list,
'file': directives.path,
- 'url': directives.path,
+ 'url': directives.uri,
'encoding': directives.encoding,
'class': directives.class_option,
# field delimiter char
@@ -206,7 +212,8 @@
state.document.settings.record_dependencies.add(source)
csv_file = io.FileInput(
source_path=source, encoding=encoding,
- error_handler=state.document.settings.input_encoding_error_handler,
+ error_handler
+ =state.document.settings.input_encoding_error_handler,
handle_io_errors=None)
csv_data = csv_file.read().splitlines()
except IOError, error:
@@ -270,20 +277,34 @@
max_cols = max(max_cols, len(row))
return rows, max_cols
-def check_table_dimensions(rows, header_rows, name, lineno, block_text,
- state_machine):
+def check_table_dimensions(rows, header_rows, stub_columns, name, lineno,
+ block_text, state_machine):
if len(rows) < header_rows:
error = state_machine.reporter.error(
'%s header row(s) specified but only %s row(s) of data supplied '
'("%s" directive).' % (header_rows, len(rows), name),
nodes.literal_block(block_text, block_text), line=lineno)
raise SystemMessagePropagation(error)
- elif len(rows) == header_rows > 0:
+ if len(rows) == header_rows > 0:
error = state_machine.reporter.error(
'Insufficient data supplied (%s row(s)); no data remaining for '
'table body, required by "%s" directive.' % (len(rows), name),
nodes.literal_block(block_text, block_text), line=lineno)
raise SystemMessagePropagation(error)
+ for row in rows:
+ if len(row) < stub_columns:
+ error = state_machine.reporter.error(
+ '%s stub column(s) specified but only %s columns(s) of data '
+ 'supplied ("%s" directive).' % (stub_columns, len(row), name),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ raise SystemMessagePropagation(error)
+ if len(row) == stub_columns > 0:
+ error = state_machine.reporter.error(
+ 'Insufficient data supplied (%s columns(s)); no data remaining '
+ 'for table body, required by "%s" directive.'
+ % (len(row), name),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ raise SystemMessagePropagation(error)
def get_column_widths(max_cols, name, options, lineno, block_text,
state_machine):
@@ -295,8 +316,13 @@
% (name, max_cols),
nodes.literal_block(block_text, block_text), line=lineno)
raise SystemMessagePropagation(error)
- else:
+ elif max_cols:
col_widths = [100 / max_cols] * max_cols
+ else:
+ error = state_machine.reporter.error(
+ 'No table data detected in CSV file.',
+ nodes.literal_block(block_text, block_text), line=lineno)
+ raise SystemMessagePropagation(error)
return col_widths
def extend_short_rows_with_empty_cells(columns, parts):
@@ -304,3 +330,112 @@
for row in part:
if len(row) < columns:
row.extend([(0, 0, 0, [])] * (columns - len(row)))
+
+def list_table(name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ """
+ Implement tables whose data is encoded as a uniform two-level bullet list.
+ For further ideas, see
+ http://docutils.sf.net/docs/dev/rst/alternatives.html#list-driven-tables
+ """
+ if not content:
+ error = state_machine.reporter.error(
+ 'The "%s" directive is empty; content required.' % name,
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [error]
+ title, messages = make_title(arguments, state, lineno)
+ node = nodes.Element() # anonymous container for parsing
+ state.nested_parse(content, content_offset, node)
+ try:
+ num_cols, col_widths = check_list_content(
+ node, name, options, content, lineno, block_text, state_machine)
+ table_data = [[item.children for item in row_list[0]]
+ for row_list in node[0]]
+ header_rows = options.get('header-rows', 0) # default 0
+ stub_columns = options.get('stub-columns', 0) # default 0
+ check_table_dimensions(
+ table_data, header_rows, stub_columns, name, lineno,
+ block_text, state_machine)
+ except SystemMessagePropagation, detail:
+ return [detail.args[0]]
+ table_node = build_table_from_list(table_data, col_widths,
+ header_rows, stub_columns)
+ table_node['classes'] += options.get('class', [])
+ if title:
+ table_node.insert(0, title)
+ return [table_node] + messages
+
+list_table.arguments = (0, 1, 1)
+list_table.options = {'header-rows': directives.nonnegative_int,
+ 'stub-columns': directives.nonnegative_int,
+ 'widths': directives.positive_int_list,
+ 'class': directives.class_option}
+list_table.content = 1
+
+def check_list_content(node, name, options, content, lineno, block_text,
+ state_machine):
+ if len(node) != 1 or not isinstance(node[0], nodes.bullet_list):
+ error = state_machine.reporter.error(
+ 'Error parsing content block for the "%s" directive: '
+ 'exactly one bullet list expected.' % name,
+ nodes.literal_block(block_text, block_text), line=lineno)
+ raise SystemMessagePropagation(error)
+ list_node = node[0]
+ # Check for a uniform two-level bullet list:
+ for item_index in range(len(list_node)):
+ item = list_node[item_index]
+ if len(item) != 1 or not isinstance(item[0], nodes.bullet_list):
+ error = state_machine.reporter.error(
+ 'Error parsing content block for the "%s" directive: '
+ 'two-level bullet list expected, but row %s does not contain '
+ 'a second-level bullet list.' % (name, item_index + 1),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ raise SystemMessagePropagation(error)
+ elif item_index:
+ if len(item[0]) != num_cols:
+ error = state_machine.reporter.error(
+ 'Error parsing content block for the "%s" directive: '
+ 'uniform two-level bullet list expected, but row %s does '
+ 'not contain the same number of items as row 1 (%s vs %s).'
+ % (name, item_index + 1, len(item[0]), num_cols),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ raise SystemMessagePropagation(error)
+ else:
+ num_cols = len(item[0])
+ col_widths = get_column_widths(
+ num_cols, name, options, lineno, block_text, state_machine)
+ if len(col_widths) != num_cols:
+ error = state_machine.reporter.error(
+ 'Error parsing "widths" option of the "%s" directive: '
+ 'number of columns does not match the table data (%s vs %s).'
+ % (name, len(col_widths), num_cols),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ raise SystemMessagePropagation(error)
+ return num_cols, col_widths
+
+def build_table_from_list(table_data, col_widths, header_rows, stub_columns):
+ table = nodes.table()
+ tgroup = nodes.tgroup(cols=len(col_widths))
+ table += tgroup
+ for col_width in col_widths:
+ colspec = nodes.colspec(colwidth=col_width)
+ if stub_columns:
+ colspec.attributes['stub'] = 1
+ stub_columns -= 1
+ tgroup += colspec
+ rows = []
+ for row in table_data:
+ row_node = nodes.row()
+ for cell in row:
+ entry = nodes.entry()
+ entry += cell
+ row_node += entry
+ rows.append(row_node)
+ if header_rows:
+ thead = nodes.thead()
+ thead.extend(rows[:header_rows])
+ tgroup += thead
+ tbody = nodes.tbody()
+ tbody.extend(rows[header_rows:])
+ tgroup += tbody
+ return table
More information about the Zope-Checkins
mailing list