1 #emacs: -*- coding: utf-8; mode: python-mode; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: t -*-
2 #ex: set sts=4 ts=4 sw=4 et:
7 Collect and render individual quotes (quote directive) or
8 selections (quotes directive) of quotes.
13 :author: Dr. Joe Black
14 :affiliation: Someone important, somewhere nice
16 :tags: software, Debian, sphinx
17 :group: Research software projects
19 quote.py is a wonderful extension
26 :copyright: Copyright 2010 Yaroslav O. Halchenko
29 Based on todo.py extension from sphinx:
30 :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
31 :license: BSD, see LICENSE for details.
36 from random import sample
38 from docutils import nodes
39 from docutils.parsers.rst import directives, Directive
40 from docutils.parsers.rst.directives import body
42 from sphinx.environment import NoUri
43 from sphinx.util.compat import Directive, make_admonition
46 class quote_node(nodes.Body, nodes.Element): pass
47 class quotes(nodes.General, nodes.Element): pass
53 def _prep_tags(options):
54 """Extract tags listed in a string"""
55 options['tags'] = set((x.strip()
57 options.get('tags', '').split(',')))
59 class Quote(Directive):
61 A quote entry, displayed (if configured) in the form of an admonition.
65 required_arguments = 0
66 optional_arguments = 0
67 final_argument_whitespace = False
69 'author': directives.unchanged,
70 'affiliation': directives.unchanged,
71 'date': directives.unchanged,
72 'group': directives.unchanged,
73 'tags': directives.unchanged, # list of tags per quote
74 'source': directives.unchanged}
78 env = self.state.document.settings.env
79 options = self.options
80 if hasattr(env, 'new_serialno'):
81 targetid = 'index-%s' % env.new_serialno('index')
83 targetid = "index-%s" % env.index_num
84 targetnode = nodes.target('', '', ids=[targetid])
85 targetnode['classes'] = ['epigraph']
88 node += nodes.block_quote(
90 nodes.paragraph('', '\n'.join(self.content), classes=['text']))
91 #state.nested_parse(self.content, self.content_offset, node)
94 if isinstance(element, nodes.block_quote):
95 element['classes'] += ['epigraph']
97 signode = [nodes.attribution('--', '--')]
98 # Embed all components within attributions
99 siglb = nodes.line_block('')
101 if 'date' in options:
102 options['date'] = '[%(date)s]' % options
103 if 'source' in options:
104 options['source'] = 'Source: %(source)s' % options
105 for el in ['author', 'date', 'affiliation', 'source']:
107 siglb += [nodes.inline('', ' '+options[el], classes=[el])]
108 signode[0].extend(siglb)
109 node[0].extend(signode)
110 node.line = self.lineno
112 _prep_tags(self.options)
113 node.options = options
114 return [targetnode] + [node]
118 def process_quotes(app, doctree):
119 # collect all quotes in the environment
120 # this is not done in the directive itself because it some transformations
121 # must have already been run, e.g. substitutions
122 _info("process_quotes")
124 env = app.builder.env
125 if not hasattr(env, 'quote_all_quotes'):
126 env.quote_all_quotes = []
127 for node in doctree.traverse(quote_node):
129 targetnode = node.parent[node.parent.index(node) - 1]
130 if not isinstance(targetnode, nodes.target):
134 env.quote_all_quotes.append({
135 'docname': env.docname,
137 'quote': node, #.deepcopy(),
138 'target': targetnode,
142 class Quotes(Directive):
144 A list of all quote entries.
148 required_arguments = 0
149 optional_arguments = 0
150 final_argument_whitespace = False
152 'random': directives.positive_int, # how many to randomly output
153 'group': directives.unchanged, # what group to show
154 'tags': directives.unchanged, # list of tags to be matched
155 # 'sections': lambda a: directives.choice(a, ('date', 'group'))
159 # Simply insert an empty quotes node which will be replaced later
160 # when process_quote_nodes is called
162 # tags which must be matched
163 if 'tags' in self.options:
164 _prep_tags(self.options)
165 res.options = self.options
166 _info("Run Quotes %s" % res)
171 def process_quote_nodes(app, doctree, fromdocname):
172 # Replace all quotes nodes with a list of the collected quotes.
173 # Augment each quote with a backlink to the original location.
174 env = app.builder.env
176 if not hasattr(env, 'quote_all_quotes'):
177 env.quote_all_quotes = []
179 #_info("process_quote_nodes '%s' %i"
180 # % (fromdocname, len(env.quote_all_quotes)))
182 for node in doctree.traverse(quotes):
186 loptions = node.options
189 if 'tags' in loptions:
190 ltags = loptions['tags']
192 lambda quote: set.issuperset(
193 quote.options['tags'], ltags))
195 if 'group' in loptions:
199 quote.options.get('group', '') == loptions['group'])
201 for quote_info in env.quote_all_quotes:
202 quote_entry = quote_info['quote']
203 if not reduce(operator.__and__,
204 [f(quote_entry) for f in filters],
208 # Insert into the quotes
209 content.append(quote_entry)
210 ## content.append(para)
212 # Handle additional quotes options
214 # Select a limited number of random samples
215 if loptions.get('random', None):
216 # Randomly select desired number of quotes
217 content = sample(content, loptions['random'])
219 # Group items into sections and intersperse the list
221 if loptions.get('sections', None):
222 raise NotImplementedError
223 term = loptions['sections']
224 terms = [q.options.get(term, None) for q in content]
225 terms = list(set([x for x in terms if x]))
226 # Simply sort according to what to group on,
227 # and then insert sections?
228 section = nodes.section('')
229 section.extend(nodes.title('', 'XXX'))
230 section += content[:2]
231 section.parent = content[0].parent
232 content = [ section ]
234 # Substitute with the content
235 node.replace_self(content)
239 def purge_quotes(app, env, docname):
240 if not hasattr(env, 'quote_all_quotes'):
242 _info("purge_quotes")
243 env.quote_all_quotes = [quote for quote in env.quote_all_quotes
244 if quote['docname'] != docname]
247 def quotes_noop(self, node):
252 app.add_node(quote_node,
253 html=(quotes_noop, quotes_noop),
254 latex=(quotes_noop, quotes_noop),
255 text=(quotes_noop, quotes_noop))
257 app.add_directive('quote', Quote)
258 app.add_directive('quotes', Quotes)
259 app.connect('doctree-read', process_quotes)
260 app.connect('doctree-resolved', process_quote_nodes)
261 app.connect('env-purge-doc', purge_quotes)