Update of /cvs-repository/Zope/lib/python/docutils In directory cvs.zope.org:/tmp/cvs-serv26422
Modified Files: Tag: Zope-2_7-branch __init__.py core.py examples.py frontend.py io.py nodes.py statemachine.py utils.py Log Message: upgrade to docutils 0.3.9 === Zope/lib/python/docutils/__init__.py 1.2.10.9 => 1.2.10.10 === --- Zope/lib/python/docutils/__init__.py:1.2.10.9 Fri Jan 7 08:26:01 2005 +++ Zope/lib/python/docutils/__init__.py Sun Oct 9 10:43:43 2005 @@ -51,7 +51,7 @@ __docformat__ = 'reStructuredText' -__version__ = '0.3.7' +__version__ = '0.3.9' """``major.minor.micro`` version number. The micro number is bumped for API changes, for new functionality, and for interim project releases. The minor number is bumped whenever there is a significant project release. The major === Zope/lib/python/docutils/core.py 1.2.10.7 => 1.2.10.8 === --- Zope/lib/python/docutils/core.py:1.2.10.7 Fri Jan 7 08:26:01 2005 +++ Zope/lib/python/docutils/core.py Sun Oct 9 10:43:43 2005 @@ -197,6 +197,7 @@ self.writer.assemble_parts() except Exception, error: if self.settings.traceback: # propagate exceptions? + self.debugging_dumps(document) raise self.report_Exception(error) exit = 1 @@ -210,6 +211,8 @@ return output def debugging_dumps(self, document): + if not document: + return if self.settings.dump_settings: print >>sys.stderr, '\n::: Runtime settings:' print >>sys.stderr, pprint.pformat(self.settings.__dict__) === Zope/lib/python/docutils/examples.py 1.1.4.3 => 1.1.4.4 === --- Zope/lib/python/docutils/examples.py:1.1.4.3 Fri Jan 7 08:26:02 2005 +++ Zope/lib/python/docutils/examples.py Sun Oct 9 10:43:43 2005 @@ -7,12 +7,13 @@ """ This module contains practical examples of Docutils client code. -Importing this module is not recommended; its contents are subject to change -in future Docutils releases. Instead, it is recommended that you copy and -paste the parts you need into your own code, modifying as necessary. +Importing this module from client code is not recommended; its contents are +subject to change in future Docutils releases. Instead, it is recommended +that you copy and paste the parts you need into your own code, modifying as +necessary. """ -from docutils import core +from docutils import core, io def html_parts(input_string, source_path=None, destination_path=None, @@ -72,3 +73,23 @@ if output_encoding != 'unicode': fragment = fragment.encode(output_encoding) return fragment + +def internals(input_string, source_path=None, destination_path=None, + input_encoding='unicode'): + """ + Return the document tree and publisher, for exploring Docutils internals. + + Parameters: see `html_parts()`. + """ + overrides = {'input_encoding': input_encoding} + output, pub = core.publish_programmatically( + source_class=io.StringInput, source=input_string, + source_path=source_path, + destination_class=io.NullOutput, destination=None, + destination_path=destination_path, + reader=None, reader_name='standalone', + parser=None, parser_name='restructuredtext', + writer=None, writer_name='null', + settings=None, settings_spec=None, settings_overrides=overrides, + config_section=None, enable_exit_status=None) + return pub.writer.document, pub === Zope/lib/python/docutils/frontend.py 1.2.10.8 => 1.2.10.9 === --- Zope/lib/python/docutils/frontend.py:1.2.10.8 Fri Jan 7 08:26:02 2005 +++ Zope/lib/python/docutils/frontend.py Sun Oct 9 10:43:43 2005 @@ -124,6 +124,13 @@ None, sys.exc_info()[2]) return value +def validate_nonnegative_int(setting, value, option_parser, + config_parser=None, config_section=None): + value = int(value) + if value < 0: + raise ValueError('negative value; must be positive or zero') + return value + def validate_threshold(setting, value, option_parser, config_parser=None, config_section=None): try: @@ -333,10 +340,10 @@ 'validator': validate_threshold}), ('Report all system messages, info-level and higher. (Same as ' '"--report=info".)', - ['--verbose', '-v'], {'action': 'store_const', 'const': 'info', + ['--verbose', '-v'], {'action': 'store_const', 'const': 1, 'dest': 'report_level'}), ('Do not report any system messages. (Same as "--report=none".)', - ['--quiet', '-q'], {'action': 'store_const', 'const': 'none', + ['--quiet', '-q'], {'action': 'store_const', 'const': 5, 'dest': 'report_level'}), ('Set the threshold (<level>) at or above which system messages are ' 'converted to exceptions, halting execution immediately by ' @@ -429,6 +436,9 @@ ['--version', '-V'], {'action': 'version'}), ('Show this help message and exit.', ['--help', '-h'], {'action': 'help'}), + # Typically not useful for non-programmatical use. + (SUPPRESS_HELP, ['--id-prefix'], {'default': ''}), + (SUPPRESS_HELP, ['--auto-id-prefix'], {'default': 'id'}), # Hidden options, for development use only: (SUPPRESS_HELP, ['--dump-settings'], {'action': 'store_true'}), (SUPPRESS_HELP, ['--dump-internals'], {'action': 'store_true'}), === Zope/lib/python/docutils/io.py 1.2.10.7 => 1.2.10.8 === --- Zope/lib/python/docutils/io.py:1.2.10.7 Fri Jan 7 08:26:02 2005 +++ Zope/lib/python/docutils/io.py Sun Oct 9 10:43:43 2005 @@ -70,32 +70,42 @@ if (self.encoding and self.encoding.lower() == 'unicode' or isinstance(data, UnicodeType)): return data - encodings = [self.encoding, 'utf-8'] - try: - encodings.append(locale.nl_langinfo(locale.CODESET)) - except: - pass - try: - encodings.append(locale.getlocale()[1]) - except: - pass - try: - encodings.append(locale.getdefaultlocale()[1]) - except: - pass - encodings.append('latin-1') + encodings = [self.encoding] + if not self.encoding: + # Apply heuristics only if no encoding is explicitly given. + encodings.append('utf-8') + try: + encodings.append(locale.nl_langinfo(locale.CODESET)) + except: + pass + try: + encodings.append(locale.getlocale()[1]) + except: + pass + try: + encodings.append(locale.getdefaultlocale()[1]) + except: + pass + encodings.append('latin-1') + error = None + error_details = '' for enc in encodings: if not enc: continue try: decoded = unicode(data, enc, self.error_handler) self.successful_encoding = enc - return decoded - except (UnicodeError, LookupError): + # Return decoded, removing BOMs. + return decoded.replace(u'\ufeff', u'') + except (UnicodeError, LookupError), error: pass + if error is not None: + error_details = '\n(%s: %s)' % (error.__class__.__name__, error) raise UnicodeError( - 'Unable to decode input data. Tried the following encodings: %s.' - % ', '.join([repr(enc) for enc in encodings if enc])) + 'Unable to decode input data. Tried the following encodings: ' + '%s.%s' + % (', '.join([repr(enc) for enc in encodings if enc]), + error_details)) class Output(TransformSpec): === Zope/lib/python/docutils/nodes.py 1.2.10.7 => 1.2.10.8 === --- Zope/lib/python/docutils/nodes.py:1.2.10.7 Fri Jan 7 08:26:02 2005 +++ Zope/lib/python/docutils/nodes.py Sun Oct 9 10:43:43 2005 @@ -26,6 +26,8 @@ import sys import os import re +import copy +import warnings import xml.dom.minidom from types import IntType, SliceType, StringType, UnicodeType, \ TupleType, ListType @@ -103,7 +105,7 @@ or replaced occurs after the current node, the old node will still be traversed, and any new nodes will not. - Within ``visit`` methods (and ``depart`` methods for + Within ``visit`` methods (and ``depart`` methods for `walkabout()`), `TreePruningException` subclasses may be raised (`SkipChildren`, `SkipSiblings`, `SkipNode`, `SkipDeparture`). @@ -111,15 +113,15 @@ ``visit`` implementation for each `Node` subclass encountered. """ visitor.document.reporter.debug( - 'calling dispatch_visit for %s' % self.__class__.__name__, - category='nodes.Node.walk') + 'docutils.nodes.Node.walk calling dispatch_visit for %s' + % self.__class__.__name__) try: visitor.dispatch_visit(self) except (SkipChildren, SkipNode): return except SkipDeparture: # not applicable; ignore pass - children = self.get_children() + children = self.children try: for child in children[:]: child.walk(visitor) @@ -138,8 +140,8 @@ """ call_depart = 1 visitor.document.reporter.debug( - 'calling dispatch_visit for %s' % self.__class__.__name__, - category='nodes.Node.walkabout') + 'docutils.nodes.Node.walkabout calling dispatch_visit for %s' + % self.__class__.__name__) try: try: visitor.dispatch_visit(self) @@ -147,7 +149,7 @@ return except SkipDeparture: call_depart = 0 - children = self.get_children() + children = self.children try: for child in children[:]: child.walkabout(visitor) @@ -157,10 +159,83 @@ pass if call_depart: visitor.document.reporter.debug( - 'calling dispatch_departure for %s' % self.__class__.__name__, - category='nodes.Node.walkabout') + 'docutils.nodes.Node.walkabout calling dispatch_departure ' + 'for %s' % self.__class__.__name__) visitor.dispatch_departure(self) + def traverse(self, condition=None, + include_self=1, descend=1, siblings=0, ascend=0): + """ + Return an iterable containing + + * self (if include_self is true) + * all descendants in tree traversal order (if descend is true) + * all siblings (if siblings is true) and their descendants (if + also descend is true) + * the siblings of the parent (if ascend is true) and their + descendants (if also descend is true), and so on + + If ascend is true, assume siblings to be true as well. + + For example, given the following tree:: + + <paragraph> + <emphasis> <--- emphasis.traverse() and + <strong> <--- strong.traverse() are called. + Foo + Bar + <reference name="Baz" refid="baz"> + Baz + + Then list(emphasis.traverse()) equals :: + + [<emphasis>, <strong>, <#text: Foo>, <#text: Bar>] + + and list(strong.traverse(ascend=1)) equals :: + + [<strong>, <#text: Foo>, <#text: Bar>, <reference>, <#text: Baz>] + """ + r = [] + if ascend: + siblings=1 + if include_self and (condition is None or condition(self)): + r.append(self) + if descend and len(self.children): + for child in self: + r.extend(child.traverse( + include_self=1, descend=1, siblings=0, ascend=0, + condition=condition)) + if siblings or ascend: + node = self + while node.parent: + index = node.parent.index(node) + for sibling in node.parent[index+1:]: + r.extend(sibling.traverse(include_self=1, descend=descend, + siblings=0, ascend=0, + condition=condition)) + if not ascend: + break + else: + node = node.parent + return r + + + def next_node(self, condition=None, + include_self=0, descend=1, siblings=0, ascend=0): + """ + Return the first node in the iterable returned by traverse(), + or None if the iterable is empty. + + Parameter list is the same as of traverse. Note that + include_self defaults to 0, though. + """ + iterable = self.traverse(condition=condition, + include_self=include_self, descend=descend, + siblings=siblings, ascend=ascend) + try: + return iterable[0] + except IndexError: + return None class Text(Node, UserString): @@ -172,6 +247,9 @@ tagname = '#text' + children = () + """Text nodes have no children, and cannot have children.""" + def __init__(self, data, rawsource=''): UserString.__init__(self, data) @@ -209,10 +287,6 @@ result.append(indent + line + '\n') return ''.join(result) - def get_children(self): - """Text nodes have no children. Return [].""" - return [] - class Element(Node): @@ -225,6 +299,12 @@ element['att'] = 'value' + There are two special attributes: 'ids' and 'names'. Both are + lists of unique identifiers, and names serve as human interfaces + to IDs. Names are case- and whitespace-normalized (see the + fully_normalize_name() function), and IDs conform to the regular + expression ``[a-z](-?[a-z0-9]+)*`` (see the make_id() function). + Elements also emulate lists for child nodes (element nodes and/or text nodes), indexing by integer. To get the first child node, use:: @@ -245,6 +325,10 @@ This is equivalent to ``element.extend([node1, node2])``. """ + attr_defaults = {'ids': [], 'classes': [], 'names': [], + 'dupnames': [], 'backrefs': []} + """Default attributes.""" + tagname = None """The element generic identifier. If None, it is set as an instance attribute to the name of the class.""" @@ -261,7 +345,7 @@ self.extend(children) # maintain parent info - self.attributes = {} + self.attributes = copy.deepcopy(self.attr_defaults) """Dictionary of attribute {name: value}.""" for att, value in attributes.items(): @@ -272,7 +356,7 @@ def _dom_node(self, domroot): element = domroot.createElement(self.tagname) - for attribute, value in self.attributes.items(): + for attribute, value in self.attlist(): if isinstance(value, ListType): value = ' '.join(['%s' % v for v in value]) element.setAttribute(attribute, '%s' % value) @@ -287,16 +371,16 @@ if len(data) > 60: data = data[:56] + ' ...' break - if self.hasattr('name'): + if self['names']: return '<%s "%s": %s>' % (self.__class__.__name__, - self.attributes['name'], data) + '; '.join(self['names']), data) else: return '<%s: %s>' % (self.__class__.__name__, data) def shortrepr(self): - if self.hasattr('name'): + if self['names']: return '<%s "%s"...>' % (self.__class__.__name__, - self.attributes['name']) + '; '.join(self['names'])) else: return '<%s...>' % self.tagname @@ -382,20 +466,24 @@ def __iadd__(self, other): """Append a node or a list of nodes to `self.children`.""" if isinstance(other, Node): - self.setup_child(other) - self.children.append(other) + self.append(other) elif other is not None: - for node in other: - self.setup_child(node) - self.children.extend(other) + self.extend(other) return self def astext(self): return self.child_text_separator.join( [child.astext() for child in self.children]) + def non_default_attributes(self): + atts = {} + for key, value in self.attributes.items(): + if self.is_not_default(key): + atts[key] = value + return atts + def attlist(self): - attlist = self.attributes.items() + attlist = self.non_default_attributes().items() attlist.sort() return attlist @@ -420,8 +508,7 @@ def extend(self, item): for node in item: - self.setup_child(node) - self.children.extend(item) + self.append(node) def insert(self, index, item): if isinstance(item, Node): @@ -439,6 +526,15 @@ def index(self, item): return self.children.index(item) + def is_not_default(self, key): + try: + return self[key] != self.attr_defaults[key] + except KeyError: + return 1 + + def clear(self): + self.children = [] + def replace(self, old, new): """Replace one child `Node` with another child or children.""" index = self.index(old) @@ -482,12 +578,10 @@ if not isinstance(childclass, TupleType): childclass = (childclass,) for index in range(start, min(len(self), end)): - match = 0 for c in childclass: if isinstance(self.children[index], c): - match = 1 break - if not match: + else: return index return None @@ -496,17 +590,33 @@ [child.pformat(indent, level+1) for child in self.children]) - def get_children(self): - """Return this element's children.""" - return self.children - def copy(self): return self.__class__(**self.attributes) def set_class(self, name): - """Add a new name to the "class" attribute.""" - self.attributes['class'] = (self.attributes.get('class', '') + ' ' - + name.lower()).strip() + """Add a new class to the "classes" attribute.""" + warnings.warn('docutils.nodes.Element.set_class deprecated; ' + "append to Element['classes'] list attribute directly", + DeprecationWarning, stacklevel=2) + assert ' ' not in name + self['classes'].append(name.lower()) + + def note_referenced_by(self, name=None, id=None): + """Note that this Element has been referenced by its name + `name` or id `id`.""" + self.referenced = 1 + # Element.expect_referenced_by_* dictionaries map names or ids + # to nodes whose ``referenced`` attribute is set to true as + # soon as this node is referenced by the given name or id. + # Needed for target propagation. + by_name = getattr(self, 'expect_referenced_by_name', {}).get(name) + by_id = getattr(self, 'expect_referenced_by_id', {}).get(id) + if by_name: + assert name is not None + by_name.referenced = 1 + if by_id: + assert id is not None + by_id.referenced = 1 class TextElement(Element): @@ -514,7 +624,7 @@ """ An element which directly contains text. - Its children are all `Text` or `TextElement` subclass nodes. You can + Its children are all `Text` or `Inline` subclass nodes. You can check whether an element's context is inline simply by checking whether its immediate parent is a `TextElement` instance (including subclasses). This is handy for nodes like `image` that can appear both inline and as @@ -557,7 +667,7 @@ class BackLinkable: def add_backref(self, refid): - self.setdefault('backrefs', []).append(refid) + self['backrefs'].append(refid) # ==================== @@ -568,15 +678,12 @@ class Titular: pass -class PreDecorative: - """Category of Node which may occur before Decorative Nodes.""" - -class PreBibliographic(PreDecorative): +class PreBibliographic: """Category of Node which may occur before Bibliographic Nodes.""" -class Bibliographic(PreDecorative): pass +class Bibliographic: pass -class Decorative: pass +class Decorative(PreBibliographic): pass class Structural: pass @@ -584,7 +691,8 @@ class General(Body): pass -class Sequential(Body): pass +class Sequential(Body): + """List-like elements.""" class Admonition(Body): pass @@ -604,9 +712,6 @@ referenced = 0 - indirect_reference_name = None - """Holds the whitespace_normalized_name (contains mixed case) of a target""" - class Labeled: """Contains a `label` as its first element.""" @@ -717,6 +822,9 @@ self.transformer = docutils.transforms.Transformer(self) """Storage for transforms to be applied to this document.""" + self.decoration = None + """Document's `decoration` node.""" + self.document = self def asdom(self, dom=xml.dom.minidom): @@ -726,21 +834,23 @@ return domroot def set_id(self, node, msgnode=None): - if node.has_key('id'): - id = node['id'] + for id in node['ids']: if self.ids.has_key(id) and self.ids[id] is not node: msg = self.reporter.severe('Duplicate ID: "%s".' % id) if msgnode != None: msgnode += msg - else: - if node.has_key('name'): - id = make_id(node['name']) + if not node['ids']: + for name in node['names']: + id = self.settings.id_prefix + make_id(name) + if id and not self.ids.has_key(id): + break else: id = '' - while not id or self.ids.has_key(id): - id = 'id%s' % self.id_start - self.id_start += 1 - node['id'] = id + while not id or self.ids.has_key(id): + id = (self.settings.id_prefix + + self.settings.auto_id_prefix + str(self.id_start)) + self.id_start += 1 + node['ids'].append(id) self.ids[id] = node return id @@ -775,8 +885,7 @@ both old and new targets are external and refer to identical URIs. The new target is invalidated regardless. """ - if node.has_key('name'): - name = node['name'] + for name in node['names']: if self.nameids.has_key(name): self.set_duplicate_name_id(node, id, name, msgnode, explicit) else: @@ -794,30 +903,30 @@ old_node = self.ids[old_id] if node.has_key('refuri'): refuri = node['refuri'] - if old_node.has_key('name') \ + if old_node['names'] \ and old_node.has_key('refuri') \ and old_node['refuri'] == refuri: level = 1 # just inform if refuri's identical if level > 1: - dupname(old_node) + dupname(old_node, name) self.nameids[name] = None msg = self.reporter.system_message( level, 'Duplicate explicit target name: "%s".' % name, backrefs=[id], base_node=node) if msgnode != None: msgnode += msg - dupname(node) + dupname(node, name) else: self.nameids[name] = id if old_id is not None: old_node = self.ids[old_id] - dupname(old_node) + dupname(old_node, name) else: if old_id is not None and not old_explicit: self.nameids[name] = None old_node = self.ids[old_id] - dupname(old_node) - dupname(node) + dupname(old_node, name) + dupname(node, name) if not explicit or (not old_explicit and old_id is not None): msg = self.reporter.info( 'Duplicate implicit target name: "%s".' % name, @@ -851,7 +960,7 @@ def note_indirect_target(self, target): self.indirect_targets.append(target) - if target.has_key('name'): + if target['names']: self.note_refname(target) def note_anonymous_target(self, target): @@ -895,7 +1004,8 @@ self.note_refname(ref) def note_substitution_def(self, subdef, def_name, msgnode=None): - name = subdef['name'] = whitespace_normalize_name(def_name) + name = whitespace_normalize_name(def_name) + subdef['names'].append(name) if self.substitution_defs.has_key(name): msg = self.reporter.error( 'Duplicate substitution definition name: "%s".' % name, @@ -903,7 +1013,7 @@ if msgnode != None: msgnode += msg oldnode = self.substitution_defs[name] - dupname(oldnode) + dupname(oldnode, name) # keep only the last definition: self.substitution_defs[name] = subdef # case-insensitive mapping: @@ -933,6 +1043,16 @@ return self.__class__(self.settings, self.reporter, **self.attributes) + def get_decoration(self): + if not self.decoration: + self.decoration = decoration() + index = self.first_child_not_matching_class(Titular) + if index is None: + self.append(self.decoration) + else: + self.insert(index, self.decoration) + return self.decoration + # ================ # Title Elements @@ -964,7 +1084,19 @@ # Decorative Elements # ===================== -class decoration(Decorative, Element): pass +class decoration(Decorative, Element): + + def get_header(self): + if not len(self.children) or not isinstance(self.children[0], header): + self.insert(0, header()) + return self.children[0] + + def get_footer(self): + if not len(self.children) or not isinstance(self.children[-1], footer): + self.append(footer()) + return self.children[-1] + + class header(Decorative, Element): pass class footer(Decorative, Element): pass @@ -1061,7 +1193,7 @@ class line_block(General, Element): pass -class line(General, TextElement): +class line(Part, TextElement): indent = None @@ -1081,8 +1213,8 @@ class comment(Special, Invisible, FixedTextElement): pass class substitution_definition(Special, Invisible, TextElement): pass class target(Special, Invisible, Inline, TextElement, Targetable): pass -class footnote(General, Element, Labeled, BackLinkable): pass -class citation(General, Element, Labeled, BackLinkable): pass +class footnote(General, BackLinkable, Element, Labeled, Targetable): pass +class citation(General, BackLinkable, Element, Labeled, Targetable): pass class label(Part, TextElement): pass class figure(General, Element): pass class caption(Part, TextElement): pass @@ -1096,7 +1228,7 @@ class entry(Part, Element): pass -class system_message(Special, PreBibliographic, Element, BackLinkable): +class system_message(Special, BackLinkable, PreBibliographic, Element): def __init__(self, message=None, *children, **attributes): if message: @@ -1210,7 +1342,7 @@ class subscript(Inline, TextElement): pass -class image(General, Inline, TextElement): +class image(General, Inline, Element): def astext(self): return self.get('alt', '') @@ -1306,8 +1438,8 @@ node_name = node.__class__.__name__ method = getattr(self, 'visit_' + node_name, self.unknown_visit) self.document.reporter.debug( - 'calling %s for %s' % (method.__name__, node_name), - category='nodes.NodeVisitor.dispatch_visit') + 'docutils.nodes.NodeVisitor.dispatch_visit calling %s for %s' + % (method.__name__, node_name)) return method(node) def dispatch_departure(self, node): @@ -1319,8 +1451,8 @@ node_name = node.__class__.__name__ method = getattr(self, 'depart_' + node_name, self.unknown_departure) self.document.reporter.debug( - 'calling %s for %s' % (method.__name__, node_name), - category='nodes.NodeVisitor.dispatch_departure') + 'docutils.nodes.NodeVisitor.dispatch_departure calling %s for %s' + % (method.__name__, node_name)) return method(node) def unknown_visit(self, node): @@ -1357,6 +1489,7 @@ subclasses), subclass `NodeVisitor` instead. """ + class GenericNodeVisitor(NodeVisitor): """ @@ -1398,10 +1531,11 @@ setattr(GenericNodeVisitor, "visit_" + _name, _call_default_visit) setattr(GenericNodeVisitor, "depart_" + _name, _call_default_departure) setattr(SparseNodeVisitor, 'visit_' + _name, _nop) - setattr(SparseNodeVisitor, 'depart' + _name, _nop) + setattr(SparseNodeVisitor, 'depart_' + _name, _nop) _add_node_class_names(node_class_names) + class TreeCopyVisitor(GenericNodeVisitor): """ @@ -1534,9 +1668,12 @@ _non_id_chars = re.compile('[^a-z0-9]+') _non_id_at_ends = re.compile('^[-0-9]+|-+$') -def dupname(node): - node['dupname'] = node['name'] - del node['name'] +def dupname(node, name): + node['dupnames'].append(name) + node['names'].remove(name) + # Assume that this method is referenced, even though it isn't; we + # don't want to throw unnecessary system_messages. + node.referenced = 1 def fully_normalize_name(name): """Return a case- and whitespace-normalized name.""" === Zope/lib/python/docutils/statemachine.py 1.2.10.7 => 1.2.10.8 === === Zope/lib/python/docutils/utils.py 1.2.10.7 => 1.2.10.8 === --- Zope/lib/python/docutils/utils.py:1.2.10.7 Fri Jan 7 08:26:02 2005 +++ Zope/lib/python/docutils/utils.py Sun Oct 9 10:43:43 2005 @@ -13,6 +13,7 @@ import sys import os import os.path +import warnings from types import StringType, UnicodeType from docutils import ApplicationError, DataError from docutils import frontend, nodes @@ -39,27 +40,14 @@ There is typically one Reporter object per process. A Reporter object is instantiated with thresholds for reporting (generating warnings) and halting processing (raising exceptions), a switch to turn debug output on - or off, and an I/O stream for warnings. These are stored in the default - reporting category, '' (zero-length string). + or off, and an I/O stream for warnings. These are stored as instance + attributes. - Multiple reporting categories [#]_ may be set, each with its own reporting - and halting thresholds, debugging switch, and warning stream - (collectively a `ConditionSet`). Categories are hierarchical dotted-name - strings that look like attribute references: 'spam', 'spam.eggs', - 'neeeow.wum.ping'. The 'spam' category is the ancestor of - 'spam.bacon.eggs'. Unset categories inherit stored conditions from their - closest ancestor category that has been set. - - When a system message is generated, the stored conditions from its - category (or ancestor if unset) are retrieved. The system message level - is compared to the thresholds stored in the category, and a warning or - error is generated as appropriate. Debug messages are produced iff the - stored debug switch is on. Message output is sent to the stored warning - stream if not set to ''. - - The default category is '' (empty string). By convention, Writers should - retrieve reporting conditions from the 'writer' category (which, unless - explicitly set, defaults to the conditions of the default category). + When a system message is generated, its level is compared to the stored + thresholds, and a warning or error is generated as appropriate. Debug + messages are produced iff the stored debug switch is on, independently of + other thresholds. Message output is sent to the stored warning stream if + not set to ''. The Reporter class also employs a modified form of the "Observer" pattern [GoF95]_ to track system messages generated. The `attach_observer` method @@ -67,9 +55,6 @@ accepts system messages. The observer can be removed with `detach_observer`, and another added in its place. - .. [#] The concept of "categories" was inspired by the log4j project: - http://jakarta.apache.org/log4j/. - .. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA, 1995. @@ -81,10 +66,7 @@ def __init__(self, source, report_level, halt_level, stream=None, debug=0, encoding='ascii', error_handler='replace'): """ - Initialize the `ConditionSet` forthe `Reporter`'s default category. - :Parameters: - - `source`: The path to or description of the source data. - `report_level`: The level at or above which warning output will be sent to `stream`. @@ -101,6 +83,23 @@ self.source = source """The path to or description of the source data.""" + self.encoding = encoding + """The character encoding for the stderr output.""" + + self.error_handler = error_handler + """The character encoding error handler.""" + + self.debug_flag = debug + """Show debug (level=0) system messages?""" + + self.report_level = report_level + """The level at or above which warning output will be sent + to `self.stream`.""" + + self.halt_level = halt_level + """The level at or above which `SystemMessage` exceptions + will be raised, halting execution.""" + if stream is None: stream = sys.stderr elif type(stream) in (StringType, UnicodeType): @@ -111,15 +110,8 @@ elif type(stream) == UnicodeType: stream = open(stream.encode(), 'w') - self.encoding = encoding - """The character encoding for the stderr output.""" - - self.error_handler = error_handler - """The character encoding error handler.""" - - self.categories = {'': ConditionSet(debug, report_level, halt_level, - stream)} - """Mapping of category names to conditions. Default category is ''.""" + self.stream = stream + """Where warning output is sent.""" self.observers = [] """List of bound methods or functions to call with each system_message @@ -130,23 +122,15 @@ def set_conditions(self, category, report_level, halt_level, stream=None, debug=0): + warnings.warn('docutils.utils.Reporter.set_conditions deprecated; ' + 'set attributes via configuration settings or directly', + DeprecationWarning, stacklevel=2) + self.report_level = report_level + self.halt_level = halt_level if stream is None: stream = sys.stderr - self.categories[category] = ConditionSet(debug, report_level, - halt_level, stream) - - def unset_conditions(self, category): - if category and self.categories.has_key(category): - del self.categories[category] - - __delitem__ = unset_conditions - - def get_conditions(self, category): - while not self.categories.has_key(category): - category = category[:category.rfind('.') + 1][:-1] - return self.categories[category] - - __getitem__ = get_conditions + self.stream = stream + self.debug = debug def attach_observer(self, observer): """ @@ -169,9 +153,6 @@ Raise an exception or generate a warning if appropriate. """ attributes = kwargs.copy() - category = kwargs.get('category', '') - if kwargs.has_key('category'): - del attributes['category'] if kwargs.has_key('base_node'): source, line = get_source_line(kwargs['base_node']) del attributes['base_node'] @@ -183,16 +164,13 @@ msg = nodes.system_message(message, level=level, type=self.levels[level], *children, **attributes) - debug, report_level, halt_level, stream = self[category].astuple() - if (level >= report_level or debug and level == 0) and stream: + if self.stream and (level >= self.report_level + or self.debug_flag and level == 0): msgtext = msg.astext().encode(self.encoding, self.error_handler) - if category: - print >>stream, msgtext, '[%s]' % category - else: - print >>stream, msgtext - if level >= halt_level: + print >>self.stream, msgtext + if level >= self.halt_level: raise SystemMessage(msg, level) - if level > 0 or debug: + if level > 0 or self.debug_flag: self.notify_observers(msg) self.max_level = max(level, self.max_level) return msg @@ -203,7 +181,8 @@ effect on the processing. Level-0 system messages are handled separately from the others. """ - return self.system_message(0, *args, **kwargs) + if self.debug_flag: + return self.system_message(0, *args, **kwargs) def info(self, *args, **kwargs): """ @@ -235,25 +214,6 @@ return self.system_message(4, *args, **kwargs) -class ConditionSet: - - """ - A set of two thresholds (`report_level` & `halt_level`), a switch - (`debug`), and an I/O stream (`stream`), corresponding to one `Reporter` - category. - """ - - def __init__(self, debug, report_level, halt_level, stream): - self.debug = debug - self.report_level = report_level - self.halt_level = halt_level - self.stream = stream - - def astuple(self): - return (self.debug, self.report_level, self.halt_level, - self.stream) - - class ExtensionOptionError(DataError): pass class BadOptionError(ExtensionOptionError): pass class BadOptionDataError(ExtensionOptionError): pass @@ -346,7 +306,7 @@ options[name] = convertor(value) except (ValueError, TypeError), detail: raise detail.__class__('(option: "%s"; value: %r)\n%s' - % (name, value, detail)) + % (name, value, ' '.join(detail.args))) return options _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org http://mail.zope.org/mailman/listinfo/zope-checkins