36
42
There is typically one Reporter object per process. A Reporter object is
37
43
instantiated with thresholds for reporting (generating warnings) and
38
44
halting processing (raising exceptions), a switch to turn debug output on
39
or off, and an I/O stream for warnings. These are stored in the default
40
reporting category, '' (zero-length string).
42
Multiple reporting categories [#]_ may be set, each with its own reporting
43
and halting thresholds, debugging switch, and warning stream
44
(collectively a `ConditionSet`). Categories are hierarchical dotted-name
45
strings that look like attribute references: 'spam', 'spam.eggs',
46
'neeeow.wum.ping'. The 'spam' category is the ancestor of
47
'spam.bacon.eggs'. Unset categories inherit stored conditions from their
48
closest ancestor category that has been set.
50
When a system message is generated, the stored conditions from its
51
category (or ancestor if unset) are retrieved. The system message level
52
is compared to the thresholds stored in the category, and a warning or
53
error is generated as appropriate. Debug messages are produced iff the
54
stored debug switch is on. Message output is sent to the stored warning
57
The default category is '' (empty string). By convention, Writers should
58
retrieve reporting conditions from the 'writer' category (which, unless
59
explicitly set, defaults to the conditions of the default category).
45
or off, and an I/O stream for warnings. These are stored as instance
48
When a system message is generated, its level is compared to the stored
49
thresholds, and a warning or error is generated as appropriate. Debug
50
messages are produced iff the stored debug switch is on, independently of
51
other thresholds. Message output is sent to the stored warning stream if
61
54
The Reporter class also employs a modified form of the "Observer" pattern
62
55
[GoF95]_ to track system messages generated. The `attach_observer` method
64
57
accepts system messages. The observer can be removed with
65
58
`detach_observer`, and another added in its place.
67
.. [#] The concept of "categories" was inspired by the log4j project:
68
http://jakarta.apache.org/log4j/.
70
60
.. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of
71
61
Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA,
89
76
exceptions will be raised, halting execution.
90
77
- `debug`: Show debug (level=0) system messages?
91
78
- `stream`: Where warning output is sent. Can be file-like (has a
92
``.write`` method), a string (file name, opened for writing), or
79
``.write`` method), a string (file name, opened for writing),
80
'' (empty string, for discarding all stream messages) or
93
81
`None` (implies `sys.stderr`; default).
94
82
- `encoding`: The encoding for stderr output.
95
83
- `error_handler`: The error handler for stderr output encoding.
97
86
self.source = source
98
87
"""The path to or description of the source data."""
102
elif type(stream) in (StringType, UnicodeType):
103
raise NotImplementedError('This should open a file for writing.')
105
89
self.encoding = encoding
106
90
"""The character encoding for the stderr output."""
108
92
self.error_handler = error_handler
109
93
"""The character encoding error handler."""
111
self.categories = {'': ConditionSet(debug, report_level, halt_level,
113
"""Mapping of category names to conditions. Default category is ''."""
95
self.debug_flag = debug
96
"""Show debug (level=0) system messages?"""
98
self.report_level = report_level
99
"""The level at or above which warning output will be sent
102
self.halt_level = halt_level
103
"""The level at or above which `SystemMessage` exceptions
104
will be raised, halting execution."""
108
elif type(stream) in (StringType, UnicodeType):
109
# Leave stream untouched if it's ''.
111
if type(stream) == StringType:
112
stream = open(stream, 'w')
113
elif type(stream) == UnicodeType:
114
stream = open(stream.encode(), 'w')
117
"""Where warning output is sent."""
115
119
self.observers = []
116
120
"""List of bound methods or functions to call with each system_message
122
126
def set_conditions(self, category, report_level, halt_level,
123
127
stream=None, debug=0):
128
warnings.warn('docutils.utils.Reporter.set_conditions deprecated; '
129
'set attributes via configuration settings or directly',
130
DeprecationWarning, stacklevel=2)
131
self.report_level = report_level
132
self.halt_level = halt_level
124
133
if stream is None:
125
134
stream = sys.stderr
126
self.categories[category] = ConditionSet(debug, report_level,
129
def unset_conditions(self, category):
130
if category and self.categories.has_key(category):
131
del self.categories[category]
133
__delitem__ = unset_conditions
135
def get_conditions(self, category):
136
while not self.categories.has_key(category):
137
category = category[:category.rfind('.') + 1][:-1]
138
return self.categories[category]
140
__getitem__ = get_conditions
136
self.debug_flag = debug
142
138
def attach_observer(self, observer):
160
156
Raise an exception or generate a warning if appropriate.
162
158
attributes = kwargs.copy()
163
category = kwargs.get('category', '')
164
if kwargs.has_key('category'):
165
del attributes['category']
166
159
if kwargs.has_key('base_node'):
167
160
source, line = get_source_line(kwargs['base_node'])
168
161
del attributes['base_node']
174
167
msg = nodes.system_message(message, level=level,
175
168
type=self.levels[level],
176
169
*children, **attributes)
177
debug, report_level, halt_level, stream = self[category].astuple()
178
if level >= report_level or debug and level == 0:
170
if self.stream and (level >= self.report_level
171
or self.debug_flag and level == 0):
179
172
msgtext = msg.astext().encode(self.encoding, self.error_handler)
181
print >>stream, msgtext, '[%s]' % category
183
print >>stream, msgtext
184
if level >= halt_level:
173
print >>self.stream, msgtext
174
if level >= self.halt_level:
185
175
raise SystemMessage(msg, level)
186
if level > 0 or debug:
176
if level > 0 or self.debug_flag:
187
177
self.notify_observers(msg)
188
178
self.max_level = max(level, self.max_level)
226
217
return self.system_message(4, *args, **kwargs)
232
A set of two thresholds (`report_level` & `halt_level`), a switch
233
(`debug`), and an I/O stream (`stream`), corresponding to one `Reporter`
237
def __init__(self, debug, report_level, halt_level, stream):
239
self.report_level = report_level
240
self.halt_level = halt_level
244
return (self.debug, self.report_level, self.halt_level,
248
220
class ExtensionOptionError(DataError): pass
249
221
class BadOptionError(ExtensionOptionError): pass
250
222
class BadOptionDataError(ExtensionOptionError): pass
384
360
attlist.append((attname.lower(), data))
387
def new_document(source, settings=None):
363
def new_reporter(source_path, settings):
365
Return a new Reporter object.
369
The path to or description of the source text of the document.
370
`settings` : optparse.Values object
374
source_path, settings.report_level, settings.halt_level,
375
stream=settings.warning_stream, debug=settings.debug,
376
encoding=settings.error_encoding,
377
error_handler=settings.error_encoding_error_handler)
380
def new_document(source_path, settings=None):
389
382
Return a new empty document object.
397
390
if settings is None:
398
391
settings = frontend.OptionParser().get_default_values()
399
reporter = Reporter(source, settings.report_level, settings.halt_level,
400
stream=settings.warning_stream, debug=settings.debug,
401
encoding=settings.error_encoding,
402
error_handler=settings.error_encoding_error_handler)
403
document = nodes.document(settings, reporter, source=source)
404
document.note_source(source, -1)
392
reporter = new_reporter(source_path, settings)
393
document = nodes.document(settings, reporter, source=source_path)
394
document.note_source(source_path, -1)
407
397
def clean_rcs_keywords(paragraph, keyword_substitutions):
437
427
parts = ['..'] * (len(source_parts) - 1) + target_parts
438
428
return '/'.join(parts)
430
def get_stylesheet_reference(settings, relative_to=None):
432
Retrieve a stylesheet reference from the settings object.
434
if settings.stylesheet_path:
435
assert not settings.stylesheet, \
436
'stylesheet and stylesheet_path are mutually exclusive.'
437
if relative_to == None:
438
relative_to = settings._destination
439
return relative_path(relative_to, settings.stylesheet_path)
441
return settings.stylesheet
443
def get_trim_footnote_ref_space(settings):
445
Return whether or not to trim footnote space.
447
If trim_footnote_reference_space is not None, return it.
449
If trim_footnote_reference_space is None, return False unless the
450
footnote reference style is 'superscript'.
452
if settings.trim_footnote_reference_space is None:
453
return hasattr(settings, 'footnote_references') and \
454
settings.footnote_references == 'superscript'
456
return settings.trim_footnote_reference_space
440
458
def get_source_line(node):
442
460
Return the "source" and "line" attributes from the `node` given or from
447
465
return node.source, node.line
448
466
node = node.parent
449
467
return None, None
469
def escape2null(text):
470
"""Return a string with escape-backslashes converted to nulls."""
474
found = text.find('\\', start)
476
parts.append(text[start:])
477
return ''.join(parts)
478
parts.append(text[start:found])
479
parts.append('\x00' + text[found+1:found+2])
480
start = found + 2 # skip character after escape
482
def unescape(text, restore_backslashes=0):
484
Return a string with nulls removed or restored to backslashes.
485
Backslash-escaped spaces are also removed.
487
if restore_backslashes:
488
return text.replace('\x00', '\\')
490
for sep in ['\x00 ', '\x00\n', '\x00']:
491
text = ''.join(text.split(sep))
494
east_asian_widths = {'W': 2, # Wide
495
'F': 2, # Full-width (wide)
497
'H': 1, # Half-width (narrow)
498
'N': 1, # Neutral (not East Asian, treated as narrow)
499
'A': 1} # Ambiguous (s/b wide in East Asian context,
500
# narrow otherwise, but that doesn't work)
501
"""Mapping of result codes from `unicodedata.east_asian_width()` to character
504
def east_asian_column_width(text):
505
if isinstance(text, types.UnicodeType):
508
total += east_asian_widths[unicodedata.east_asian_width(c)]
513
if hasattr(unicodedata, 'east_asian_width'):
514
column_width = east_asian_column_width
519
class DependencyList:
522
List of dependencies, with file recording support.
524
Note that the output file is not automatically closed. You have
525
to explicitly call the close() method.
528
def __init__(self, output_file=None, dependencies=[]):
530
Initialize the dependency list, automatically setting the
531
output file to `output_file` (see `set_output()`) and adding
532
all supplied dependencies.
534
self.set_output(output_file)
535
for i in dependencies:
538
def set_output(self, output_file):
540
Set the output file and clear the list of already added
543
`output_file` must be a string. The specified file is
544
immediately overwritten.
546
If output_file is '-', the output will be written to stdout.
547
If it is None, no file output is done when calling add().
550
if output_file == '-':
551
self.file = sys.stdout
553
self.file = open(output_file, 'w')
557
def add(self, filename):
559
If the dependency `filename` has not already been added,
560
append it to self.list and print it to self.file if self.file
563
if not filename in self.list:
564
self.list.append(filename)
565
if self.file is not None:
566
print >>self.file, filename
570
Close the output file.
577
output_file = self.file.name
580
return '%s(%r, %s)' % (self.__class__.__name__, output_file, self.list)