1 # Authors: David Goodger, Dethe Elza
2 # Contact: goodger@users.sourceforge.net
5 # Copyright: This module has been placed in the public domain.
7 """Miscellaneous directives."""
9 __docformat__
= 'reStructuredText'
14 from docutils
import io
, nodes
, statemachine
, utils
15 from docutils
.parsers
.rst
import directives
, roles
, states
16 from docutils
.transforms
import misc
24 def include(name
, arguments
, options
, content
, lineno
,
25 content_offset
, block_text
, state
, state_machine
):
26 """Include a reST file as part of the content of this reST file."""
27 source
= state_machine
.input_lines
.source(
28 lineno
- state_machine
.input_offset
- 1)
29 source_dir
= os
.path
.dirname(os
.path
.abspath(source
))
30 path
= ''.join(arguments
[0].splitlines())
31 if path
.find(' ') != -1:
32 error
= state_machine
.reporter
.error(
33 '"%s" directive path contains whitespace.' % name
,
34 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
36 path
= os
.path
.normpath(os
.path
.join(source_dir
, path
))
37 path
= utils
.relative_path(None, path
)
38 encoding
= options
.get('encoding', state
.document
.settings
.input_encoding
)
40 state
.document
.settings
.record_dependencies
.add(path
)
41 include_file
= io
.FileInput(
42 source_path
=path
, encoding
=encoding
,
43 error_handler
=state
.document
.settings
.input_encoding_error_handler
,
44 handle_io_errors
=None)
45 except IOError, error
:
46 severe
= state_machine
.reporter
.severe(
47 'Problems with "%s" directive path:\n%s: %s.'
48 % (name
, error
.__class
__.__name
__, error
),
49 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
51 include_text
= include_file
.read()
52 if options
.has_key('literal'):
53 literal_block
= nodes
.literal_block(include_text
, include_text
,
55 literal_block
.line
= 1
58 include_lines
= statemachine
.string2lines(include_text
,
60 state_machine
.insert_input(include_lines
, path
)
63 include
.arguments
= (1, 0, 1)
64 include
.options
= {'literal': directives
.flag
,
65 'encoding': directives
.encoding
}
67 def raw(name
, arguments
, options
, content
, lineno
,
68 content_offset
, block_text
, state
, state_machine
):
70 Pass through content unchanged
72 Content is included in output based on type argument
74 Content may be included inline (content section of directive) or
75 imported from a file or url.
77 attributes
= {'format': ' '.join(arguments
[0].lower().split())}
78 encoding
= options
.get('encoding', state
.document
.settings
.input_encoding
)
80 if options
.has_key('file') or options
.has_key('url'):
81 error
= state_machine
.reporter
.error(
82 '"%s" directive may not both specify an external file and '
83 'have content.' % name
,
84 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
86 text
= '\n'.join(content
)
87 elif options
.has_key('file'):
88 if options
.has_key('url'):
89 error
= state_machine
.reporter
.error(
90 'The "file" and "url" options may not be simultaneously '
91 'specified for the "%s" directive.' % name
,
92 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
94 source_dir
= os
.path
.dirname(
95 os
.path
.abspath(state
.document
.current_source
))
96 path
= os
.path
.normpath(os
.path
.join(source_dir
, options
['file']))
97 path
= utils
.relative_path(None, path
)
99 state
.document
.settings
.record_dependencies
.add(path
)
100 raw_file
= io
.FileInput(
101 source_path
=path
, encoding
=encoding
,
102 error_handler
=state
.document
.settings
.input_encoding_error_handler
,
103 handle_io_errors
=None)
104 except IOError, error
:
105 severe
= state_machine
.reporter
.severe(
106 'Problems with "%s" directive path:\n%s.' % (name
, error
),
107 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
109 text
= raw_file
.read()
110 attributes
['source'] = path
111 elif options
.has_key('url'):
113 severe
= state_machine
.reporter
.severe(
114 'Problems with the "%s" directive and its "url" option: '
115 'unable to access the required functionality (from the '
116 '"urllib2" module).' % name
,
117 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
119 source
= options
['url']
121 raw_text
= urllib2
.urlopen(source
).read()
122 except (urllib2
.URLError
, IOError, OSError), error
:
123 severe
= state_machine
.reporter
.severe(
124 'Problems with "%s" directive URL "%s":\n%s.'
125 % (name
, options
['url'], error
),
126 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
128 raw_file
= io
.StringInput(
129 source
=raw_text
, source_path
=source
, encoding
=encoding
,
130 error_handler
=state
.document
.settings
.input_encoding_error_handler
)
131 text
= raw_file
.read()
132 attributes
['source'] = source
134 error
= state_machine
.reporter
.warning(
135 'The "%s" directive requires content; none supplied.' % (name
),
136 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
138 raw_node
= nodes
.raw('', text
, **attributes
)
141 raw
.arguments
= (1, 0, 1)
142 raw
.options
= {'file': directives
.path
,
143 'url': directives
.path
,
144 'encoding': directives
.encoding
}
147 def replace(name
, arguments
, options
, content
, lineno
,
148 content_offset
, block_text
, state
, state_machine
):
149 if not isinstance(state
, states
.SubstitutionDef
):
150 error
= state_machine
.reporter
.error(
151 'Invalid context: the "%s" directive can only be used within a '
152 'substitution definition.' % (name
),
153 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
155 text
= '\n'.join(content
)
156 element
= nodes
.Element(text
)
158 state
.nested_parse(content
, content_offset
, element
)
159 if len(element
) != 1 or not isinstance(element
[0], nodes
.paragraph
):
162 if isinstance(node
, nodes
.system_message
):
163 if node
.has_key('backrefs'):
165 messages
.append(node
)
166 error
= state_machine
.reporter
.error(
167 'Error in "%s" directive: may contain a single paragraph '
168 'only.' % (name
), line
=lineno
)
169 messages
.append(error
)
172 return element
[0].children
174 error
= state_machine
.reporter
.error(
175 'The "%s" directive is empty; content required.' % (name
),
181 def unicode_directive(name
, arguments
, options
, content
, lineno
,
182 content_offset
, block_text
, state
, state_machine
):
184 Convert Unicode character codes (numbers) to characters. Codes may be
185 decimal numbers, hexadecimal numbers (prefixed by ``0x``, ``x``, ``\x``,
186 ``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style numeric character
187 entities (e.g. ``☮``). Text following ".." is a comment and is
188 ignored. Spaces are ignored, and any other text remains as-is.
190 if not isinstance(state
, states
.SubstitutionDef
):
191 error
= state_machine
.reporter
.error(
192 'Invalid context: the "%s" directive can only be used within a '
193 'substitution definition.' % (name
),
194 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
196 substitution_definition
= state_machine
.node
197 if options
.has_key('trim'):
198 substitution_definition
.attributes
['ltrim'] = 1
199 substitution_definition
.attributes
['rtrim'] = 1
200 if options
.has_key('ltrim'):
201 substitution_definition
.attributes
['ltrim'] = 1
202 if options
.has_key('rtrim'):
203 substitution_definition
.attributes
['rtrim'] = 1
204 codes
= unicode_comment_pattern
.split(arguments
[0])[0].split()
205 element
= nodes
.Element()
208 decoded
= directives
.unicode_code(code
)
209 except ValueError, err
:
210 error
= state_machine
.reporter
.error(
211 'Invalid character code: %s\n%s: %s'
212 % (code
, err
.__class
__.__name
__, err
),
213 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
215 element
+= nodes
.Text(decoded
)
216 return element
.children
218 unicode_directive
.arguments
= (1, 0, 1)
219 unicode_directive
.options
= {'trim': directives
.flag
,
220 'ltrim': directives
.flag
,
221 'rtrim': directives
.flag
}
222 unicode_comment_pattern
= re
.compile(r
'( |\n|^)\.\. ')
224 def class_directive(name
, arguments
, options
, content
, lineno
,
225 content_offset
, block_text
, state
, state_machine
):
227 Set a "class" attribute on the next element.
228 A "pending" element is inserted, and a transform does the work later.
231 class_value
= directives
.class_option(arguments
[0])
233 error
= state_machine
.reporter
.error(
234 'Invalid class attribute value for "%s" directive: "%s".'
235 % (name
, arguments
[0]),
236 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
238 pending
= nodes
.pending(misc
.ClassAttribute
,
239 {'class': class_value
, 'directive': name
},
241 state_machine
.document
.note_pending(pending
)
244 class_directive
.arguments
= (1, 0, 1)
245 class_directive
.content
= 1
247 role_arg_pat
= re
.compile(r
'(%s)\s*(\(\s*(%s)\s*\)\s*)?$'
248 % ((states
.Inliner
.simplename
,) * 2))
249 def role(name
, arguments
, options
, content
, lineno
,
250 content_offset
, block_text
, state
, state_machine
):
251 """Dynamically create and register a custom interpreted text role."""
252 if content_offset
> lineno
or not content
:
253 error
= state_machine
.reporter
.error(
254 '"%s" directive requires arguments on the first line.'
255 % name
, nodes
.literal_block(block_text
, block_text
), line
=lineno
)
258 match
= role_arg_pat
.match(args
)
260 error
= state_machine
.reporter
.error(
261 '"%s" directive arguments not valid role names: "%s".'
262 % (name
, args
), nodes
.literal_block(block_text
, block_text
),
265 new_role_name
= match
.group(1)
266 base_role_name
= match
.group(3)
269 base_role
, messages
= roles
.role(
270 base_role_name
, state_machine
.language
, lineno
, state
.reporter
)
271 if base_role
is None:
272 error
= state
.reporter
.error(
273 'Unknown interpreted text role "%s".' % base_role_name
,
274 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
275 return messages
+ [error
]
277 base_role
= roles
.generic_custom_role
278 assert not hasattr(base_role
, 'arguments'), (
279 'Supplemental directive arguments for "%s" directive not supported'
280 '(specified by "%r" role).' % (name
, base_role
))
282 (arguments
, options
, content
, content_offset
) = (
283 state
.parse_directive_block(content
[1:], content_offset
, base_role
,
285 except states
.MarkupError
, detail
:
286 error
= state_machine
.reporter
.error(
287 'Error in "%s" directive:\n%s.' % (name
, detail
),
288 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
289 return messages
+ [error
]
290 if not options
.has_key('class'):
292 options
['class'] = directives
.class_option(new_role_name
)
293 except ValueError, detail
:
294 error
= state_machine
.reporter
.error(
295 'Invalid argument for "%s" directive:\n%s.'
297 nodes
.literal_block(block_text
, block_text
), line
=lineno
)
298 return messages
+ [error
]
299 role
= roles
.CustomRole(new_role_name
, base_role
, options
, content
)
300 roles
.register_local_role(new_role_name
, role
)
305 def directive_test_function(name
, arguments
, options
, content
, lineno
,
306 content_offset
, block_text
, state
, state_machine
):
307 """This directive is useful only for testing purposes."""
309 text
= '\n'.join(content
)
310 info
= state_machine
.reporter
.info(
311 'Directive processed. Type="%s", arguments=%r, options=%r, '
312 'content:' % (name
, arguments
, options
),
313 nodes
.literal_block(text
, text
), line
=lineno
)
315 info
= state_machine
.reporter
.info(
316 'Directive processed. Type="%s", arguments=%r, options=%r, '
317 'content: None' % (name
, arguments
, options
), line
=lineno
)
320 directive_test_function
.arguments
= (0, 1, 1)
321 directive_test_function
.options
= {'option': directives
.unchanged_required
}
322 directive_test_function
.content
= 1