# -*- 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)