~ubuntu-branches/ubuntu/hardy/python-docutils/hardy

« back to all changes in this revision

Viewing changes to docutils/utils.py

  • Committer: Bazaar Package Importer
  • Author(s): martin f. krafft
  • Date: 2006-07-10 11:45:05 UTC
  • mfrom: (2.1.4 edgy)
  • Revision ID: james.westby@ubuntu.com-20060710114505-otkhqcslevewxmz5
Tags: 0.4-3
Added build dependency on python-central (closes: #377580).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Author: David Goodger
2
2
# Contact: goodger@users.sourceforge.net
3
 
# Revision: $Revision: 1.31 $
4
 
# Date: $Date: 2004/04/27 19:51:07 $
 
3
# Revision: $Revision: 4152 $
 
4
# Date: $Date: 2005-12-08 00:46:30 +0100 (Thu, 08 Dec 2005) $
5
5
# Copyright: This module has been placed in the public domain.
6
6
 
7
7
"""
13
13
import sys
14
14
import os
15
15
import os.path
 
16
import types
 
17
import warnings
 
18
import unicodedata
16
19
from types import StringType, UnicodeType
17
20
from docutils import ApplicationError, DataError
18
21
from docutils import frontend, nodes
25
28
        self.level = level
26
29
 
27
30
 
 
31
class SystemMessagePropagation(ApplicationError): pass
 
32
 
 
33
 
28
34
class Reporter:
29
35
 
30
36
    """
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).
41
 
 
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.
49
 
 
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
55
 
    stream.
56
 
 
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
 
46
    attributes.
 
47
 
 
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
 
52
    not set to ''.
60
53
 
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.
66
59
 
67
 
    .. [#] The concept of "categories" was inspired by the log4j project:
68
 
       http://jakarta.apache.org/log4j/.
69
 
 
70
60
    .. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of
71
61
       Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA,
72
62
       1995.
78
68
    def __init__(self, source, report_level, halt_level, stream=None,
79
69
                 debug=0, encoding='ascii', error_handler='replace'):
80
70
        """
81
 
        Initialize the `ConditionSet` forthe `Reporter`'s default category.
82
 
 
83
71
        :Parameters:
84
 
 
85
72
            - `source`: The path to or description of the source data.
86
73
            - `report_level`: The level at or above which warning output will
87
74
              be sent to `stream`.
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.
96
84
        """
 
85
 
97
86
        self.source = source
98
87
        """The path to or description of the source data."""
99
 
        
100
 
        if stream is None:
101
 
            stream = sys.stderr
102
 
        elif type(stream) in (StringType, UnicodeType):
103
 
            raise NotImplementedError('This should open a file for writing.')
104
88
 
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."""
110
94
 
111
 
        self.categories = {'': ConditionSet(debug, report_level, halt_level,
112
 
                                            stream)}
113
 
        """Mapping of category names to conditions. Default category is ''."""
 
95
        self.debug_flag = debug
 
96
        """Show debug (level=0) system messages?"""
 
97
 
 
98
        self.report_level = report_level
 
99
        """The level at or above which warning output will be sent
 
100
        to `self.stream`."""
 
101
 
 
102
        self.halt_level = halt_level
 
103
        """The level at or above which `SystemMessage` exceptions
 
104
        will be raised, halting execution."""
 
105
 
 
106
        if stream is None:
 
107
            stream = sys.stderr
 
108
        elif type(stream) in (StringType, UnicodeType):
 
109
            # Leave stream untouched if it's ''.
 
110
            if stream != '':
 
111
                if type(stream) == StringType:
 
112
                    stream = open(stream, 'w')
 
113
                elif type(stream) == UnicodeType:
 
114
                    stream = open(stream.encode(), 'w')
 
115
 
 
116
        self.stream = stream
 
117
        """Where warning output is sent."""
114
118
 
115
119
        self.observers = []
116
120
        """List of bound methods or functions to call with each system_message
121
125
 
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,
127
 
                                                 halt_level, stream)
128
 
 
129
 
    def unset_conditions(self, category):
130
 
        if category and self.categories.has_key(category):
131
 
            del self.categories[category]
132
 
 
133
 
    __delitem__ = unset_conditions
134
 
 
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]
139
 
 
140
 
    __getitem__ = get_conditions
 
135
        self.stream = stream
 
136
        self.debug_flag = debug
141
137
 
142
138
    def attach_observer(self, observer):
143
139
        """
160
156
        Raise an exception or generate a warning if appropriate.
161
157
        """
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)
180
 
            if category:
181
 
                print >>stream, msgtext, '[%s]' % category
182
 
            else:
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)
189
179
        return msg
194
184
        effect on the processing. Level-0 system messages are handled
195
185
        separately from the others.
196
186
        """
197
 
        return self.system_message(0, *args, **kwargs)
 
187
        if self.debug_flag:
 
188
            return self.system_message(0, *args, **kwargs)
198
189
 
199
190
    def info(self, *args, **kwargs):
200
191
        """
226
217
        return self.system_message(4, *args, **kwargs)
227
218
 
228
219
 
229
 
class ConditionSet:
230
 
 
231
 
    """
232
 
    A set of two thresholds (`report_level` & `halt_level`), a switch
233
 
    (`debug`), and an I/O stream (`stream`), corresponding to one `Reporter`
234
 
    category.
235
 
    """
236
 
 
237
 
    def __init__(self, debug, report_level, halt_level, stream):
238
 
        self.debug = debug
239
 
        self.report_level = report_level
240
 
        self.halt_level = halt_level
241
 
        self.stream = stream
242
 
 
243
 
    def astuple(self):
244
 
        return (self.debug, self.report_level, self.halt_level,
245
 
                self.stream)
246
 
 
247
 
 
248
220
class ExtensionOptionError(DataError): pass
249
221
class BadOptionError(ExtensionOptionError): pass
250
222
class BadOptionDataError(ExtensionOptionError): pass
265
237
        - `KeyError` for unknown option names.
266
238
        - `ValueError` for invalid option values (raised by the conversion
267
239
           function).
 
240
        - `TypeError` for invalid option value types (raised by conversion
 
241
           function).
268
242
        - `DuplicateOptionError` for duplicate options.
269
243
        - `BadOptionError` for invalid fields.
270
244
        - `BadOptionDataError` for invalid option data (missing name,
321
295
        - `DuplicateOptionError` for duplicate options.
322
296
        - `ValueError` for invalid option values (raised by conversion
323
297
           function).
 
298
        - `TypeError` for invalid option value types (raised by conversion
 
299
           function).
324
300
    """
325
301
    options = {}
326
302
    for name, value in option_list:
333
309
            options[name] = convertor(value)
334
310
        except (ValueError, TypeError), detail:
335
311
            raise detail.__class__('(option: "%s"; value: %r)\n%s'
336
 
                                   % (name, value, detail))
 
312
                                   % (name, value, ' '.join(detail.args)))
337
313
    return options
338
314
 
339
315
 
384
360
        attlist.append((attname.lower(), data))
385
361
    return attlist
386
362
 
387
 
def new_document(source, settings=None):
 
363
def new_reporter(source_path, settings):
 
364
    """
 
365
    Return a new Reporter object.
 
366
 
 
367
    :Parameters:
 
368
        `source` : string
 
369
            The path to or description of the source text of the document.
 
370
        `settings` : optparse.Values object
 
371
            Runtime settings.
 
372
    """
 
373
    reporter = Reporter(
 
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)
 
378
    return reporter
 
379
 
 
380
def new_document(source_path, settings=None):
388
381
    """
389
382
    Return a new empty document object.
390
383
 
396
389
    """
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)
405
395
    return document
406
396
 
407
397
def clean_rcs_keywords(paragraph, keyword_substitutions):
437
427
    parts = ['..'] * (len(source_parts) - 1) + target_parts
438
428
    return '/'.join(parts)
439
429
 
 
430
def get_stylesheet_reference(settings, relative_to=None):
 
431
    """
 
432
    Retrieve a stylesheet reference from the settings object.
 
433
    """
 
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)
 
440
    else:
 
441
        return settings.stylesheet
 
442
 
 
443
def get_trim_footnote_ref_space(settings):
 
444
    """
 
445
    Return whether or not to trim footnote space.
 
446
 
 
447
    If trim_footnote_reference_space is not None, return it.
 
448
 
 
449
    If trim_footnote_reference_space is None, return False unless the
 
450
    footnote reference style is 'superscript'.
 
451
    """
 
452
    if settings.trim_footnote_reference_space is None:
 
453
        return hasattr(settings, 'footnote_references') and \
 
454
               settings.footnote_references == 'superscript'
 
455
    else:
 
456
        return settings.trim_footnote_reference_space
 
457
 
440
458
def get_source_line(node):
441
459
    """
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
 
468
 
 
469
def escape2null(text):
 
470
    """Return a string with escape-backslashes converted to nulls."""
 
471
    parts = []
 
472
    start = 0
 
473
    while 1:
 
474
        found = text.find('\\', start)
 
475
        if found == -1:
 
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
 
481
 
 
482
def unescape(text, restore_backslashes=0):
 
483
    """
 
484
    Return a string with nulls removed or restored to backslashes.
 
485
    Backslash-escaped spaces are also removed.
 
486
    """
 
487
    if restore_backslashes:
 
488
        return text.replace('\x00', '\\')
 
489
    else:
 
490
        for sep in ['\x00 ', '\x00\n', '\x00']:
 
491
            text = ''.join(text.split(sep))
 
492
        return text
 
493
 
 
494
east_asian_widths = {'W': 2,   # Wide
 
495
                     'F': 2,   # Full-width (wide)
 
496
                     'Na': 1,  # Narrow
 
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
 
502
column widths."""
 
503
 
 
504
def east_asian_column_width(text):
 
505
    if isinstance(text, types.UnicodeType):
 
506
        total = 0
 
507
        for c in text:
 
508
            total += east_asian_widths[unicodedata.east_asian_width(c)]
 
509
        return total
 
510
    else:
 
511
        return len(text)
 
512
 
 
513
if hasattr(unicodedata, 'east_asian_width'):
 
514
    column_width = east_asian_column_width
 
515
else:
 
516
    column_width = len
 
517
 
 
518
 
 
519
class DependencyList:
 
520
 
 
521
    """
 
522
    List of dependencies, with file recording support.
 
523
 
 
524
    Note that the output file is not automatically closed.  You have
 
525
    to explicitly call the close() method.
 
526
    """
 
527
 
 
528
    def __init__(self, output_file=None, dependencies=[]):
 
529
        """
 
530
        Initialize the dependency list, automatically setting the
 
531
        output file to `output_file` (see `set_output()`) and adding
 
532
        all supplied dependencies.
 
533
        """
 
534
        self.set_output(output_file)
 
535
        for i in dependencies:
 
536
            self.add(i)
 
537
 
 
538
    def set_output(self, output_file):
 
539
        """
 
540
        Set the output file and clear the list of already added
 
541
        dependencies.
 
542
 
 
543
        `output_file` must be a string.  The specified file is
 
544
        immediately overwritten.
 
545
 
 
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().
 
548
        """
 
549
        self.list = []
 
550
        if output_file == '-':
 
551
            self.file = sys.stdout
 
552
        elif output_file:
 
553
            self.file = open(output_file, 'w')
 
554
        else:
 
555
            self.file = None
 
556
 
 
557
    def add(self, filename):
 
558
        """
 
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
 
561
        is not None.
 
562
        """
 
563
        if not filename in self.list:
 
564
            self.list.append(filename)
 
565
            if self.file is not None:
 
566
                print >>self.file, filename
 
567
 
 
568
    def close(self):
 
569
        """
 
570
        Close the output file.
 
571
        """
 
572
        self.file.close()
 
573
        self.file = None
 
574
 
 
575
    def __repr__(self):
 
576
        if self.file:
 
577
            output_file = self.file.name
 
578
        else:
 
579
            output_file = None
 
580
        return '%s(%r, %s)' % (self.__class__.__name__, output_file, self.list)