~ubuntu-branches/ubuntu/jaunty/python-docutils/jaunty

« back to all changes in this revision

Viewing changes to docutils/parsers/rst/directives/misc.py

  • Committer: Bazaar Package Importer
  • Author(s): Simon McVittie
  • Date: 2008-07-24 10:39:53 UTC
  • mfrom: (1.1.4 upstream) (3.1.7 intrepid)
  • Revision ID: james.westby@ubuntu.com-20080724103953-8gh4uezg17g9ysgy
Tags: 0.5-2
* Upload docutils 0.5 to unstable
* Update rst.el to upstream Subversion r5596, which apparently fixes
  all its performance problems (17_speed_up_rst_el.dpatch, closes: #474941)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Authors: David Goodger, Dethe Elza
2
 
# Contact: goodger@users.sourceforge.net
3
 
# Revision: $Revision: 4229 $
4
 
# Date: $Date: 2005-12-23 00:46:16 +0100 (Fri, 23 Dec 2005) $
 
1
# $Id: misc.py 5015 2007-03-12 20:25:40Z wiemann $
 
2
# Authors: David Goodger <goodger@python.org>; Dethe Elza
5
3
# Copyright: This module has been placed in the public domain.
6
4
 
7
5
"""Miscellaneous directives."""
13
11
import re
14
12
import time
15
13
from docutils import io, nodes, statemachine, utils
 
14
from docutils.parsers.rst import Directive, convert_directive_function
16
15
from docutils.parsers.rst import directives, roles, states
17
16
from docutils.transforms import misc
18
17
 
19
 
try:
20
 
    import urllib2
21
 
except ImportError:
22
 
    urllib2 = None
23
 
 
24
 
 
25
 
standard_include_path = os.path.join(os.path.dirname(states.__file__),
26
 
                                     'include')
27
 
 
28
 
def include(name, arguments, options, content, lineno,
29
 
            content_offset, block_text, state, state_machine):
30
 
    """Include a reST file as part of the content of this reST file."""
31
 
    if not state.document.settings.file_insertion_enabled:
32
 
        warning = state_machine.reporter.warning(
33
 
              '"%s" directive disabled.' % name,
34
 
              nodes.literal_block(block_text, block_text), line=lineno)
35
 
        return [warning]
36
 
    source = state_machine.input_lines.source(
37
 
        lineno - state_machine.input_offset - 1)
38
 
    source_dir = os.path.dirname(os.path.abspath(source))
39
 
    path = directives.path(arguments[0])
40
 
    if path.startswith('<') and  path.endswith('>'):
41
 
        path = os.path.join(standard_include_path, path[1:-1])
42
 
    path = os.path.normpath(os.path.join(source_dir, path))
43
 
    path = utils.relative_path(None, path)
44
 
    encoding = options.get('encoding', state.document.settings.input_encoding)
45
 
    try:
46
 
        state.document.settings.record_dependencies.add(path)
47
 
        include_file = io.FileInput(
48
 
            source_path=path, encoding=encoding,
49
 
            error_handler=state.document.settings.input_encoding_error_handler,
50
 
            handle_io_errors=None)
51
 
    except IOError, error:
52
 
        severe = state_machine.reporter.severe(
53
 
              'Problems with "%s" directive path:\n%s: %s.'
54
 
              % (name, error.__class__.__name__, error),
55
 
              nodes.literal_block(block_text, block_text), line=lineno)
56
 
        return [severe]
57
 
    try:
58
 
        include_text = include_file.read()
59
 
    except UnicodeError, error:
60
 
        severe = state_machine.reporter.severe(
61
 
              'Problem with "%s" directive:\n%s: %s'
62
 
              % (name, error.__class__.__name__, error),
63
 
              nodes.literal_block(block_text, block_text), line=lineno)
64
 
        return [severe]
65
 
    if options.has_key('literal'):
66
 
        literal_block = nodes.literal_block(include_text, include_text,
67
 
                                            source=path)
68
 
        literal_block.line = 1
69
 
        return literal_block
70
 
    else:
71
 
        include_lines = statemachine.string2lines(include_text,
72
 
                                                  convert_whitespace=1)
73
 
        state_machine.insert_input(include_lines, path)
74
 
        return []
75
 
 
76
 
include.arguments = (1, 0, 1)
77
 
include.options = {'literal': directives.flag,
78
 
                   'encoding': directives.encoding}
79
 
 
80
 
def raw(name, arguments, options, content, lineno,
81
 
        content_offset, block_text, state, state_machine):
 
18
 
 
19
class Include(Directive):
 
20
 
 
21
    """
 
22
    Include content read from a separate source file.
 
23
 
 
24
    Content may be parsed by the parser, or included as a literal
 
25
    block.  The encoding of the included file can be specified.  Only
 
26
    a part of the given file argument may be included by specifying
 
27
    text to match before and/or after the text to be used.
 
28
    """
 
29
 
 
30
    required_arguments = 1
 
31
    optional_arguments = 0
 
32
    final_argument_whitespace = True
 
33
    option_spec = {'literal': directives.flag,
 
34
                   'encoding': directives.encoding,
 
35
                   'start-after': directives.unchanged_required,
 
36
                   'end-before': directives.unchanged_required}
 
37
 
 
38
    standard_include_path = os.path.join(os.path.dirname(states.__file__),
 
39
                                         'include')
 
40
 
 
41
    def run(self):
 
42
        """Include a reST file as part of the content of this reST file."""
 
43
        if not self.state.document.settings.file_insertion_enabled:
 
44
            raise self.warning('"%s" directive disabled.' % self.name)
 
45
        source = self.state_machine.input_lines.source(
 
46
            self.lineno - self.state_machine.input_offset - 1)
 
47
        source_dir = os.path.dirname(os.path.abspath(source))
 
48
        path = directives.path(self.arguments[0])
 
49
        if path.startswith('<') and path.endswith('>'):
 
50
            path = os.path.join(self.standard_include_path, path[1:-1])
 
51
        path = os.path.normpath(os.path.join(source_dir, path))
 
52
        path = utils.relative_path(None, path)
 
53
        encoding = self.options.get(
 
54
            'encoding', self.state.document.settings.input_encoding)
 
55
        try:
 
56
            self.state.document.settings.record_dependencies.add(path)
 
57
            include_file = io.FileInput(
 
58
                source_path=path, encoding=encoding,
 
59
                error_handler=(self.state.document.settings.\
 
60
                               input_encoding_error_handler),
 
61
                handle_io_errors=None)
 
62
        except IOError, error:
 
63
            raise self.severe('Problems with "%s" directive path:\n%s: %s.'
 
64
                              % (self.name, error.__class__.__name__, error))
 
65
        try:
 
66
            include_text = include_file.read()
 
67
        except UnicodeError, error:
 
68
            raise self.severe(
 
69
                'Problem with "%s" directive:\n%s: %s'
 
70
                % (self.name, error.__class__.__name__, error))
 
71
        # start-after/end-before: no restrictions on newlines in match-text,
 
72
        # and no restrictions on matching inside lines vs. line boundaries
 
73
        after_text = self.options.get('start-after', None)
 
74
        if after_text:
 
75
            # skip content in include_text before *and incl.* a matching text
 
76
            after_index = include_text.find(after_text)
 
77
            if after_index < 0:
 
78
                raise self.severe('Problem with "start-after" option of "%s" '
 
79
                                  'directive:\nText not found.' % self.name)
 
80
            include_text = include_text[after_index + len(after_text):]
 
81
        before_text = self.options.get('end-before', None)
 
82
        if before_text:
 
83
            # skip content in include_text after *and incl.* a matching text
 
84
            before_index = include_text.find(before_text)
 
85
            if before_index < 0:
 
86
                raise self.severe('Problem with "end-before" option of "%s" '
 
87
                                  'directive:\nText not found.' % self.name)
 
88
            include_text = include_text[:before_index]
 
89
        if self.options.has_key('literal'):
 
90
            literal_block = nodes.literal_block(include_text, include_text,
 
91
                                                source=path)
 
92
            literal_block.line = 1
 
93
            return [literal_block]
 
94
        else:
 
95
            include_lines = statemachine.string2lines(include_text,
 
96
                                                      convert_whitespace=1)
 
97
            self.state_machine.insert_input(include_lines, path)
 
98
            return []
 
99
 
 
100
 
 
101
class Raw(Directive):
 
102
 
82
103
    """
83
104
    Pass through content unchanged
84
105
 
87
108
    Content may be included inline (content section of directive) or
88
109
    imported from a file or url.
89
110
    """
90
 
    if ( not state.document.settings.raw_enabled
91
 
         or (not state.document.settings.file_insertion_enabled
92
 
             and (options.has_key('file') or options.has_key('url'))) ):
93
 
        warning = state_machine.reporter.warning(
94
 
              '"%s" directive disabled.' % name,
95
 
              nodes.literal_block(block_text, block_text), line=lineno)
96
 
        return [warning]
97
 
    attributes = {'format': ' '.join(arguments[0].lower().split())}
98
 
    encoding = options.get('encoding', state.document.settings.input_encoding)
99
 
    if content:
100
 
        if options.has_key('file') or options.has_key('url'):
101
 
            error = state_machine.reporter.error(
102
 
                  '"%s" directive may not both specify an external file and '
103
 
                  'have content.' % name,
104
 
                  nodes.literal_block(block_text, block_text), line=lineno)
105
 
            return [error]
106
 
        text = '\n'.join(content)
107
 
    elif options.has_key('file'):
108
 
        if options.has_key('url'):
109
 
            error = state_machine.reporter.error(
110
 
                  'The "file" and "url" options may not be simultaneously '
111
 
                  'specified for the "%s" directive.' % name,
112
 
                  nodes.literal_block(block_text, block_text), line=lineno)
113
 
            return [error]
114
 
        source_dir = os.path.dirname(
115
 
            os.path.abspath(state.document.current_source))
116
 
        path = os.path.normpath(os.path.join(source_dir, options['file']))
117
 
        path = utils.relative_path(None, path)
118
 
        try:
119
 
            state.document.settings.record_dependencies.add(path)
120
 
            raw_file = io.FileInput(
121
 
                source_path=path, encoding=encoding,
122
 
                error_handler=state.document.settings.input_encoding_error_handler,
123
 
                handle_io_errors=None)
124
 
        except IOError, error:
125
 
            severe = state_machine.reporter.severe(
126
 
                  'Problems with "%s" directive path:\n%s.' % (name, error),
127
 
                  nodes.literal_block(block_text, block_text), line=lineno)
128
 
            return [severe]
129
 
        try:
130
 
            text = raw_file.read()
131
 
        except UnicodeError, error:
132
 
            severe = state_machine.reporter.severe(
133
 
                  'Problem with "%s" directive:\n%s: %s'
134
 
                  % (name, error.__class__.__name__, error),
135
 
                  nodes.literal_block(block_text, block_text), line=lineno)
136
 
            return [severe]
137
 
        attributes['source'] = path
138
 
    elif options.has_key('url'):
139
 
        if not urllib2:
140
 
            severe = state_machine.reporter.severe(
141
 
                  'Problems with the "%s" directive and its "url" option: '
142
 
                  'unable to access the required functionality (from the '
143
 
                  '"urllib2" module).' % name,
144
 
                  nodes.literal_block(block_text, block_text), line=lineno)
145
 
            return [severe]
146
 
        source = options['url']
147
 
        try:
148
 
            raw_text = urllib2.urlopen(source).read()
149
 
        except (urllib2.URLError, IOError, OSError), error:
150
 
            severe = state_machine.reporter.severe(
151
 
                  'Problems with "%s" directive URL "%s":\n%s.'
152
 
                  % (name, options['url'], error),
153
 
                  nodes.literal_block(block_text, block_text), line=lineno)
154
 
            return [severe]
155
 
        raw_file = io.StringInput(
156
 
            source=raw_text, source_path=source, encoding=encoding,
157
 
            error_handler=state.document.settings.input_encoding_error_handler)
158
 
        try:
159
 
            text = raw_file.read()
160
 
        except UnicodeError, error:
161
 
            severe = state_machine.reporter.severe(
162
 
                  'Problem with "%s" directive:\n%s: %s'
163
 
                  % (name, error.__class__.__name__, error),
164
 
                  nodes.literal_block(block_text, block_text), line=lineno)
165
 
            return [severe]
166
 
        attributes['source'] = source
167
 
    else:
168
 
        error = state_machine.reporter.warning(
169
 
            'The "%s" directive requires content; none supplied.' % (name),
170
 
            nodes.literal_block(block_text, block_text), line=lineno)
171
 
        return [error]
172
 
    raw_node = nodes.raw('', text, **attributes)
173
 
    return [raw_node]
174
 
 
175
 
raw.arguments = (1, 0, 1)
176
 
raw.options = {'file': directives.path,
177
 
               'url': directives.uri,
178
 
               'encoding': directives.encoding}
179
 
raw.content = 1
180
 
 
181
 
def replace(name, arguments, options, content, lineno,
182
 
            content_offset, block_text, state, state_machine):
183
 
    if not isinstance(state, states.SubstitutionDef):
184
 
        error = state_machine.reporter.error(
185
 
            'Invalid context: the "%s" directive can only be used within a '
186
 
            'substitution definition.' % (name),
187
 
            nodes.literal_block(block_text, block_text), line=lineno)
188
 
        return [error]
189
 
    text = '\n'.join(content)
190
 
    element = nodes.Element(text)
191
 
    if text:
192
 
        state.nested_parse(content, content_offset, element)
193
 
        if len(element) != 1 or not isinstance(element[0], nodes.paragraph):
 
111
 
 
112
    required_arguments = 1
 
113
    optional_arguments = 0
 
114
    final_argument_whitespace = True
 
115
    option_spec = {'file': directives.path,
 
116
                   'url': directives.uri,
 
117
                   'encoding': directives.encoding}
 
118
    has_content = True
 
119
 
 
120
    def run(self):
 
121
        if (not self.state.document.settings.raw_enabled
 
122
            or (not self.state.document.settings.file_insertion_enabled
 
123
                and (self.options.has_key('file')
 
124
                     or self.options.has_key('url')))):
 
125
            raise self.warning('"%s" directive disabled.' % self.name)
 
126
        attributes = {'format': ' '.join(self.arguments[0].lower().split())}
 
127
        encoding = self.options.get(
 
128
            'encoding', self.state.document.settings.input_encoding)
 
129
        if self.content:
 
130
            if self.options.has_key('file') or self.options.has_key('url'):
 
131
                raise self.error(
 
132
                    '"%s" directive may not both specify an external file '
 
133
                    'and have content.' % self.name)
 
134
            text = '\n'.join(self.content)
 
135
        elif self.options.has_key('file'):
 
136
            if self.options.has_key('url'):
 
137
                raise self.error(
 
138
                    'The "file" and "url" options may not be simultaneously '
 
139
                    'specified for the "%s" directive.' % self.name)
 
140
            source_dir = os.path.dirname(
 
141
                os.path.abspath(self.state.document.current_source))
 
142
            path = os.path.normpath(os.path.join(source_dir,
 
143
                                                 self.options['file']))
 
144
            path = utils.relative_path(None, path)
 
145
            try:
 
146
                self.state.document.settings.record_dependencies.add(path)
 
147
                raw_file = io.FileInput(
 
148
                    source_path=path, encoding=encoding,
 
149
                    error_handler=(self.state.document.settings.\
 
150
                                   input_encoding_error_handler),
 
151
                    handle_io_errors=None)
 
152
            except IOError, error:
 
153
                raise self.severe('Problems with "%s" directive path:\n%s.'
 
154
                                  % (self.name, error))
 
155
            try:
 
156
                text = raw_file.read()
 
157
            except UnicodeError, error:
 
158
                raise self.severe(
 
159
                    'Problem with "%s" directive:\n%s: %s'
 
160
                    % (self.name, error.__class__.__name__, error))
 
161
            attributes['source'] = path
 
162
        elif self.options.has_key('url'):
 
163
            source = self.options['url']
 
164
            # Do not import urllib2 at the top of the module because
 
165
            # it may fail due to broken SSL dependencies, and it takes
 
166
            # about 0.15 seconds to load.
 
167
            import urllib2
 
168
            try:
 
169
                raw_text = urllib2.urlopen(source).read()
 
170
            except (urllib2.URLError, IOError, OSError), error:
 
171
                raise self.severe(
 
172
                    'Problems with "%s" directive URL "%s":\n%s.'
 
173
                    % (self.name, self.options['url'], error))
 
174
            raw_file = io.StringInput(
 
175
                source=raw_text, source_path=source, encoding=encoding,
 
176
                error_handler=(self.state.document.settings.\
 
177
                               input_encoding_error_handler))
 
178
            try:
 
179
                text = raw_file.read()
 
180
            except UnicodeError, error:
 
181
                raise self.severe(
 
182
                    'Problem with "%s" directive:\n%s: %s'
 
183
                    % (self.name, error.__class__.__name__, error))
 
184
            attributes['source'] = source
 
185
        else:
 
186
            # This will always fail because there is no content.
 
187
            self.assert_has_content()
 
188
        raw_node = nodes.raw('', text, **attributes)
 
189
        return [raw_node]
 
190
 
 
191
 
 
192
class Replace(Directive):
 
193
 
 
194
    has_content = True
 
195
 
 
196
    def run(self):
 
197
        if not isinstance(self.state, states.SubstitutionDef):
 
198
            raise self.error(
 
199
                'Invalid context: the "%s" directive can only be used within '
 
200
                'a substitution definition.' % self.name)
 
201
        self.assert_has_content()
 
202
        text = '\n'.join(self.content)
 
203
        element = nodes.Element(text)
 
204
        self.state.nested_parse(self.content, self.content_offset,
 
205
                                element)
 
206
        if ( len(element) != 1
 
207
             or not isinstance(element[0], nodes.paragraph)):
194
208
            messages = []
195
209
            for node in element:
196
210
                if isinstance(node, nodes.system_message):
197
211
                    node['backrefs'] = []
198
212
                    messages.append(node)
199
 
            error = state_machine.reporter.error(
 
213
            error = self.state_machine.reporter.error(
200
214
                'Error in "%s" directive: may contain a single paragraph '
201
 
                'only.' % (name), line=lineno)
 
215
                'only.' % (self.name), line=self.lineno)
202
216
            messages.append(error)
203
217
            return messages
204
218
        else:
205
219
            return element[0].children
206
 
    else:
207
 
        error = state_machine.reporter.error(
208
 
            'The "%s" directive is empty; content required.' % (name),
209
 
            line=lineno)
210
 
        return [error]
211
 
 
212
 
replace.content = 1
213
 
 
214
 
def unicode_directive(name, arguments, options, content, lineno,
215
 
                      content_offset, block_text, state, state_machine):
 
220
 
 
221
 
 
222
class Unicode(Directive):
 
223
 
216
224
    r"""
217
225
    Convert Unicode character codes (numbers) to characters.  Codes may be
218
226
    decimal numbers, hexadecimal numbers (prefixed by ``0x``, ``x``, ``\x``,
220
228
    entities (e.g. ``&#x262E;``).  Text following ".." is a comment and is
221
229
    ignored.  Spaces are ignored, and any other text remains as-is.
222
230
    """
223
 
    if not isinstance(state, states.SubstitutionDef):
224
 
        error = state_machine.reporter.error(
225
 
            'Invalid context: the "%s" directive can only be used within a '
226
 
            'substitution definition.' % (name),
227
 
            nodes.literal_block(block_text, block_text), line=lineno)
228
 
        return [error]
229
 
    substitution_definition = state_machine.node
230
 
    if options.has_key('trim'):
231
 
        substitution_definition.attributes['ltrim'] = 1
232
 
        substitution_definition.attributes['rtrim'] = 1
233
 
    if options.has_key('ltrim'):
234
 
        substitution_definition.attributes['ltrim'] = 1
235
 
    if options.has_key('rtrim'):
236
 
        substitution_definition.attributes['rtrim'] = 1
237
 
    codes = unicode_comment_pattern.split(arguments[0])[0].split()
238
 
    element = nodes.Element()
239
 
    for code in codes:
240
 
        try:
241
 
            decoded = directives.unicode_code(code)
242
 
        except ValueError, err:
243
 
            error = state_machine.reporter.error(
244
 
                'Invalid character code: %s\n%s: %s'
245
 
                % (code, err.__class__.__name__, err),
246
 
                nodes.literal_block(block_text, block_text), line=lineno)
247
 
            return [error]
248
 
        element += nodes.Text(decoded)
249
 
    return element.children
250
 
 
251
 
unicode_directive.arguments = (1, 0, 1)
252
 
unicode_directive.options = {'trim': directives.flag,
253
 
                             'ltrim': directives.flag,
254
 
                             'rtrim': directives.flag}
255
 
unicode_comment_pattern = re.compile(r'( |\n|^)\.\. ')
256
 
 
257
 
def class_directive(name, arguments, options, content, lineno,
258
 
                       content_offset, block_text, state, state_machine):
 
231
 
 
232
    required_arguments = 1
 
233
    optional_arguments = 0
 
234
    final_argument_whitespace = True
 
235
    option_spec = {'trim': directives.flag,
 
236
                   'ltrim': directives.flag,
 
237
                   'rtrim': directives.flag}
 
238
 
 
239
    comment_pattern = re.compile(r'( |\n|^)\.\. ')
 
240
 
 
241
    def run(self):
 
242
        if not isinstance(self.state, states.SubstitutionDef):
 
243
            raise self.error(
 
244
                'Invalid context: the "%s" directive can only be used within '
 
245
                'a substitution definition.' % self.name)
 
246
        substitution_definition = self.state_machine.node
 
247
        if self.options.has_key('trim'):
 
248
            substitution_definition.attributes['ltrim'] = 1
 
249
            substitution_definition.attributes['rtrim'] = 1
 
250
        if self.options.has_key('ltrim'):
 
251
            substitution_definition.attributes['ltrim'] = 1
 
252
        if self.options.has_key('rtrim'):
 
253
            substitution_definition.attributes['rtrim'] = 1
 
254
        codes = self.comment_pattern.split(self.arguments[0])[0].split()
 
255
        element = nodes.Element()
 
256
        for code in codes:
 
257
            try:
 
258
                decoded = directives.unicode_code(code)
 
259
            except ValueError, err:
 
260
                raise self.error(
 
261
                    'Invalid character code: %s\n%s: %s'
 
262
                    % (code, err.__class__.__name__, err))
 
263
            element += nodes.Text(decoded)
 
264
        return element.children
 
265
 
 
266
 
 
267
class Class(Directive):
 
268
 
259
269
    """
260
270
    Set a "class" attribute on the directive content or the next element.
261
271
    When applied to the next element, a "pending" element is inserted, and a
262
272
    transform does the work later.
263
273
    """
264
 
    try:
265
 
        class_value = directives.class_option(arguments[0])
266
 
    except ValueError:
267
 
        error = state_machine.reporter.error(
268
 
            'Invalid class attribute value for "%s" directive: "%s".'
269
 
            % (name, arguments[0]),
270
 
            nodes.literal_block(block_text, block_text), line=lineno)
271
 
        return [error]
272
 
    node_list = []
273
 
    if content:
274
 
        container = nodes.Element()
275
 
        state.nested_parse(content, content_offset, container)
276
 
        for node in container:
277
 
            node['classes'].extend(class_value)
278
 
        node_list.extend(container.children)
279
 
    else:
280
 
        pending = nodes.pending(misc.ClassAttribute,
281
 
                                {'class': class_value, 'directive': name},
282
 
                                block_text)
283
 
        state_machine.document.note_pending(pending)
284
 
        node_list.append(pending)
285
 
    return node_list
286
 
 
287
 
class_directive.arguments = (1, 0, 1)
288
 
class_directive.content = 1
289
 
 
290
 
role_arg_pat = re.compile(r'(%s)\s*(\(\s*(%s)\s*\)\s*)?$'
291
 
                          % ((states.Inliner.simplename,) * 2))
292
 
def role(name, arguments, options, content, lineno,
293
 
         content_offset, block_text, state, state_machine):
294
 
    """Dynamically create and register a custom interpreted text role."""
295
 
    if content_offset > lineno or not content:
296
 
        error = state_machine.reporter.error(
297
 
            '"%s" directive requires arguments on the first line.'
298
 
            % name, nodes.literal_block(block_text, block_text), line=lineno)
299
 
        return [error]
300
 
    args = content[0]
301
 
    match = role_arg_pat.match(args)
302
 
    if not match:
303
 
        error = state_machine.reporter.error(
304
 
            '"%s" directive arguments not valid role names: "%s".'
305
 
            % (name, args), nodes.literal_block(block_text, block_text),
306
 
            line=lineno)
307
 
        return [error]
308
 
    new_role_name = match.group(1)
309
 
    base_role_name = match.group(3)
310
 
    messages = []
311
 
    if base_role_name:
312
 
        base_role, messages = roles.role(
313
 
            base_role_name, state_machine.language, lineno, state.reporter)
314
 
        if base_role is None:
315
 
            error = state.reporter.error(
316
 
                'Unknown interpreted text role "%s".' % base_role_name,
317
 
                nodes.literal_block(block_text, block_text), line=lineno)
318
 
            return messages + [error]
319
 
    else:
320
 
        base_role = roles.generic_custom_role
321
 
    assert not hasattr(base_role, 'arguments'), (
322
 
        'Supplemental directive arguments for "%s" directive not supported'
323
 
        '(specified by "%r" role).' % (name, base_role))
324
 
    try:
325
 
        (arguments, options, content, content_offset) = (
326
 
            state.parse_directive_block(content[1:], content_offset, base_role,
327
 
                                        option_presets={}))
328
 
    except states.MarkupError, detail:
329
 
        error = state_machine.reporter.error(
330
 
            'Error in "%s" directive:\n%s.' % (name, detail),
331
 
            nodes.literal_block(block_text, block_text), line=lineno)
332
 
        return messages + [error]
333
 
    if not options.has_key('class'):
334
 
        try:
335
 
            options['class'] = directives.class_option(new_role_name)
336
 
        except ValueError, detail:
337
 
            error = state_machine.reporter.error(
338
 
                'Invalid argument for "%s" directive:\n%s.'
339
 
                % (name, detail),
340
 
                nodes.literal_block(block_text, block_text), line=lineno)
341
 
            return messages + [error]
342
 
    role = roles.CustomRole(new_role_name, base_role, options, content)
343
 
    roles.register_local_role(new_role_name, role)
344
 
    return messages
345
 
 
346
 
role.content = 1
347
 
 
348
 
def default_role(name, arguments, options, content, lineno,
349
 
                 content_offset, block_text, state, state_machine):
 
274
 
 
275
    required_arguments = 1
 
276
    optional_arguments = 0
 
277
    final_argument_whitespace = True
 
278
    has_content = True
 
279
 
 
280
    def run(self):
 
281
        try:
 
282
            class_value = directives.class_option(self.arguments[0])
 
283
        except ValueError:
 
284
            raise self.error(
 
285
                'Invalid class attribute value for "%s" directive: "%s".'
 
286
                % (self.name, self.arguments[0]))
 
287
        node_list = []
 
288
        if self.content:
 
289
            container = nodes.Element()
 
290
            self.state.nested_parse(self.content, self.content_offset,
 
291
                                    container)
 
292
            for node in container:
 
293
                node['classes'].extend(class_value)
 
294
            node_list.extend(container.children)
 
295
        else:
 
296
            pending = nodes.pending(
 
297
                misc.ClassAttribute,
 
298
                {'class': class_value, 'directive': self.name},
 
299
                self.block_text)
 
300
            self.state_machine.document.note_pending(pending)
 
301
            node_list.append(pending)
 
302
        return node_list
 
303
 
 
304
 
 
305
class Role(Directive):
 
306
 
 
307
    has_content = True
 
308
 
 
309
    argument_pattern = re.compile(r'(%s)\s*(\(\s*(%s)\s*\)\s*)?$'
 
310
                                  % ((states.Inliner.simplename,) * 2))
 
311
 
 
312
    def run(self):
 
313
        """Dynamically create and register a custom interpreted text role."""
 
314
        if self.content_offset > self.lineno or not self.content:
 
315
            raise self.error('"%s" directive requires arguments on the first '
 
316
                             'line.' % self.name)
 
317
        args = self.content[0]
 
318
        match = self.argument_pattern.match(args)
 
319
        if not match:
 
320
            raise self.error('"%s" directive arguments not valid role names: '
 
321
                             '"%s".' % (self.name, args))
 
322
        new_role_name = match.group(1)
 
323
        base_role_name = match.group(3)
 
324
        messages = []
 
325
        if base_role_name:
 
326
            base_role, messages = roles.role(
 
327
                base_role_name, self.state_machine.language, self.lineno,
 
328
                self.state.reporter)
 
329
            if base_role is None:
 
330
                error = self.state.reporter.error(
 
331
                    'Unknown interpreted text role "%s".' % base_role_name,
 
332
                    nodes.literal_block(self.block_text, self.block_text),
 
333
                    line=self.lineno)
 
334
                return messages + [error]
 
335
        else:
 
336
            base_role = roles.generic_custom_role
 
337
        assert not hasattr(base_role, 'arguments'), (
 
338
            'Supplemental directive arguments for "%s" directive not '
 
339
            'supported (specified by "%r" role).' % (self.name, base_role))
 
340
        try:
 
341
            converted_role = convert_directive_function(base_role)
 
342
            (arguments, options, content, content_offset) = (
 
343
                self.state.parse_directive_block(
 
344
                self.content[1:], self.content_offset, converted_role,
 
345
                option_presets={}))
 
346
        except states.MarkupError, detail:
 
347
            error = self.state_machine.reporter.error(
 
348
                'Error in "%s" directive:\n%s.' % (self.name, detail),
 
349
                nodes.literal_block(self.block_text, self.block_text),
 
350
                line=self.lineno)
 
351
            return messages + [error]
 
352
        if not options.has_key('class'):
 
353
            try:
 
354
                options['class'] = directives.class_option(new_role_name)
 
355
            except ValueError, detail:
 
356
                error = self.state_machine.reporter.error(
 
357
                    'Invalid argument for "%s" directive:\n%s.'
 
358
                    % (self.name, detail), nodes.literal_block(
 
359
                    self.block_text, self.block_text), line=self.lineno)
 
360
                return messages + [error]
 
361
        role = roles.CustomRole(new_role_name, base_role, options, content)
 
362
        roles.register_local_role(new_role_name, role)
 
363
        return messages
 
364
 
 
365
 
 
366
class DefaultRole(Directive):
 
367
 
350
368
    """Set the default interpreted text role."""
351
 
    if not arguments:
352
 
        if roles._roles.has_key(''):
353
 
            # restore the "default" default role
354
 
            del roles._roles['']
 
369
 
 
370
    required_arguments = 0
 
371
    optional_arguments = 1
 
372
    final_argument_whitespace = False
 
373
 
 
374
    def run(self):
 
375
        if not self.arguments:
 
376
            if roles._roles.has_key(''):
 
377
                # restore the "default" default role
 
378
                del roles._roles['']
 
379
            return []
 
380
        role_name = self.arguments[0]
 
381
        role, messages = roles.role(role_name, self.state_machine.language,
 
382
                                    self.lineno, self.state.reporter)
 
383
        if role is None:
 
384
            error = self.state.reporter.error(
 
385
                'Unknown interpreted text role "%s".' % role_name,
 
386
                nodes.literal_block(self.block_text, self.block_text),
 
387
                line=self.lineno)
 
388
            return messages + [error]
 
389
        roles._roles[''] = role
 
390
        # @@@ should this be local to the document, not the parser?
 
391
        return messages
 
392
 
 
393
 
 
394
class Title(Directive):
 
395
 
 
396
    required_arguments = 1
 
397
    optional_arguments = 0
 
398
    final_argument_whitespace = True
 
399
 
 
400
    def run(self):
 
401
        self.state_machine.document['title'] = self.arguments[0]
355
402
        return []
356
 
    role_name = arguments[0]
357
 
    role, messages = roles.role(
358
 
        role_name, state_machine.language, lineno, state.reporter)
359
 
    if role is None:
360
 
        error = state.reporter.error(
361
 
            'Unknown interpreted text role "%s".' % role_name,
362
 
            nodes.literal_block(block_text, block_text), line=lineno)
363
 
        return messages + [error]
364
 
    roles._roles[''] = role
365
 
    # @@@ should this be local to the document, not the parser?
366
 
    return messages
367
 
 
368
 
default_role.arguments = (0, 1, 0)
369
 
 
370
 
def title(name, arguments, options, content, lineno,
371
 
          content_offset, block_text, state, state_machine):
372
 
    state_machine.document['title'] = arguments[0]
373
 
    return []
374
 
 
375
 
title.arguments = (1, 0, 1)
376
 
 
377
 
def date(name, arguments, options, content, lineno,
378
 
         content_offset, block_text, state, state_machine):
379
 
    if not isinstance(state, states.SubstitutionDef):
380
 
        error = state_machine.reporter.error(
381
 
            'Invalid context: the "%s" directive can only be used within a '
382
 
            'substitution definition.' % (name),
383
 
            nodes.literal_block(block_text, block_text), line=lineno)
384
 
        return [error]
385
 
    format = '\n'.join(content) or '%Y-%m-%d'
386
 
    text = time.strftime(format)
387
 
    return [nodes.Text(text)]
388
 
 
389
 
date.content = 1
390
 
 
391
 
def directive_test_function(name, arguments, options, content, lineno,
392
 
                            content_offset, block_text, state, state_machine):
 
403
 
 
404
 
 
405
class Date(Directive):
 
406
 
 
407
    has_content = True
 
408
 
 
409
    def run(self):
 
410
        if not isinstance(self.state, states.SubstitutionDef):
 
411
            raise self.error(
 
412
                'Invalid context: the "%s" directive can only be used within '
 
413
                'a substitution definition.' % self.name)
 
414
        format = '\n'.join(self.content) or '%Y-%m-%d'
 
415
        text = time.strftime(format)
 
416
        return [nodes.Text(text)]
 
417
 
 
418
 
 
419
class TestDirective(Directive):
 
420
 
393
421
    """This directive is useful only for testing purposes."""
394
 
    if content:
395
 
        text = '\n'.join(content)
396
 
        info = state_machine.reporter.info(
397
 
            'Directive processed. Type="%s", arguments=%r, options=%r, '
398
 
            'content:' % (name, arguments, options),
399
 
            nodes.literal_block(text, text), line=lineno)
400
 
    else:
401
 
        info = state_machine.reporter.info(
402
 
            'Directive processed. Type="%s", arguments=%r, options=%r, '
403
 
            'content: None' % (name, arguments, options), line=lineno)
404
 
    return [info]
405
 
 
406
 
directive_test_function.arguments = (0, 1, 1)
407
 
directive_test_function.options = {'option': directives.unchanged_required}
408
 
directive_test_function.content = 1
 
422
 
 
423
    required_arguments = 0
 
424
    optional_arguments = 1
 
425
    final_argument_whitespace = True
 
426
    option_spec = {'option': directives.unchanged_required}
 
427
    has_content = True
 
428
 
 
429
    def run(self):
 
430
        if self.content:
 
431
            text = '\n'.join(self.content)
 
432
            info = self.state_machine.reporter.info(
 
433
                'Directive processed. Type="%s", arguments=%r, options=%r, '
 
434
                'content:' % (self.name, self.arguments, self.options),
 
435
                nodes.literal_block(text, text), line=self.lineno)
 
436
        else:
 
437
            info = self.state_machine.reporter.info(
 
438
                'Directive processed. Type="%s", arguments=%r, options=%r, '
 
439
                'content: None' % (self.name, self.arguments, self.options),
 
440
                line=self.lineno)
 
441
        return [info]
 
442
 
 
443
# Old-style, functional definition:
 
444
#
 
445
# def directive_test_function(name, arguments, options, content, lineno,
 
446
#                             content_offset, block_text, state, state_machine):
 
447
#     """This directive is useful only for testing purposes."""
 
448
#     if content:
 
449
#         text = '\n'.join(content)
 
450
#         info = state_machine.reporter.info(
 
451
#             'Directive processed. Type="%s", arguments=%r, options=%r, '
 
452
#             'content:' % (name, arguments, options),
 
453
#             nodes.literal_block(text, text), line=lineno)
 
454
#     else:
 
455
#         info = state_machine.reporter.info(
 
456
#             'Directive processed. Type="%s", arguments=%r, options=%r, '
 
457
#             'content: None' % (name, arguments, options), line=lineno)
 
458
#     return [info]
 
459
#
 
460
# directive_test_function.arguments = (0, 1, 1)
 
461
# directive_test_function.options = {'option': directives.unchanged_required}
 
462
# directive_test_function.content = 1