Convert directives in builtin extensions to class API.

This commit is contained in:
Georg Brandl 2009-02-18 21:48:00 +01:00
parent 6bfed75113
commit 3724f9020a
4 changed files with 167 additions and 112 deletions

View File

@ -23,6 +23,7 @@ from docutils import nodes
from docutils.parsers.rst import directives from docutils.parsers.rst import directives
from sphinx.builders import Builder from sphinx.builders import Builder
from sphinx.util.compat import Directive
from sphinx.util.console import bold from sphinx.util.console import bold
blankline_re = re.compile(r'^\s*<BLANKLINE>', re.MULTILINE) blankline_re = re.compile(r'^\s*<BLANKLINE>', re.MULTILINE)
@ -30,63 +31,77 @@ doctestopt_re = re.compile(r'#\s*doctest:.+$', re.MULTILINE)
# set up the necessary directives # set up the necessary directives
def test_directive(name, arguments, options, content, lineno, class TestDirective(Directive):
content_offset, block_text, state, state_machine): """
# use ordinary docutils nodes for test code: they get special attributes Base class for doctest-related directives.
# so that our builder recognizes them, and the other builders are happy. """
code = '\n'.join(content)
test = None has_content = True
if name == 'doctest': required_arguments = 0
if '<BLANKLINE>' in code: optional_arguments = 1
# convert <BLANKLINE>s to ordinary blank lines for presentation final_argument_whitespace = True
test = code
code = blankline_re.sub('', code) def run(self):
if doctestopt_re.search(code): # use ordinary docutils nodes for test code: they get special attributes
if not test: # so that our builder recognizes them, and the other builders are happy.
code = '\n'.join(self.content)
test = None
if self.name == 'doctest':
if '<BLANKLINE>' in code:
# convert <BLANKLINE>s to ordinary blank lines for presentation
test = code test = code
code = doctestopt_re.sub('', code) code = blankline_re.sub('', code)
nodetype = nodes.literal_block if doctestopt_re.search(code):
if name == 'testsetup' or 'hide' in options: if not test:
nodetype = nodes.comment test = code
if arguments: code = doctestopt_re.sub('', code)
groups = [x.strip() for x in arguments[0].split(',')] nodetype = nodes.literal_block
else: if self.name == 'testsetup' or 'hide' in self.options:
groups = ['default'] nodetype = nodes.comment
node = nodetype(code, code, testnodetype=name, groups=groups) if self.arguments:
node.line = lineno groups = [x.strip() for x in self.arguments[0].split(',')]
if test is not None: else:
# only save if it differs from code groups = ['default']
node['test'] = test node = nodetype(code, code, testnodetype=self.name, groups=groups)
if name == 'testoutput': node.line = self.lineno
# don't try to highlight output if test is not None:
node['language'] = 'none' # only save if it differs from code
node['options'] = {} node['test'] = test
if name in ('doctest', 'testoutput') and 'options' in options: if self.name == 'testoutput':
# parse doctest-like output comparison flags # don't try to highlight output
option_strings = options['options'].replace(',', ' ').split() node['language'] = 'none'
for option in option_strings: node['options'] = {}
if (option[0] not in '+-' or option[1:] not in if self.name in ('doctest', 'testoutput') and 'options' in self.options:
doctest.OPTIONFLAGS_BY_NAME): # parse doctest-like output comparison flags
# XXX warn? option_strings = self.options['options'].replace(',', ' ').split()
continue for option in option_strings:
flag = doctest.OPTIONFLAGS_BY_NAME[option[1:]] if (option[0] not in '+-' or option[1:] not in
node['options'][flag] = (option[0] == '+') doctest.OPTIONFLAGS_BY_NAME):
return [node] # 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 class TestsetupDirective(TestDirective):
# options they accept option_spec = {}
def testsetup_directive(*args): class DoctestDirective(TestDirective):
return test_directive(*args) option_spec = {
'hide': directives.flag,
'options': directives.unchanged,
}
def doctest_directive(*args): class TestcodeDirective(TestDirective):
return test_directive(*args) option_spec = {
'hide': directives.flag,
}
def testcode_directive(*args): class TestoutputDirective(TestDirective):
return test_directive(*args) option_spec = {
'hide': directives.flag,
def testoutput_directive(*args): 'options': directives.unchanged,
return test_directive(*args) }
parser = doctest.DocTestParser() parser = doctest.DocTestParser()
@ -334,13 +349,10 @@ Doctest summary
def setup(app): def setup(app):
app.add_directive('testsetup', testsetup_directive, 1, (0, 1, 1)) app.add_directive('testsetup', TestsetupDirective)
app.add_directive('doctest', doctest_directive, 1, (0, 1, 1), app.add_directive('doctest', DoctestDirective)
hide=directives.flag, options=directives.unchanged) app.add_directive('testcode', TestcodeDirective)
app.add_directive('testcode', testcode_directive, 1, (0, 1, 1), app.add_directive('testoutput', TestoutputDirective)
hide=directives.flag)
app.add_directive('testoutput', testoutput_directive, 1, (0, 1, 1),
hide=directives.flag, options=directives.unchanged)
app.add_builder(DocTestBuilder) app.add_builder(DocTestBuilder)
# this config value adds to sys.path # this config value adds to sys.path
app.add_config_value('doctest_path', [], False) app.add_config_value('doctest_path', [], False)

View File

@ -22,17 +22,27 @@
from docutils import nodes from docutils import nodes
from sphinx.util.compat import Directive
class ifconfig(nodes.Element): pass class ifconfig(nodes.Element): pass
def ifconfig_directive(name, arguments, options, content, lineno, class IfConfig(Directive):
content_offset, block_text, state, state_machine):
node = ifconfig() has_content = True
node.line = lineno required_arguments = 1
node['expr'] = arguments[0] optional_arguments = 0
state.nested_parse(content, content_offset, node) final_argument_whitespace = True
return [node] 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): def process_ifconfig_nodes(app, doctree, docname):
@ -58,5 +68,5 @@ def process_ifconfig_nodes(app, doctree, docname):
def setup(app): def setup(app):
app.add_node(ifconfig) 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) app.connect('doctree-resolved', process_ifconfig_nodes)

View File

@ -12,6 +12,8 @@
from docutils import nodes, utils from docutils import nodes, utils
from docutils.parsers.rst import directives from docutils.parsers.rst import directives
from sphinx.util.compat import Directive
class math(nodes.Inline, nodes.TextElement): class math(nodes.Inline, nodes.TextElement):
pass pass
@ -45,22 +47,33 @@ def eq_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
node['docname'] = inliner.document.settings.env.docname node['docname'] = inliner.document.settings.env.docname
return [node], [] return [node], []
def math_directive(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine): class MathDirective(Directive):
latex = '\n'.join(content)
if arguments and arguments[0]: has_content = True
latex = arguments[0] + '\n\n' + latex required_arguments = 0
node = displaymath() optional_arguments = 1
node['latex'] = latex final_argument_whitespace = True
node['label'] = options.get('label', None) option_spec = {
node['nowrap'] = 'nowrap' in options 'label': directives.unchanged,
node['docname'] = state.document.settings.env.docname 'nowrap': directives.flag,
ret = [node] }
if node['label']:
tnode = nodes.target('', '', ids=['equation-' + node['label']]) def run(self):
state.document.note_explicit_target(tnode) latex = '\n'.join(self.content)
ret.insert(0, tnode) if self.arguments and self.arguments[0]:
return ret 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): def latex_visit_math(self, node):
@ -134,6 +147,5 @@ def setup(app, htmlinlinevisitors, htmldisplayvisitors):
html=(html_visit_eqref, html_depart_eqref)) html=(html_visit_eqref, html_depart_eqref))
app.add_role('math', math_role) app.add_role('math', math_role)
app.add_role('eq', eq_role) app.add_role('eq', eq_role)
app.add_directive('math', math_directive, 1, (0, 1, 1), app.add_directive('math', MathDirective)
label=directives.unchanged, nowrap=directives.flag)
app.connect('doctree-resolved', number_equations) app.connect('doctree-resolved', number_equations)

View File

@ -14,42 +14,63 @@
from docutils import nodes 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 todo_node(nodes.Admonition, nodes.Element): pass
class todolist(nodes.General, nodes.Element): pass class todolist(nodes.General, nodes.Element): pass
def todo_directive(name, arguments, options, content, lineno, class Todo(Directive):
content_offset, block_text, state, state_machine): """
env = state.document.settings.env A todo entry, displayed (if configured) in the form of an admonition.
"""
targetid = "todo-%s" % env.index_num has_content = True
env.index_num += 1 required_arguments = 0
targetnode = nodes.target('', '', ids=[targetid]) optional_arguments = 0
final_argument_whitespace = False
option_spec = {}
ad = make_admonition(todo_node, name, [_('Todo')], options, content, lineno, def run(self):
content_offset, block_text, state, state_machine) env = self.state.document.settings.env
# Attach a list of all todos to the environment, targetid = "todo-%s" % env.index_num
# the todolist works with the collected todo nodes env.index_num += 1
if not hasattr(env, 'todo_all_todos'): targetnode = nodes.target('', '', ids=[targetid])
env.todo_all_todos = []
env.todo_all_todos.append({
'docname': env.docname,
'lineno': lineno,
'todo': ad[0].deepcopy(),
'target': targetnode,
})
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, class TodoList(Directive):
content_offset, block_text, state, state_machine): """
# Simply insert an empty todolist node which will be replaced later A list of all todo entries.
# when process_todo_nodes is called """
return [todolist('')]
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): def process_todo_nodes(app, doctree, fromdocname):
@ -119,8 +140,8 @@ def setup(app):
latex=(visit_todo_node, depart_todo_node), latex=(visit_todo_node, depart_todo_node),
text=(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('todo', Todo)
app.add_directive('todolist', todolist_directive, 0, (0, 0, 0)) app.add_directive('todolist', TodoList)
app.connect('doctree-resolved', process_todo_nodes) app.connect('doctree-resolved', process_todo_nodes)
app.connect('env-purge-doc', purge_todos) app.connect('env-purge-doc', purge_todos)