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 _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org http://mail.zope.org/mailman/listinfo/zope-checkins