210 lines
7.3 KiB
Python
210 lines
7.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
question
|
|
~~~~~~~~
|
|
|
|
Allow questions to be inserted into your documentation.
|
|
The questionlist directive collects
|
|
all questions of your project and lists them along with a backlink to the
|
|
original location.
|
|
|
|
:copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
|
:license: BSD, see LICENSE for details.
|
|
"""
|
|
|
|
global questno, qno
|
|
questno = 1
|
|
qno = 0
|
|
|
|
from docutils import nodes
|
|
|
|
from sphinx.locale import _
|
|
from sphinx.environment import NoUri
|
|
from sphinx.util.nodes import set_source_info
|
|
#from sphinx.util.compat import Directive #, make_admonition
|
|
from docutils.parsers.rst.directives.admonitions import Directive
|
|
#from docutils.parsers.rst.directives.admonitions import make_admonition
|
|
|
|
class question_node(nodes.Admonition, nodes.Element): pass
|
|
class questionlist(nodes.General, nodes.Element): pass
|
|
|
|
def make_admonition(node_class, name, arguments, options, content, lineno,
|
|
content_offset, block_text, state, state_machine):
|
|
if not content:
|
|
error = state_machine.reporter.error(
|
|
'The "%s" admonition is empty; content required.' % (name),
|
|
nodes.literal_block(block_text, block_text), line=lineno)
|
|
return [error]
|
|
text = '\n'.join(content)
|
|
admonition_node = node_class(text)
|
|
if arguments:
|
|
title_text = arguments[0]
|
|
textnodes, messages = state.inline_text(title_text, lineno)
|
|
admonition_node += nodes.title(title_text, '', *textnodes)
|
|
admonition_node += messages
|
|
if 'class' in options:
|
|
classes = options['class']
|
|
else:
|
|
classes = ['admonition-' + nodes.make_id(title_text)]
|
|
admonition_node['classes'] += classes
|
|
state.nested_parse(content, content_offset, admonition_node)
|
|
return [admonition_node]
|
|
|
|
class Question(Directive):
|
|
"""
|
|
A question entry, displayed in the form of an admonition.
|
|
"""
|
|
|
|
has_content = True
|
|
required_arguments = 0
|
|
optional_arguments = 1
|
|
final_argument_whitespace = False
|
|
option_spec = {'number': int}
|
|
|
|
def run(self):
|
|
global questno
|
|
questno = self.options.get('number', questno)
|
|
env = self.state.document.settings.env
|
|
targetid = 'index-%s' % env.new_serialno('index')
|
|
targetnode = nodes.target('', '', ids=[targetid])
|
|
|
|
self.options['class'] = [_('question')]
|
|
ad = make_admonition(question_node, self.name,
|
|
[_('Question %d'%questno)], self.options,
|
|
self.content, self.lineno, self.content_offset,
|
|
self.block_text, self.state, self.state_machine)
|
|
questno += 1
|
|
set_source_info(self, ad[0])
|
|
return [targetnode] + ad
|
|
|
|
|
|
def process_questions(app, doctree):
|
|
# collect all questions in the environment
|
|
# this is not done in the directive itself because it some transformations
|
|
# must have already been run, e.g. substitutions
|
|
env = app.builder.env
|
|
if not hasattr(env, 'question_all_questions'):
|
|
env.question_all_questions = []
|
|
global qno
|
|
for node in doctree.traverse(question_node):
|
|
try:
|
|
targetnode = node.parent[node.parent.index(node) - 1]
|
|
if not isinstance(targetnode, nodes.target):
|
|
raise IndexError
|
|
except IndexError:
|
|
targetnode = None
|
|
qno += 1
|
|
env.question_all_questions.append({
|
|
'docname': env.docname,
|
|
'source': node.source or env.doc2path(env.docname),
|
|
'lineno': node.line,
|
|
'question': node.deepcopy(),
|
|
'questno': qno,
|
|
'target': targetnode,
|
|
})
|
|
|
|
|
|
class QuestionList(Directive):
|
|
"""
|
|
A list of all question entries.
|
|
"""
|
|
|
|
has_content = False
|
|
required_arguments = 0
|
|
optional_arguments = 0
|
|
final_argument_whitespace = False
|
|
option_spec = {}
|
|
|
|
def run(self):
|
|
# Simply insert an empty questionlist node which will be replaced later
|
|
# when process_question_nodes is called
|
|
return [questionlist('')]
|
|
|
|
|
|
def process_question_nodes(app, doctree, fromdocname):
|
|
### if not app.config['question_include_questions']:
|
|
if False:
|
|
for node in doctree.traverse(question_node):
|
|
node.parent.remove(node)
|
|
|
|
# Replace all questionlist nodes with a list of the collected questions.
|
|
# Augment each question with a backlink to the original location.
|
|
env = app.builder.env
|
|
|
|
if not hasattr(env, 'question_all_questions'):
|
|
env.question_all_questions = []
|
|
|
|
for node in doctree.traverse(questionlist):
|
|
### if not app.config['question_include_questions']:
|
|
# if False:
|
|
# node.replace_self([])
|
|
# continue
|
|
|
|
content = []
|
|
|
|
for question_info in env.question_all_questions:
|
|
qno = question_info['questno']
|
|
para = nodes.paragraph(classes=['question-source'])
|
|
description = _('<<original entry>>')
|
|
desc1 = description[:description.find('<<')]
|
|
desc2 = description[description.find('>>')+2:]
|
|
para += nodes.Text(desc1, desc1)
|
|
|
|
# Create a reference
|
|
newnode = nodes.reference('', '', internal=True)
|
|
### innernode = nodes.emphasis(_('original entry'), _('original entry'))
|
|
innernode = nodes.strong(_('Question %d'%qno), _('Question %d'%qno))
|
|
try:
|
|
newnode['refuri'] = app.builder.get_relative_uri(
|
|
fromdocname, question_info['docname'])
|
|
newnode['refuri'] += '#' + question_info['target']['refid']
|
|
except NoUri:
|
|
# ignore if no URI can be determined, e.g. for LaTeX output
|
|
pass
|
|
newnode.append(innernode)
|
|
para += newnode
|
|
para += nodes.Text(desc2, desc2)
|
|
|
|
# (Recursively) resolve references in the question content
|
|
question_entry = question_info['question']
|
|
env.resolve_references(question_entry, question_info['docname'],
|
|
app.builder)
|
|
|
|
# Insert into the questionlist
|
|
### content.append(question_entry)
|
|
content.append(para)
|
|
|
|
node.replace_self(content)
|
|
|
|
|
|
def purge_questions(app, env, docname):
|
|
if not hasattr(env, 'question_all_questions'):
|
|
return
|
|
env.question_all_questions = [question for question in env.question_all_questions
|
|
if question['docname'] != docname]
|
|
|
|
|
|
def visit_question_node(self, node):
|
|
self.visit_admonition(node)
|
|
|
|
def depart_question_node(self, node):
|
|
self.depart_admonition(node)
|
|
|
|
def setup(app):
|
|
app.add_config_value('question_include_questions', False, False)
|
|
|
|
app.add_node(questionlist)
|
|
app.add_node(question_node,
|
|
html=(visit_question_node, depart_question_node),
|
|
latex=(visit_question_node, depart_question_node),
|
|
text=(visit_question_node, depart_question_node),
|
|
man=(visit_question_node, depart_question_node),
|
|
texinfo=(visit_question_node, depart_question_node))
|
|
|
|
app.add_directive('question', Question)
|
|
app.add_directive('questionlist', QuestionList)
|
|
app.connect('doctree-read', process_questions)
|
|
app.connect('doctree-resolved', process_question_nodes)
|
|
app.connect('env-purge-doc', purge_questions)
|
|
|