diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py index d2aacaa45..51463661a 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -23,6 +23,7 @@ from docutils import nodes from docutils.parsers.rst import directives from sphinx.builders import Builder +from sphinx.util.compat import Directive from sphinx.util.console import bold blankline_re = re.compile(r'^\s*', re.MULTILINE) @@ -30,63 +31,77 @@ doctestopt_re = re.compile(r'#\s*doctest:.+$', re.MULTILINE) # set up the necessary directives -def test_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - # use ordinary docutils nodes for test code: they get special attributes - # so that our builder recognizes them, and the other builders are happy. - code = '\n'.join(content) - test = None - if name == 'doctest': - if '' in code: - # convert s to ordinary blank lines for presentation - test = code - code = blankline_re.sub('', code) - if doctestopt_re.search(code): - if not test: +class TestDirective(Directive): + """ + Base class for doctest-related directives. + """ + + has_content = True + required_arguments = 0 + optional_arguments = 1 + final_argument_whitespace = True + + def run(self): + # use ordinary docutils nodes for test code: they get special attributes + # so that our builder recognizes them, and the other builders are happy. + code = '\n'.join(self.content) + test = None + if self.name == 'doctest': + if '' in code: + # convert s to ordinary blank lines for presentation test = code - code = doctestopt_re.sub('', code) - nodetype = nodes.literal_block - if name == 'testsetup' or 'hide' in options: - nodetype = nodes.comment - if arguments: - groups = [x.strip() for x in arguments[0].split(',')] - else: - groups = ['default'] - node = nodetype(code, code, testnodetype=name, groups=groups) - node.line = lineno - if test is not None: - # only save if it differs from code - node['test'] = test - if name == 'testoutput': - # don't try to highlight output - node['language'] = 'none' - node['options'] = {} - if name in ('doctest', 'testoutput') and 'options' in options: - # parse doctest-like output comparison flags - option_strings = options['options'].replace(',', ' ').split() - for option in option_strings: - if (option[0] not in '+-' or option[1:] not in - doctest.OPTIONFLAGS_BY_NAME): - # XXX warn? - continue - flag = doctest.OPTIONFLAGS_BY_NAME[option[1:]] - node['options'][flag] = (option[0] == '+') - return [node] + code = blankline_re.sub('', code) + if doctestopt_re.search(code): + if not test: + test = code + code = doctestopt_re.sub('', code) + nodetype = nodes.literal_block + if self.name == 'testsetup' or 'hide' in self.options: + nodetype = nodes.comment + if self.arguments: + groups = [x.strip() for x in self.arguments[0].split(',')] + else: + groups = ['default'] + node = nodetype(code, code, testnodetype=self.name, groups=groups) + node.line = self.lineno + if test is not None: + # only save if it differs from code + node['test'] = test + if self.name == 'testoutput': + # don't try to highlight output + node['language'] = 'none' + node['options'] = {} + if self.name in ('doctest', 'testoutput') and 'options' in self.options: + # parse doctest-like output comparison flags + option_strings = self.options['options'].replace(',', ' ').split() + for option in option_strings: + if (option[0] not in '+-' or option[1:] not in + doctest.OPTIONFLAGS_BY_NAME): + # XXX warn? + continue + flag = doctest.OPTIONFLAGS_BY_NAME[option[1:]] + node['options'][flag] = (option[0] == '+') + return [node] -# need to have individual functions for each directive due to different -# options they accept +class TestsetupDirective(TestDirective): + option_spec = {} -def testsetup_directive(*args): - return test_directive(*args) +class DoctestDirective(TestDirective): + option_spec = { + 'hide': directives.flag, + 'options': directives.unchanged, + } -def doctest_directive(*args): - return test_directive(*args) +class TestcodeDirective(TestDirective): + option_spec = { + 'hide': directives.flag, + } -def testcode_directive(*args): - return test_directive(*args) - -def testoutput_directive(*args): - return test_directive(*args) +class TestoutputDirective(TestDirective): + option_spec = { + 'hide': directives.flag, + 'options': directives.unchanged, + } parser = doctest.DocTestParser() @@ -334,13 +349,10 @@ Doctest summary def setup(app): - app.add_directive('testsetup', testsetup_directive, 1, (0, 1, 1)) - app.add_directive('doctest', doctest_directive, 1, (0, 1, 1), - hide=directives.flag, options=directives.unchanged) - app.add_directive('testcode', testcode_directive, 1, (0, 1, 1), - hide=directives.flag) - app.add_directive('testoutput', testoutput_directive, 1, (0, 1, 1), - hide=directives.flag, options=directives.unchanged) + app.add_directive('testsetup', TestsetupDirective) + app.add_directive('doctest', DoctestDirective) + app.add_directive('testcode', TestcodeDirective) + app.add_directive('testoutput', TestoutputDirective) app.add_builder(DocTestBuilder) # this config value adds to sys.path app.add_config_value('doctest_path', [], False) diff --git a/sphinx/ext/ifconfig.py b/sphinx/ext/ifconfig.py index af3975b83..90cd2b2c4 100644 --- a/sphinx/ext/ifconfig.py +++ b/sphinx/ext/ifconfig.py @@ -22,17 +22,27 @@ from docutils import nodes +from sphinx.util.compat import Directive + class ifconfig(nodes.Element): pass -def ifconfig_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - node = ifconfig() - node.line = lineno - node['expr'] = arguments[0] - state.nested_parse(content, content_offset, node) - return [node] +class IfConfig(Directive): + + has_content = True + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} + + def run(self): + node = ifconfig() + node.document = self.state.document + node.line = self.lineno + node['expr'] = self.arguments[0] + self.state.nested_parse(self.content, self.content_offset, node) + return [node] def process_ifconfig_nodes(app, doctree, docname): @@ -58,5 +68,5 @@ def process_ifconfig_nodes(app, doctree, docname): def setup(app): app.add_node(ifconfig) - app.add_directive('ifconfig', ifconfig_directive, 1, (1, 0, 1)) + app.add_directive('ifconfig', IfConfig) app.connect('doctree-resolved', process_ifconfig_nodes) diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py index 12af089da..fea786e3e 100644 --- a/sphinx/ext/mathbase.py +++ b/sphinx/ext/mathbase.py @@ -12,6 +12,8 @@ from docutils import nodes, utils from docutils.parsers.rst import directives +from sphinx.util.compat import Directive + class math(nodes.Inline, nodes.TextElement): pass @@ -45,22 +47,33 @@ def eq_role(role, rawtext, text, lineno, inliner, options={}, content=[]): node['docname'] = inliner.document.settings.env.docname return [node], [] -def math_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - latex = '\n'.join(content) - if arguments and arguments[0]: - latex = arguments[0] + '\n\n' + latex - node = displaymath() - node['latex'] = latex - node['label'] = options.get('label', None) - node['nowrap'] = 'nowrap' in options - node['docname'] = state.document.settings.env.docname - ret = [node] - if node['label']: - tnode = nodes.target('', '', ids=['equation-' + node['label']]) - state.document.note_explicit_target(tnode) - ret.insert(0, tnode) - return ret + +class MathDirective(Directive): + + has_content = True + required_arguments = 0 + optional_arguments = 1 + final_argument_whitespace = True + option_spec = { + 'label': directives.unchanged, + 'nowrap': directives.flag, + } + + def run(self): + latex = '\n'.join(self.content) + if self.arguments and self.arguments[0]: + latex = self.arguments[0] + '\n\n' + latex + node = displaymath() + node['latex'] = latex + node['label'] = self.options.get('label', None) + node['nowrap'] = 'nowrap' in self.options + node['docname'] = self.state.document.settings.env.docname + ret = [node] + if node['label']: + tnode = nodes.target('', '', ids=['equation-' + node['label']]) + self.state.document.note_explicit_target(tnode) + ret.insert(0, tnode) + return ret def latex_visit_math(self, node): @@ -134,6 +147,5 @@ def setup(app, htmlinlinevisitors, htmldisplayvisitors): html=(html_visit_eqref, html_depart_eqref)) app.add_role('math', math_role) app.add_role('eq', eq_role) - app.add_directive('math', math_directive, 1, (0, 1, 1), - label=directives.unchanged, nowrap=directives.flag) + app.add_directive('math', MathDirective) app.connect('doctree-resolved', number_equations) diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index 7294e4f99..61dd4d673 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -14,42 +14,63 @@ from docutils import nodes -from sphinx.util.compat import make_admonition +from sphinx.util.compat import Directive, make_admonition class todo_node(nodes.Admonition, nodes.Element): pass class todolist(nodes.General, nodes.Element): pass -def todo_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - env = state.document.settings.env +class Todo(Directive): + """ + A todo entry, displayed (if configured) in the form of an admonition. + """ - targetid = "todo-%s" % env.index_num - env.index_num += 1 - targetnode = nodes.target('', '', ids=[targetid]) + has_content = True + required_arguments = 0 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = {} - ad = make_admonition(todo_node, name, [_('Todo')], options, content, lineno, - content_offset, block_text, state, state_machine) + def run(self): + env = self.state.document.settings.env - # Attach a list of all todos to the environment, - # the todolist works with the collected todo nodes - if not hasattr(env, 'todo_all_todos'): - env.todo_all_todos = [] - env.todo_all_todos.append({ - 'docname': env.docname, - 'lineno': lineno, - 'todo': ad[0].deepcopy(), - 'target': targetnode, - }) + targetid = "todo-%s" % env.index_num + env.index_num += 1 + targetnode = nodes.target('', '', ids=[targetid]) - return [targetnode] + ad + ad = make_admonition(todo_node, self.name, [_('Todo')], self.options, + self.content, self.lineno, self.content_offset, + self.block_text, self.state, self.state_machine) + + # Attach a list of all todos to the environment, + # the todolist works with the collected todo nodes + if not hasattr(env, 'todo_all_todos'): + env.todo_all_todos = [] + env.todo_all_todos.append({ + 'docname': env.docname, + 'lineno': self.lineno, + 'todo': ad[0].deepcopy(), + 'target': targetnode, + }) + + return [targetnode] + ad -def todolist_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - # Simply insert an empty todolist node which will be replaced later - # when process_todo_nodes is called - return [todolist('')] +class TodoList(Directive): + """ + A list of all todo entries. + """ + + has_content = False + required_arguments = 0 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = {} + + def run(self): + # Simply insert an empty todolist node which will be replaced later + # when process_todo_nodes is called + return [todolist('')] def process_todo_nodes(app, doctree, fromdocname): @@ -119,8 +140,8 @@ def setup(app): latex=(visit_todo_node, depart_todo_node), text=(visit_todo_node, depart_todo_node)) - app.add_directive('todo', todo_directive, 1, (0, 0, 1)) - app.add_directive('todolist', todolist_directive, 0, (0, 0, 0)) + app.add_directive('todo', Todo) + app.add_directive('todolist', TodoList) app.connect('doctree-resolved', process_todo_nodes) app.connect('env-purge-doc', purge_todos)