mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Convert builtin directives to classes. Refactor desc_directive so that it is easier to subclass.
This commit is contained in:
parent
8bd4e8e398
commit
044d42953d
@ -57,8 +57,7 @@ import sphinx
|
||||
from sphinx.roles import xfileref_role, innernodetypes
|
||||
from sphinx.config import Config
|
||||
from sphinx.builders import BUILTIN_BUILDERS
|
||||
from sphinx.directives import desc_directive, target_directive, \
|
||||
additional_xref_types
|
||||
from sphinx.directives import GenericDesc, Target, additional_xref_types
|
||||
from sphinx.environment import SphinxStandaloneReader
|
||||
from sphinx.util.compat import Directive, directive_dwim
|
||||
from sphinx.util.console import bold
|
||||
@ -314,7 +313,7 @@ class Sphinx(object):
|
||||
parse_node=None, ref_nodeclass=None):
|
||||
additional_xref_types[directivename] = (rolename, indextemplate,
|
||||
parse_node)
|
||||
directives.register_directive(directivename, desc_directive)
|
||||
directives.register_directive(directivename, GenericDesc)
|
||||
roles.register_canonical_role(rolename, xfileref_role)
|
||||
if ref_nodeclass is not None:
|
||||
innernodetypes[rolename] = ref_nodeclass
|
||||
@ -322,7 +321,7 @@ class Sphinx(object):
|
||||
def add_crossref_type(self, directivename, rolename, indextemplate='',
|
||||
ref_nodeclass=None):
|
||||
additional_xref_types[directivename] = (rolename, indextemplate, None)
|
||||
directives.register_directive(directivename, target_directive)
|
||||
directives.register_directive(directivename, Target)
|
||||
roles.register_canonical_role(rolename, xfileref_role)
|
||||
if ref_nodeclass is not None:
|
||||
innernodetypes[rolename] = ref_nodeclass
|
||||
|
@ -16,135 +16,159 @@ from docutils.parsers.rst import directives
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.util import parselinenos
|
||||
from sphinx.util.compat import Directive
|
||||
|
||||
|
||||
# ------ highlight directive ---------------------------------------------------
|
||||
class Highlight(Directive):
|
||||
"""
|
||||
Directive to set the highlighting language for code blocks, as well
|
||||
as the threshold for line numbers.
|
||||
"""
|
||||
|
||||
def highlightlang_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
if 'linenothreshold' in options:
|
||||
try:
|
||||
linenothreshold = int(options['linenothreshold'])
|
||||
except Exception:
|
||||
linenothreshold = 10
|
||||
else:
|
||||
linenothreshold = sys.maxint
|
||||
return [addnodes.highlightlang(lang=arguments[0].strip(),
|
||||
linenothreshold=linenothreshold)]
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {
|
||||
'linenothreshold': directives.unchanged,
|
||||
}
|
||||
|
||||
highlightlang_directive.content = 0
|
||||
highlightlang_directive.arguments = (1, 0, 0)
|
||||
highlightlang_directive.options = {'linenothreshold': directives.unchanged}
|
||||
directives.register_directive('highlight', highlightlang_directive)
|
||||
# old name
|
||||
directives.register_directive('highlightlang', highlightlang_directive)
|
||||
|
||||
|
||||
# ------ code-block directive --------------------------------------------------
|
||||
|
||||
def codeblock_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
code = u'\n'.join(content)
|
||||
literal = nodes.literal_block(code, code)
|
||||
literal['language'] = arguments[0]
|
||||
literal['linenos'] = 'linenos' in options
|
||||
return [literal]
|
||||
|
||||
codeblock_directive.content = 1
|
||||
codeblock_directive.arguments = (1, 0, 0)
|
||||
codeblock_directive.options = {'linenos': directives.flag}
|
||||
directives.register_directive('code-block', codeblock_directive)
|
||||
directives.register_directive('sourcecode', codeblock_directive)
|
||||
|
||||
|
||||
# ------ literalinclude directive ----------------------------------------------
|
||||
|
||||
def literalinclude_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
"""Like .. include:: :literal:, but only warns if the include file is
|
||||
not found."""
|
||||
if not state.document.settings.file_insertion_enabled:
|
||||
return [state.document.reporter.warning('File insertion disabled',
|
||||
line=lineno)]
|
||||
env = state.document.settings.env
|
||||
rel_fn = arguments[0]
|
||||
source_dir = path.dirname(path.abspath(state_machine.input_lines.source(
|
||||
lineno - state_machine.input_offset - 1)))
|
||||
fn = path.normpath(path.join(source_dir, rel_fn))
|
||||
|
||||
if 'pyobject' in options and 'lines' in options:
|
||||
return [state.document.reporter.warning(
|
||||
'Cannot use both "pyobject" and "lines" options', line=lineno)]
|
||||
|
||||
encoding = options.get('encoding', env.config.source_encoding)
|
||||
try:
|
||||
f = codecs.open(fn, 'rU', encoding)
|
||||
lines = f.readlines()
|
||||
f.close()
|
||||
except (IOError, OSError):
|
||||
return [state.document.reporter.warning(
|
||||
'Include file %r not found or reading it failed' % arguments[0],
|
||||
line=lineno)]
|
||||
except UnicodeError:
|
||||
return [state.document.reporter.warning(
|
||||
'Encoding %r used for reading included file %r seems to '
|
||||
'be wrong, try giving an :encoding: option' %
|
||||
(encoding, arguments[0]))]
|
||||
|
||||
objectname = options.get('pyobject')
|
||||
if objectname is not None:
|
||||
from sphinx.pycode import ModuleAnalyzer
|
||||
analyzer = ModuleAnalyzer.for_file(fn, '')
|
||||
tags = analyzer.find_tags()
|
||||
if objectname not in tags:
|
||||
return [state.document.reporter.warning(
|
||||
'Object named %r not found in include file %r' %
|
||||
(objectname, arguments[0]), line=lineno)]
|
||||
def run(self):
|
||||
if 'linenothreshold' in self.options:
|
||||
try:
|
||||
linenothreshold = int(self.options['linenothreshold'])
|
||||
except Exception:
|
||||
linenothreshold = 10
|
||||
else:
|
||||
lines = lines[tags[objectname][1] - 1 : tags[objectname][2] - 1]
|
||||
linenothreshold = sys.maxint
|
||||
return [addnodes.highlightlang(lang=self.arguments[0].strip(),
|
||||
linenothreshold=linenothreshold)]
|
||||
|
||||
linespec = options.get('lines')
|
||||
if linespec is not None:
|
||||
|
||||
class CodeBlock(Directive):
|
||||
"""
|
||||
Directive for a code block with special highlighting or line numbering
|
||||
settings.
|
||||
"""
|
||||
|
||||
has_content = True
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {
|
||||
'linenos': directives.flag,
|
||||
}
|
||||
|
||||
def run(self):
|
||||
code = u'\n'.join(self.content)
|
||||
literal = nodes.literal_block(code, code)
|
||||
literal['language'] = self.arguments[0]
|
||||
literal['linenos'] = 'linenos' in self.options
|
||||
return [literal]
|
||||
|
||||
|
||||
class LiteralInclude(Directive):
|
||||
"""
|
||||
Like ``.. include:: :literal:``, but only warns if the include file is
|
||||
not found, and does not raise errors. Also has several options for
|
||||
selecting what to include.
|
||||
"""
|
||||
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {
|
||||
'linenos': directives.flag,
|
||||
'language': directives.unchanged_required,
|
||||
'encoding': directives.encoding,
|
||||
'pyobject': directives.unchanged_required,
|
||||
'lines': directives.unchanged_required,
|
||||
'start-after': directives.unchanged_required,
|
||||
'end-before': directives.unchanged_required,
|
||||
}
|
||||
|
||||
def run(self):
|
||||
document = self.state.document
|
||||
filename = self.arguments[0]
|
||||
if not document.settings.file_insertion_enabled:
|
||||
return [document.reporter.warning('File insertion disabled',
|
||||
line=self.lineno)]
|
||||
env = document.settings.env
|
||||
rel_fn = filename
|
||||
sourcename = self.state_machine.input_lines.source(
|
||||
self.lineno - self.state_machine.input_offset - 1)
|
||||
source_dir = path.dirname(path.abspath(sourcename))
|
||||
fn = path.normpath(path.join(source_dir, rel_fn))
|
||||
|
||||
if 'pyobject' in self.options and 'lines' in self.options:
|
||||
return [document.reporter.warning(
|
||||
'Cannot use both "pyobject" and "lines" options',
|
||||
line=self.lineno)]
|
||||
|
||||
encoding = self.options.get('encoding', env.config.source_encoding)
|
||||
try:
|
||||
linelist = parselinenos(linespec, len(lines))
|
||||
except ValueError, err:
|
||||
return [state.document.reporter.warning(str(err), line=lineno)]
|
||||
lines = [lines[i] for i in linelist]
|
||||
f = codecs.open(fn, 'rU', encoding)
|
||||
lines = f.readlines()
|
||||
f.close()
|
||||
except (IOError, OSError):
|
||||
return [document.reporter.warning(
|
||||
'Include file %r not found or reading it failed' % filename,
|
||||
line=self.lineno)]
|
||||
except UnicodeError:
|
||||
return [document.reporter.warning(
|
||||
'Encoding %r used for reading included file %r seems to '
|
||||
'be wrong, try giving an :encoding: option' %
|
||||
(encoding, filename))]
|
||||
|
||||
startafter = options.get('start-after')
|
||||
endbefore = options.get('end-before')
|
||||
if startafter is not None or endbefore is not None:
|
||||
use = not startafter
|
||||
res = []
|
||||
for line in lines:
|
||||
if not use and startafter in line:
|
||||
use = True
|
||||
elif use and endbefore in line:
|
||||
use = False
|
||||
break
|
||||
elif use:
|
||||
res.append(line)
|
||||
lines = res
|
||||
objectname = self.options.get('pyobject')
|
||||
if objectname is not None:
|
||||
from sphinx.pycode import ModuleAnalyzer
|
||||
analyzer = ModuleAnalyzer.for_file(fn, '')
|
||||
tags = analyzer.find_tags()
|
||||
if objectname not in tags:
|
||||
return [document.reporter.warning(
|
||||
'Object named %r not found in include file %r' %
|
||||
(objectname, filename), line=self.lineno)]
|
||||
else:
|
||||
lines = lines[tags[objectname][1]-1 : tags[objectname][2]-1]
|
||||
|
||||
text = ''.join(lines)
|
||||
retnode = nodes.literal_block(text, text, source=fn)
|
||||
retnode.line = 1
|
||||
if options.get('language', ''):
|
||||
retnode['language'] = options['language']
|
||||
if 'linenos' in options:
|
||||
retnode['linenos'] = True
|
||||
state.document.settings.env.note_dependency(rel_fn)
|
||||
return [retnode]
|
||||
linespec = self.options.get('lines')
|
||||
if linespec is not None:
|
||||
try:
|
||||
linelist = parselinenos(linespec, len(lines))
|
||||
except ValueError, err:
|
||||
return [document.reporter.warning(str(err), line=self.lineno)]
|
||||
lines = [lines[i] for i in linelist]
|
||||
|
||||
literalinclude_directive.options = {
|
||||
'linenos': directives.flag,
|
||||
'language': directives.unchanged_required,
|
||||
'encoding': directives.encoding,
|
||||
'pyobject': directives.unchanged_required,
|
||||
'lines': directives.unchanged_required,
|
||||
'start-after': directives.unchanged_required,
|
||||
'end-before': directives.unchanged_required,
|
||||
}
|
||||
literalinclude_directive.content = 0
|
||||
literalinclude_directive.arguments = (1, 0, 0)
|
||||
directives.register_directive('literalinclude', literalinclude_directive)
|
||||
startafter = self.options.get('start-after')
|
||||
endbefore = self.options.get('end-before')
|
||||
if startafter is not None or endbefore is not None:
|
||||
use = not startafter
|
||||
res = []
|
||||
for line in lines:
|
||||
if not use and startafter in line:
|
||||
use = True
|
||||
elif use and endbefore in line:
|
||||
use = False
|
||||
break
|
||||
elif use:
|
||||
res.append(line)
|
||||
lines = res
|
||||
|
||||
text = ''.join(lines)
|
||||
retnode = nodes.literal_block(text, text, source=fn)
|
||||
retnode.line = 1
|
||||
if self.options.get('language', ''):
|
||||
retnode['language'] = self.options['language']
|
||||
if 'linenos' in self.options:
|
||||
retnode['linenos'] = True
|
||||
document.settings.env.note_dependency(rel_fn)
|
||||
return [retnode]
|
||||
|
||||
|
||||
directives.register_directive('highlight', Highlight)
|
||||
directives.register_directive('highlightlang', Highlight) # old name
|
||||
directives.register_directive('code-block', CodeBlock)
|
||||
directives.register_directive('sourcecode', CodeBlock)
|
||||
directives.register_directive('literalinclude', LiteralInclude)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -15,266 +15,313 @@ from docutils.parsers.rst import directives
|
||||
from sphinx import addnodes
|
||||
from sphinx.locale import pairindextypes
|
||||
from sphinx.util import patfilter, ws_re, caption_ref_re, url_re, docname_join
|
||||
from sphinx.util.compat import make_admonition
|
||||
from sphinx.util.compat import Directive, make_admonition
|
||||
|
||||
|
||||
# ------ the TOC tree ----------------------------------------------------------
|
||||
class TocTree(Directive):
|
||||
"""
|
||||
Directive to notify Sphinx about the hierarchical structure of the docs,
|
||||
and to include a table-of-contents like tree in the current document.
|
||||
"""
|
||||
|
||||
def toctree_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
env = state.document.settings.env
|
||||
suffix = env.config.source_suffix
|
||||
glob = 'glob' in options
|
||||
has_content = True
|
||||
required_arguments = 0
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {
|
||||
'maxdepth': int,
|
||||
'glob': directives.flag,
|
||||
'hidden': directives.flag,
|
||||
}
|
||||
|
||||
ret = []
|
||||
# (title, ref) pairs, where ref may be a document, or an external link,
|
||||
# and title may be None if the document's title is to be used
|
||||
entries = []
|
||||
includefiles = []
|
||||
includetitles = {}
|
||||
all_docnames = env.found_docs.copy()
|
||||
# don't add the currently visited file in catch-all patterns
|
||||
all_docnames.remove(env.docname)
|
||||
for entry in content:
|
||||
if not entry:
|
||||
continue
|
||||
if not glob:
|
||||
# look for explicit titles and documents ("Some Title <document>").
|
||||
m = caption_ref_re.match(entry)
|
||||
if m:
|
||||
ref = m.group(2)
|
||||
title = m.group(1)
|
||||
docname = ref
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
suffix = env.config.source_suffix
|
||||
glob = 'glob' in self.options
|
||||
|
||||
ret = []
|
||||
# (title, ref) pairs, where ref may be a document, or an external link,
|
||||
# and title may be None if the document's title is to be used
|
||||
entries = []
|
||||
includefiles = []
|
||||
includetitles = {}
|
||||
all_docnames = env.found_docs.copy()
|
||||
# don't add the currently visited file in catch-all patterns
|
||||
all_docnames.remove(env.docname)
|
||||
for entry in self.content:
|
||||
if not entry:
|
||||
continue
|
||||
if not glob:
|
||||
# look for explicit titles ("Some Title <document>")
|
||||
m = caption_ref_re.match(entry)
|
||||
if m:
|
||||
ref = m.group(2)
|
||||
title = m.group(1)
|
||||
docname = ref
|
||||
else:
|
||||
ref = docname = entry
|
||||
title = None
|
||||
# remove suffixes (backwards compatibility)
|
||||
if docname.endswith(suffix):
|
||||
docname = docname[:-len(suffix)]
|
||||
# absolutize filenames
|
||||
docname = docname_join(env.docname, docname)
|
||||
if url_re.match(ref) or ref == 'self':
|
||||
entries.append((title, ref))
|
||||
elif docname not in env.found_docs:
|
||||
ret.append(self.state.document.reporter.warning(
|
||||
'toctree references unknown document %r' % docname,
|
||||
line=self.lineno))
|
||||
else:
|
||||
entries.append((title, docname))
|
||||
includefiles.append(docname)
|
||||
else:
|
||||
ref = docname = entry
|
||||
title = None
|
||||
# remove suffixes (backwards compatibility)
|
||||
if docname.endswith(suffix):
|
||||
docname = docname[:-len(suffix)]
|
||||
# absolutize filenames
|
||||
docname = docname_join(env.docname, docname)
|
||||
if url_re.match(ref) or ref == 'self':
|
||||
entries.append((title, ref))
|
||||
elif docname not in env.found_docs:
|
||||
ret.append(state.document.reporter.warning(
|
||||
'toctree references unknown document %r' % docname,
|
||||
line=lineno))
|
||||
else:
|
||||
entries.append((title, docname))
|
||||
includefiles.append(docname)
|
||||
else:
|
||||
patname = docname_join(env.docname, entry)
|
||||
docnames = sorted(patfilter(all_docnames, patname))
|
||||
for docname in docnames:
|
||||
all_docnames.remove(docname) # don't include it again
|
||||
entries.append((None, docname))
|
||||
includefiles.append(docname)
|
||||
if not docnames:
|
||||
ret.append(state.document.reporter.warning(
|
||||
'toctree glob pattern %r didn\'t match any documents'
|
||||
% entry, line=lineno))
|
||||
subnode = addnodes.toctree()
|
||||
subnode['parent'] = env.docname
|
||||
subnode['entries'] = entries
|
||||
subnode['includefiles'] = includefiles
|
||||
subnode['maxdepth'] = options.get('maxdepth', -1)
|
||||
subnode['glob'] = glob
|
||||
subnode['hidden'] = 'hidden' in options
|
||||
ret.append(subnode)
|
||||
return ret
|
||||
|
||||
toctree_directive.content = 1
|
||||
toctree_directive.options = {'maxdepth': int, 'glob': directives.flag,
|
||||
'hidden': directives.flag}
|
||||
directives.register_directive('toctree', toctree_directive)
|
||||
patname = docname_join(env.docname, entry)
|
||||
docnames = sorted(patfilter(all_docnames, patname))
|
||||
for docname in docnames:
|
||||
all_docnames.remove(docname) # don't include it again
|
||||
entries.append((None, docname))
|
||||
includefiles.append(docname)
|
||||
if not docnames:
|
||||
ret.append(self.state.document.reporter.warning(
|
||||
'toctree glob pattern %r didn\'t match any documents'
|
||||
% entry, line=self.lineno))
|
||||
subnode = addnodes.toctree()
|
||||
subnode['parent'] = env.docname
|
||||
subnode['entries'] = entries
|
||||
subnode['includefiles'] = includefiles
|
||||
subnode['maxdepth'] = self.options.get('maxdepth', -1)
|
||||
subnode['glob'] = glob
|
||||
subnode['hidden'] = 'hidden' in self.options
|
||||
ret.append(subnode)
|
||||
return ret
|
||||
|
||||
|
||||
# ------ section metadata ------------------------------------------------------
|
||||
class Module(Directive):
|
||||
"""
|
||||
Directive to mark description of a new module.
|
||||
"""
|
||||
|
||||
def module_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
env = state.document.settings.env
|
||||
modname = arguments[0].strip()
|
||||
noindex = 'noindex' in options
|
||||
env.currmodule = modname
|
||||
env.note_module(modname, options.get('synopsis', ''),
|
||||
options.get('platform', ''),
|
||||
'deprecated' in options)
|
||||
modulenode = addnodes.module()
|
||||
modulenode['modname'] = modname
|
||||
modulenode['synopsis'] = options.get('synopsis', '')
|
||||
targetnode = nodes.target('', '', ids=['module-' + modname])
|
||||
state.document.note_explicit_target(targetnode)
|
||||
ret = [modulenode, targetnode]
|
||||
if 'platform' in options:
|
||||
modulenode['platform'] = options['platform']
|
||||
node = nodes.paragraph()
|
||||
node += nodes.emphasis('', _('Platforms: '))
|
||||
node += nodes.Text(options['platform'], options['platform'])
|
||||
ret.append(node)
|
||||
# the synopsis isn't printed; in fact, it is only used in the
|
||||
# modindex currently
|
||||
if not noindex:
|
||||
indextext = _('%s (module)') % modname
|
||||
inode = addnodes.index(entries=[('single', indextext,
|
||||
'module-' + modname, modname)])
|
||||
ret.insert(0, inode)
|
||||
return ret
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {
|
||||
'platform': lambda x: x,
|
||||
'synopsis': lambda x: x,
|
||||
'noindex': directives.flag,
|
||||
'deprecated': directives.flag,
|
||||
}
|
||||
|
||||
module_directive.arguments = (1, 0, 0)
|
||||
module_directive.options = {'platform': lambda x: x,
|
||||
'synopsis': lambda x: x,
|
||||
'noindex': directives.flag,
|
||||
'deprecated': directives.flag}
|
||||
directives.register_directive('module', module_directive)
|
||||
|
||||
|
||||
def currentmodule_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
# This directive is just to tell people that we're documenting
|
||||
# stuff in module foo, but links to module foo won't lead here.
|
||||
env = state.document.settings.env
|
||||
modname = arguments[0].strip()
|
||||
if modname == 'None':
|
||||
env.currmodule = None
|
||||
else:
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
modname = self.arguments[0].strip()
|
||||
noindex = 'noindex' in self.options
|
||||
env.currmodule = modname
|
||||
return []
|
||||
|
||||
currentmodule_directive.arguments = (1, 0, 0)
|
||||
directives.register_directive('currentmodule', currentmodule_directive)
|
||||
env.note_module(modname, self.options.get('synopsis', ''),
|
||||
self.options.get('platform', ''),
|
||||
'deprecated' in self.options)
|
||||
modulenode = addnodes.module()
|
||||
modulenode['modname'] = modname
|
||||
modulenode['synopsis'] = self.options.get('synopsis', '')
|
||||
targetnode = nodes.target('', '', ids=['module-' + modname])
|
||||
self.state.document.note_explicit_target(targetnode)
|
||||
ret = [modulenode, targetnode]
|
||||
if 'platform' in self.options:
|
||||
platform = self.options['platform']
|
||||
modulenode['platform'] = platform
|
||||
node = nodes.paragraph()
|
||||
node += nodes.emphasis('', _('Platforms: '))
|
||||
node += nodes.Text(platform, platform)
|
||||
ret.append(node)
|
||||
# the synopsis isn't printed; in fact, it is only used in the
|
||||
# modindex currently
|
||||
if not noindex:
|
||||
indextext = _('%s (module)') % modname
|
||||
inode = addnodes.index(entries=[('single', indextext,
|
||||
'module-' + modname, modname)])
|
||||
ret.insert(0, inode)
|
||||
return ret
|
||||
|
||||
|
||||
def author_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
# Show authors only if the show_authors option is on
|
||||
env = state.document.settings.env
|
||||
if not env.config.show_authors:
|
||||
return []
|
||||
para = nodes.paragraph()
|
||||
emph = nodes.emphasis()
|
||||
para += emph
|
||||
if name == 'sectionauthor':
|
||||
text = _('Section author: ')
|
||||
elif name == 'moduleauthor':
|
||||
text = _('Module author: ')
|
||||
else:
|
||||
text = _('Author: ')
|
||||
emph += nodes.Text(text, text)
|
||||
inodes, messages = state.inline_text(arguments[0], lineno)
|
||||
emph.extend(inodes)
|
||||
return [para] + messages
|
||||
class CurrentModule(Directive):
|
||||
"""
|
||||
This directive is just to tell Sphinx that we're documenting
|
||||
stuff in module foo, but links to module foo won't lead here.
|
||||
"""
|
||||
|
||||
author_directive.arguments = (1, 0, 1)
|
||||
directives.register_directive('sectionauthor', author_directive)
|
||||
directives.register_directive('moduleauthor', author_directive)
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {}
|
||||
|
||||
|
||||
def program_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
env = state.document.settings.env
|
||||
program = ws_re.sub('-', arguments[0].strip())
|
||||
if program == 'None':
|
||||
env.currprogram = None
|
||||
else:
|
||||
env.currprogram = program
|
||||
return []
|
||||
|
||||
program_directive.arguments = (1, 0, 1)
|
||||
directives.register_directive('program', program_directive)
|
||||
|
||||
|
||||
# ------ index markup ----------------------------------------------------------
|
||||
|
||||
indextypes = [
|
||||
'single', 'pair', 'triple',
|
||||
]
|
||||
|
||||
def index_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
arguments = arguments[0].split('\n')
|
||||
env = state.document.settings.env
|
||||
targetid = 'index-%s' % env.index_num
|
||||
env.index_num += 1
|
||||
targetnode = nodes.target('', '', ids=[targetid])
|
||||
state.document.note_explicit_target(targetnode)
|
||||
indexnode = addnodes.index()
|
||||
indexnode['entries'] = ne = []
|
||||
for entry in arguments:
|
||||
entry = entry.strip()
|
||||
for type in pairindextypes:
|
||||
if entry.startswith(type+':'):
|
||||
value = entry[len(type)+1:].strip()
|
||||
value = pairindextypes[type] + '; ' + value
|
||||
ne.append(('pair', value, targetid, value))
|
||||
break
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
modname = self.arguments[0].strip()
|
||||
if modname == 'None':
|
||||
env.currmodule = None
|
||||
else:
|
||||
for type in indextypes:
|
||||
env.currmodule = modname
|
||||
return []
|
||||
|
||||
|
||||
class Author(Directive):
|
||||
"""
|
||||
Directive to give the name of the author of the current document
|
||||
or section. Shown in the output only if the show_authors option is on.
|
||||
"""
|
||||
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
option_spec = {}
|
||||
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
if not env.config.show_authors:
|
||||
return []
|
||||
para = nodes.paragraph()
|
||||
emph = nodes.emphasis()
|
||||
para += emph
|
||||
if self.name == 'sectionauthor':
|
||||
text = _('Section author: ')
|
||||
elif self.name == 'moduleauthor':
|
||||
text = _('Module author: ')
|
||||
else:
|
||||
text = _('Author: ')
|
||||
emph += nodes.Text(text, text)
|
||||
inodes, messages = self.state.inline_text(self.arguments[0],
|
||||
self.lineno)
|
||||
emph.extend(inodes)
|
||||
return [para] + messages
|
||||
|
||||
|
||||
class Program(Directive):
|
||||
"""
|
||||
Directive to name the program for which options are documented.
|
||||
"""
|
||||
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
option_spec = {}
|
||||
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
program = ws_re.sub('-', self.arguments[0].strip())
|
||||
if program == 'None':
|
||||
env.currprogram = None
|
||||
else:
|
||||
env.currprogram = program
|
||||
return []
|
||||
|
||||
|
||||
class Index(Directive):
|
||||
"""
|
||||
Directive to add entries to the index.
|
||||
"""
|
||||
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
option_spec = {}
|
||||
|
||||
indextypes = [
|
||||
'single', 'pair', 'triple',
|
||||
]
|
||||
|
||||
def run(self):
|
||||
arguments = self.arguments[0].split('\n')
|
||||
env = self.state.document.settings.env
|
||||
targetid = 'index-%s' % env.index_num
|
||||
env.index_num += 1
|
||||
targetnode = nodes.target('', '', ids=[targetid])
|
||||
self.state.document.note_explicit_target(targetnode)
|
||||
indexnode = addnodes.index()
|
||||
indexnode['entries'] = ne = []
|
||||
for entry in arguments:
|
||||
entry = entry.strip()
|
||||
for type in pairindextypes:
|
||||
if entry.startswith(type+':'):
|
||||
value = entry[len(type)+1:].strip()
|
||||
ne.append((type, value, targetid, value))
|
||||
value = pairindextypes[type] + '; ' + value
|
||||
ne.append(('pair', value, targetid, value))
|
||||
break
|
||||
# shorthand notation for single entries
|
||||
else:
|
||||
for value in entry.split(','):
|
||||
value = value.strip()
|
||||
if not value:
|
||||
continue
|
||||
ne.append(('single', value, targetid, value))
|
||||
return [indexnode, targetnode]
|
||||
|
||||
index_directive.arguments = (1, 0, 1)
|
||||
directives.register_directive('index', index_directive)
|
||||
|
||||
# ------ versionadded/versionchanged -------------------------------------------
|
||||
|
||||
def version_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
node = addnodes.versionmodified()
|
||||
node.document = state.document
|
||||
node['type'] = name
|
||||
node['version'] = arguments[0]
|
||||
if len(arguments) == 2:
|
||||
inodes, messages = state.inline_text(arguments[1], lineno+1)
|
||||
node.extend(inodes)
|
||||
if content:
|
||||
state.nested_parse(content, content_offset, node)
|
||||
ret = [node] + messages
|
||||
else:
|
||||
ret = [node]
|
||||
env = state.document.settings.env
|
||||
env.note_versionchange(node['type'], node['version'], node, lineno)
|
||||
return ret
|
||||
|
||||
version_directive.arguments = (1, 1, 1)
|
||||
version_directive.content = 1
|
||||
|
||||
directives.register_directive('deprecated', version_directive)
|
||||
directives.register_directive('versionadded', version_directive)
|
||||
directives.register_directive('versionchanged', version_directive)
|
||||
for type in self.indextypes:
|
||||
if entry.startswith(type+':'):
|
||||
value = entry[len(type)+1:].strip()
|
||||
ne.append((type, value, targetid, value))
|
||||
break
|
||||
# shorthand notation for single entries
|
||||
else:
|
||||
for value in entry.split(','):
|
||||
value = value.strip()
|
||||
if not value:
|
||||
continue
|
||||
ne.append(('single', value, targetid, value))
|
||||
return [indexnode, targetnode]
|
||||
|
||||
|
||||
# ------ see also --------------------------------------------------------------
|
||||
class VersionChange(Directive):
|
||||
"""
|
||||
Directive to describe a change/addition/deprecation in a specific version.
|
||||
"""
|
||||
|
||||
def seealso_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
ret = make_admonition(
|
||||
addnodes.seealso, name, [_('See also')], options, content,
|
||||
lineno, content_offset, block_text, state, state_machine)
|
||||
if arguments:
|
||||
argnodes, msgs = state.inline_text(arguments[0], lineno)
|
||||
para = nodes.paragraph()
|
||||
para += argnodes
|
||||
para += msgs
|
||||
ret[0].insert(1, para)
|
||||
return ret
|
||||
has_content = True
|
||||
required_arguments = 1
|
||||
optional_arguments = 1
|
||||
final_argument_whitespace = True
|
||||
option_spec = {}
|
||||
|
||||
seealso_directive.content = 1
|
||||
seealso_directive.arguments = (0, 1, 1)
|
||||
directives.register_directive('seealso', seealso_directive)
|
||||
def run(self):
|
||||
node = addnodes.versionmodified()
|
||||
node.document = self.state.document
|
||||
node['type'] = self.name
|
||||
node['version'] = self.arguments[0]
|
||||
if len(self.arguments) == 2:
|
||||
inodes, messages = self.state.inline_text(self.arguments[1],
|
||||
self.lineno+1)
|
||||
node.extend(inodes)
|
||||
if self.content:
|
||||
self.state.nested_parse(self.content, self.content_offset, node)
|
||||
ret = [node] + messages
|
||||
else:
|
||||
ret = [node]
|
||||
env = self.state.document.settings.env
|
||||
env.note_versionchange(node['type'], node['version'], node, self.lineno)
|
||||
return ret
|
||||
|
||||
|
||||
# ------ production list (for the reference) -----------------------------------
|
||||
class SeeAlso(Directive):
|
||||
"""
|
||||
An admonition mentioning things to look at as reference.
|
||||
"""
|
||||
|
||||
has_content = True
|
||||
required_arguments = 0
|
||||
optional_arguments = 1
|
||||
final_argument_whitespace = True
|
||||
option_spec = {}
|
||||
|
||||
def run(self):
|
||||
ret = make_admonition(
|
||||
addnodes.seealso, self.name, [_('See also')], self.options,
|
||||
self.content, self.lineno, self.content_offset, self.block_text,
|
||||
self.state, self.state_machine)
|
||||
if self.arguments:
|
||||
argnodes, msgs = self.state.inline_text(self.arguments[0],
|
||||
self.lineno)
|
||||
para = nodes.paragraph()
|
||||
para += argnodes
|
||||
para += msgs
|
||||
ret[0].insert(1, para)
|
||||
return ret
|
||||
|
||||
|
||||
token_re = re.compile('`([a-z_]+)`')
|
||||
|
||||
@ -297,149 +344,202 @@ def token_xrefs(text, env):
|
||||
retnodes.append(nodes.Text(text[pos:], text[pos:]))
|
||||
return retnodes
|
||||
|
||||
def productionlist_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
env = state.document.settings.env
|
||||
node = addnodes.productionlist()
|
||||
messages = []
|
||||
i = 0
|
||||
class ProductionList(Directive):
|
||||
"""
|
||||
Directive to list grammar productions.
|
||||
"""
|
||||
|
||||
for rule in arguments[0].split('\n'):
|
||||
if i == 0 and ':' not in rule:
|
||||
# production group
|
||||
continue
|
||||
i += 1
|
||||
try:
|
||||
name, tokens = rule.split(':', 1)
|
||||
except ValueError:
|
||||
break
|
||||
subnode = addnodes.production()
|
||||
subnode['tokenname'] = name.strip()
|
||||
if subnode['tokenname']:
|
||||
idname = 'grammar-token-%s' % subnode['tokenname']
|
||||
if idname not in state.document.ids:
|
||||
subnode['ids'].append(idname)
|
||||
state.document.note_implicit_target(subnode, subnode)
|
||||
env.note_reftarget('token', subnode['tokenname'], idname)
|
||||
subnode.extend(token_xrefs(tokens, env))
|
||||
node.append(subnode)
|
||||
return [node] + messages
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
option_spec = {}
|
||||
|
||||
productionlist_directive.content = 0
|
||||
productionlist_directive.arguments = (1, 0, 1)
|
||||
directives.register_directive('productionlist', productionlist_directive)
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
node = addnodes.productionlist()
|
||||
messages = []
|
||||
i = 0
|
||||
|
||||
|
||||
# ------ glossary directive ----------------------------------------------------
|
||||
|
||||
def glossary_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
"""Glossary with cross-reference targets for :term: roles."""
|
||||
env = state.document.settings.env
|
||||
node = addnodes.glossary()
|
||||
node.document = state.document
|
||||
state.nested_parse(content, content_offset, node)
|
||||
|
||||
# the content should be definition lists
|
||||
dls = [child for child in node if isinstance(child, nodes.definition_list)]
|
||||
# now, extract definition terms to enable cross-reference creation
|
||||
for dl in dls:
|
||||
dl['classes'].append('glossary')
|
||||
for li in dl.children:
|
||||
if not li.children or not isinstance(li[0], nodes.term):
|
||||
for rule in self.arguments[0].split('\n'):
|
||||
if i == 0 and ':' not in rule:
|
||||
# production group
|
||||
continue
|
||||
termtext = li.children[0].astext()
|
||||
new_id = 'term-' + nodes.make_id(termtext)
|
||||
if new_id in env.gloss_entries:
|
||||
new_id = 'term-' + str(len(env.gloss_entries))
|
||||
env.gloss_entries.add(new_id)
|
||||
li[0]['names'].append(new_id)
|
||||
li[0]['ids'].append(new_id)
|
||||
state.document.settings.env.note_reftarget('term', termtext.lower(),
|
||||
new_id)
|
||||
# add an index entry too
|
||||
indexnode = addnodes.index()
|
||||
indexnode['entries'] = [('single', termtext, new_id, termtext)]
|
||||
li.insert(0, indexnode)
|
||||
return [node]
|
||||
|
||||
glossary_directive.content = 1
|
||||
glossary_directive.arguments = (0, 0, 0)
|
||||
directives.register_directive('glossary', glossary_directive)
|
||||
i += 1
|
||||
try:
|
||||
name, tokens = rule.split(':', 1)
|
||||
except ValueError:
|
||||
break
|
||||
subnode = addnodes.production()
|
||||
subnode['tokenname'] = name.strip()
|
||||
if subnode['tokenname']:
|
||||
idname = 'grammar-token-%s' % subnode['tokenname']
|
||||
if idname not in self.state.document.ids:
|
||||
subnode['ids'].append(idname)
|
||||
self.state.document.note_implicit_target(subnode, subnode)
|
||||
env.note_reftarget('token', subnode['tokenname'], idname)
|
||||
subnode.extend(token_xrefs(tokens, env))
|
||||
node.append(subnode)
|
||||
return [node] + messages
|
||||
|
||||
|
||||
# ------ miscellaneous markup --------------------------------------------------
|
||||
class Glossary(Directive):
|
||||
"""
|
||||
Directive to create a glossary with cross-reference targets
|
||||
for :term: roles.
|
||||
"""
|
||||
|
||||
def centered_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
if not arguments:
|
||||
return []
|
||||
subnode = addnodes.centered()
|
||||
inodes, messages = state.inline_text(arguments[0], lineno)
|
||||
subnode.extend(inodes)
|
||||
return [subnode] + messages
|
||||
has_content = True
|
||||
required_arguments = 0
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {}
|
||||
|
||||
centered_directive.arguments = (1, 0, 1)
|
||||
directives.register_directive('centered', centered_directive)
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
node = addnodes.glossary()
|
||||
node.document = self.state.document
|
||||
self.state.nested_parse(self.content, self.content_offset, node)
|
||||
|
||||
# the content should be definition lists
|
||||
dls = [child for child in node
|
||||
if isinstance(child, nodes.definition_list)]
|
||||
# now, extract definition terms to enable cross-reference creation
|
||||
for dl in dls:
|
||||
dl['classes'].append('glossary')
|
||||
for li in dl.children:
|
||||
if not li.children or not isinstance(li[0], nodes.term):
|
||||
continue
|
||||
termtext = li.children[0].astext()
|
||||
new_id = 'term-' + nodes.make_id(termtext)
|
||||
if new_id in env.gloss_entries:
|
||||
new_id = 'term-' + str(len(env.gloss_entries))
|
||||
env.gloss_entries.add(new_id)
|
||||
li[0]['names'].append(new_id)
|
||||
li[0]['ids'].append(new_id)
|
||||
env.note_reftarget('term', termtext.lower(), new_id)
|
||||
# add an index entry too
|
||||
indexnode = addnodes.index()
|
||||
indexnode['entries'] = [('single', termtext, new_id, termtext)]
|
||||
li.insert(0, indexnode)
|
||||
return [node]
|
||||
|
||||
|
||||
def acks_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
node = addnodes.acks()
|
||||
node.document = state.document
|
||||
state.nested_parse(content, content_offset, node)
|
||||
if len(node.children) != 1 or not isinstance(node.children[0],
|
||||
nodes.bullet_list):
|
||||
return [state.document.reporter.warning('.. acks content is not a list',
|
||||
line=lineno)]
|
||||
return [node]
|
||||
class Centered(Directive):
|
||||
"""
|
||||
Directive to create a centered line of bold text.
|
||||
"""
|
||||
|
||||
acks_directive.content = 1
|
||||
acks_directive.arguments = (0, 0, 0)
|
||||
directives.register_directive('acks', acks_directive)
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
option_spec = {}
|
||||
|
||||
def run(self):
|
||||
if not self.arguments:
|
||||
return []
|
||||
subnode = addnodes.centered()
|
||||
inodes, messages = self.state.inline_text(self.arguments[0],
|
||||
self.lineno)
|
||||
subnode.extend(inodes)
|
||||
return [subnode] + messages
|
||||
|
||||
|
||||
def hlist_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
ncolumns = options.get('columns', 2)
|
||||
node = nodes.paragraph()
|
||||
node.document = state.document
|
||||
state.nested_parse(content, content_offset, node)
|
||||
if len(node.children) != 1 or not isinstance(node.children[0],
|
||||
nodes.bullet_list):
|
||||
return [state.document.reporter.warning(
|
||||
'.. hlist content is not a list', line=lineno)]
|
||||
fulllist = node.children[0]
|
||||
# create a hlist node where the items are distributed
|
||||
npercol, nmore = divmod(len(fulllist), ncolumns)
|
||||
index = 0
|
||||
newnode = addnodes.hlist()
|
||||
for column in range(ncolumns):
|
||||
endindex = index + (column < nmore and (npercol+1) or npercol)
|
||||
col = addnodes.hlistcol()
|
||||
col += nodes.bullet_list()
|
||||
col[0] += fulllist.children[index:endindex]
|
||||
index = endindex
|
||||
newnode += col
|
||||
return [newnode]
|
||||
|
||||
hlist_directive.content = 1
|
||||
hlist_directive.arguments = (0, 0, 0)
|
||||
hlist_directive.options = {'columns': int}
|
||||
directives.register_directive('hlist', hlist_directive)
|
||||
class Acks(Directive):
|
||||
"""
|
||||
Directive for a list of names.
|
||||
"""
|
||||
|
||||
has_content = True
|
||||
required_arguments = 0
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {}
|
||||
|
||||
def run(self):
|
||||
node = addnodes.acks()
|
||||
node.document = self.state.document
|
||||
self.state.nested_parse(self.content, self.content_offset, node)
|
||||
if len(node.children) != 1 or not isinstance(node.children[0],
|
||||
nodes.bullet_list):
|
||||
return [self.state.document.reporter.warning(
|
||||
'.. acks content is not a list', line=self.lineno)]
|
||||
return [node]
|
||||
|
||||
|
||||
def tabularcolumns_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
# support giving explicit tabulary column definition to latex
|
||||
node = addnodes.tabular_col_spec()
|
||||
node['spec'] = arguments[0]
|
||||
return [node]
|
||||
class HList(Directive):
|
||||
"""
|
||||
Directive for a list that gets compacted horizontally.
|
||||
"""
|
||||
|
||||
tabularcolumns_directive.content = 0
|
||||
tabularcolumns_directive.arguments = (1, 0, 1)
|
||||
directives.register_directive('tabularcolumns', tabularcolumns_directive)
|
||||
has_content = True
|
||||
required_arguments = 0
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {
|
||||
'columns': int,
|
||||
}
|
||||
|
||||
def run(self):
|
||||
ncolumns = self.options.get('columns', 2)
|
||||
node = nodes.paragraph()
|
||||
node.document = self.state.document
|
||||
self.state.nested_parse(self.content, self.content_offset, node)
|
||||
if len(node.children) != 1 or not isinstance(node.children[0],
|
||||
nodes.bullet_list):
|
||||
return [self.state.document.reporter.warning(
|
||||
'.. hlist content is not a list', line=self.lineno)]
|
||||
fulllist = node.children[0]
|
||||
# create a hlist node where the items are distributed
|
||||
npercol, nmore = divmod(len(fulllist), ncolumns)
|
||||
index = 0
|
||||
newnode = addnodes.hlist()
|
||||
for column in range(ncolumns):
|
||||
endindex = index + (column < nmore and (npercol+1) or npercol)
|
||||
col = addnodes.hlistcol()
|
||||
col += nodes.bullet_list()
|
||||
col[0] += fulllist.children[index:endindex]
|
||||
index = endindex
|
||||
newnode += col
|
||||
return [newnode]
|
||||
|
||||
|
||||
class TabularColumns(Directive):
|
||||
"""
|
||||
Directive to give an explicit tabulary column definition to LaTeX.
|
||||
"""
|
||||
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
option_spec = {}
|
||||
|
||||
def run(self):
|
||||
node = addnodes.tabular_col_spec()
|
||||
node['spec'] = self.arguments[0]
|
||||
return [node]
|
||||
|
||||
|
||||
directives.register_directive('toctree', TocTree)
|
||||
directives.register_directive('module', Module)
|
||||
directives.register_directive('currentmodule', CurrentModule)
|
||||
directives.register_directive('sectionauthor', Author)
|
||||
directives.register_directive('moduleauthor', Author)
|
||||
directives.register_directive('program', Program)
|
||||
directives.register_directive('index', Index)
|
||||
directives.register_directive('deprecated', VersionChange)
|
||||
directives.register_directive('versionadded', VersionChange)
|
||||
directives.register_directive('versionchanged', VersionChange)
|
||||
directives.register_directive('seealso', SeeAlso)
|
||||
directives.register_directive('productionlist', ProductionList)
|
||||
directives.register_directive('glossary', Glossary)
|
||||
directives.register_directive('centered', Centered)
|
||||
directives.register_directive('acks', Acks)
|
||||
directives.register_directive('hlist', HList)
|
||||
directives.register_directive('tabularcolumns', TabularColumns)
|
||||
|
||||
# register the standard rst class directive under a different name
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user