fixed ("added") encoding support for raw and include directive
[docutils.git] / docutils / parsers / rst / directives / misc.py
blobd615f5540ba4aff959e70606bfc174cad6c111ae
1 # Authors: David Goodger, Dethe Elza
2 # Contact: goodger@users.sourceforge.net
3 # Revision: $Revision$
4 # Date: $Date$
5 # Copyright: This module has been placed in the public domain.
7 """Miscellaneous directives."""
9 __docformat__ = 'reStructuredText'
11 import sys
12 import os.path
13 import re
14 from docutils import io, nodes, statemachine, utils
15 from docutils.parsers.rst import directives, roles, states
16 from docutils.transforms import misc
18 try:
19 import urllib2
20 except ImportError:
21 urllib2 = None
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)
35 return [error]
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)
39 try:
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)
50 return [severe]
51 include_text = include_file.read()
52 if options.has_key('literal'):
53 literal_block = nodes.literal_block(include_text, include_text,
54 source=path)
55 literal_block.line = 1
56 return literal_block
57 else:
58 include_lines = statemachine.string2lines(include_text,
59 convert_whitespace=1)
60 state_machine.insert_input(include_lines, path)
61 return []
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):
69 """
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.
76 """
77 attributes = {'format': ' '.join(arguments[0].lower().split())}
78 encoding = options.get('encoding', state.document.settings.input_encoding)
79 if content:
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)
85 return [error]
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)
93 return [error]
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)
98 try:
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)
108 return [severe]
109 text = raw_file.read()
110 attributes['source'] = path
111 elif options.has_key('url'):
112 if not urllib2:
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)
118 return [severe]
119 source = options['url']
120 try:
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)
127 return [severe]
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
133 else:
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)
137 return [error]
138 raw_node = nodes.raw('', text, **attributes)
139 return [raw_node]
141 raw.arguments = (1, 0, 1)
142 raw.options = {'file': directives.path,
143 'url': directives.path,
144 'encoding': directives.encoding}
145 raw.content = 1
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)
154 return [error]
155 text = '\n'.join(content)
156 element = nodes.Element(text)
157 if text:
158 state.nested_parse(content, content_offset, element)
159 if len(element) != 1 or not isinstance(element[0], nodes.paragraph):
160 messages = []
161 for node in element:
162 if isinstance(node, nodes.system_message):
163 if node.has_key('backrefs'):
164 del node['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)
170 return messages
171 else:
172 return element[0].children
173 else:
174 error = state_machine.reporter.error(
175 'The "%s" directive is empty; content required.' % (name),
176 line=lineno)
177 return [error]
179 replace.content = 1
181 def unicode_directive(name, arguments, options, content, lineno,
182 content_offset, block_text, state, state_machine):
183 r"""
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)
195 return [error]
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()
206 for code in codes:
207 try:
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)
214 return [error]
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.
230 try:
231 class_value = directives.class_option(arguments[0])
232 except ValueError:
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)
237 return [error]
238 pending = nodes.pending(misc.ClassAttribute,
239 {'class': class_value, 'directive': name},
240 block_text)
241 state_machine.document.note_pending(pending)
242 return [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)
256 return [error]
257 args = content[0]
258 match = role_arg_pat.match(args)
259 if not match:
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),
263 line=lineno)
264 return [error]
265 new_role_name = match.group(1)
266 base_role_name = match.group(3)
267 messages = []
268 if base_role_name:
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]
276 else:
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))
281 try:
282 (arguments, options, content, content_offset) = (
283 state.parse_directive_block(content[1:], content_offset, base_role,
284 option_presets={}))
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'):
291 try:
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.'
296 % (name, detail),
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)
301 return messages
303 role.content = 1
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."""
308 if content:
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)
314 else:
315 info = state_machine.reporter.info(
316 'Directive processed. Type="%s", arguments=%r, options=%r, '
317 'content: None' % (name, arguments, options), line=lineno)
318 return [info]
320 directive_test_function.arguments = (0, 1, 1)
321 directive_test_function.options = {'option': directives.unchanged_required}
322 directive_test_function.content = 1