def render_directive(self, token: Token): """Render special fenced code blocks as directives.""" first_line = token.info.split(maxsplit=1) name = first_line[0][1:-1] arguments = "" if len(first_line) == 1 else first_line[1] # TODO directive name white/black lists content = token.content position = token.map[0] self.document.current_line = position # get directive class directive_class, messages = directives.directive( name, self.language_module, self.document) # type: (Directive, list) if not directive_class: error = self.reporter.error( 'Unknown directive type "{}".\n'.format(name), # nodes.literal_block(content, content), line=position, ) self.current_node += [error] + messages return try: arguments, options, body_lines = parse_directive_text( directive_class, arguments, content) except DirectiveParsingError as error: error = self.reporter.error( "Directive '{}': {}".format(name, error), nodes.literal_block(content, content), line=position, ) self.current_node += [error] return # initialise directive if issubclass(directive_class, Include): directive_instance = MockIncludeDirective( self, name=name, klass=directive_class, arguments=arguments, options=options, body=body_lines, token=token, ) else: state_machine = MockStateMachine(self, position) state = MockState(self, state_machine, position) directive_instance = directive_class( name=name, # the list of positional arguments arguments=arguments, # a dictionary mapping option names to values options=options, # the directive content line by line content=StringList(body_lines, self.document["source"]), # the absolute line number of the first line of the directive lineno=position, # the line offset of the first line of the content content_offset= 0, # TODO get content offset from `parse_directive_text` # a string containing the entire directive block_text="\n".join(body_lines), state=state, state_machine=state_machine, ) # run directive try: result = directive_instance.run() except DirectiveError as error: msg_node = self.reporter.system_message(error.level, error.msg, line=position) msg_node += nodes.literal_block(content, content) result = [msg_node] except MockingError as exc: error = self.reporter.error( "Directive '{}' cannot be mocked: {}: {}".format( name, exc.__class__.__name__, exc), nodes.literal_block(content, content), line=position, ) self.current_node += [error] return assert isinstance( result, list), 'Directive "{}" must return a list of nodes.'.format(name) for i in range(len(result)): assert isinstance( result[i], nodes.Node ), 'Directive "{}" returned non-Node object (index {}): {}'.format( name, i, result[i]) self.current_node += result
def run_directive(self, name: str, first_line: str, content: str, position: int) -> List[nodes.Element]: """Run a directive and return the generated nodes. :param name: the name of the directive :param first_line: The text on the same line as the directive name. May be an argument or body text, dependent on the directive :param content: All text after the first line. Can include options. :param position: The line number of the first line """ # TODO directive name white/black lists self.document.current_line = position # get directive class directive_class, messages = directives.directive( name, self.language_module_rst, self.document) # type: (Directive, list) if not directive_class: error = self.reporter.error( 'Unknown directive type "{}".\n'.format(name), # nodes.literal_block(content, content), line=position, ) return [error] + messages if issubclass(directive_class, Include): # this is a Markdown only option, # to allow for altering relative image reference links directive_class.option_spec["relative-images"] = directives.flag directive_class.option_spec["relative-docs"] = directives.path try: arguments, options, body_lines = parse_directive_text( directive_class, first_line, content) except DirectiveParsingError as error: error = self.reporter.error( "Directive '{}': {}".format(name, error), nodes.literal_block(content, content), line=position, ) return [error] # initialise directive if issubclass(directive_class, Include): directive_instance = MockIncludeDirective( self, name=name, klass=directive_class, arguments=arguments, options=options, body=body_lines, lineno=position, ) else: state_machine = MockStateMachine(self, position) state = MockState(self, state_machine, position) directive_instance = directive_class( name=name, # the list of positional arguments arguments=arguments, # a dictionary mapping option names to values options=options, # the directive content line by line content=StringList(body_lines, self.document["source"]), # the absolute line number of the first line of the directive lineno=position, # the line offset of the first line of the content content_offset= 0, # TODO get content offset from `parse_directive_text` # a string containing the entire directive block_text="\n".join(body_lines), state=state, state_machine=state_machine, ) # run directive try: result = directive_instance.run() except DirectiveError as error: msg_node = self.reporter.system_message(error.level, error.msg, line=position) msg_node += nodes.literal_block(content, content) result = [msg_node] except MockingError as exc: error_msg = self.reporter.error( "Directive '{}' cannot be mocked: {}: {}".format( name, exc.__class__.__name__, exc), nodes.literal_block(content, content), line=position, ) return [error_msg] assert isinstance( result, list), 'Directive "{}" must return a list of nodes.'.format(name) for i in range(len(result)): assert isinstance( result[i], nodes.Node ), 'Directive "{}" returned non-Node object (index {}): {}'.format( name, i, result[i]) return result