]> git.donarmstrong.com Git - neurodebian.git/blob - sphinx/sphinxext/quote.py
Also for stats report which repo and which job number use our setup
[neurodebian.git] / sphinx / sphinxext / quote.py
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:
3 """
4    ext.quote
5    ~~~~~~~~~
6
7    Collect and render individual quotes (quote directive) or
8    selections (quotes directive) of quotes.
9
10    Example::
11
12       .. quote::
13          :author: Dr. Joe Black
14                  :affiliation: Someone important, somewhere nice
15                  :date: 1990-01-01
16                  :tags: software, Debian, sphinx
17                  :group: Research software projects
18
19              quote.py is a wonderful extension
20
21           .. quotes::
22              :random: 1
23                  :tags: sphinx
24
25
26    :copyright: Copyright 2010 Yaroslav O. Halchenko
27    :license: BSD
28
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.
32
33 """
34
35 import operator
36 from random import sample
37
38 from docutils import nodes
39 from docutils.parsers.rst import directives, Directive
40 from docutils.parsers.rst.directives import body
41
42 from sphinx.environment import NoUri
43 from sphinx.util.compat import Directive, make_admonition
44
45
46 class quote_node(nodes.Body, nodes.Element): pass
47 class quotes(nodes.General, nodes.Element): pass
48
49 def _info(msg):
50         # print "I: ", msg
51         pass
52
53 def _prep_tags(options):
54         """Extract tags listed in a string"""
55         options['tags'] = set((x.strip()
56                                                    for x in
57                                                    options.get('tags', '').split(',')))
58
59 class Quote(Directive):
60     """
61     A quote entry, displayed (if configured) in the form of an admonition.
62     """
63
64     has_content = True
65     required_arguments = 0
66     optional_arguments = 0
67     final_argument_whitespace = False
68         option_spec = {
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}
75
76     def run(self):
77         state = self.state
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')
82                 else:
83                         targetid = "index-%s" % env.index_num
84         targetnode = nodes.target('', '', ids=[targetid])
85                 targetnode['classes'] = ['epigraph']
86
87         node = quote_node()
88                 node += nodes.block_quote(
89                         '',
90                         nodes.paragraph('', '\n'.join(self.content), classes=['text']))
91                 #state.nested_parse(self.content, self.content_offset, node)
92
93         for element in node:
94             if isinstance(element, nodes.block_quote):
95                 element['classes'] += ['epigraph']
96
97                 signode = [nodes.attribution('--', '--')]
98                 # Embed all components within attributions
99                 siglb = nodes.line_block('')
100                 # Pre-format some
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']:
106                         if el in options:
107                                 siglb += [nodes.inline('', '  '+options[el], classes=[el])]
108                 signode[0].extend(siglb)
109                 node[0].extend(signode)
110                 node.line = self.lineno
111                 # tune up options
112                 _prep_tags(self.options)
113                 node.options = options
114         return [targetnode] + [node]
115
116
117
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")
123
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):
128         try:
129             targetnode = node.parent[node.parent.index(node) - 1]
130             if not isinstance(targetnode, nodes.target):
131                 raise IndexError
132         except IndexError:
133             targetnode = None
134         env.quote_all_quotes.append({
135             'docname': env.docname,
136             'lineno': node.line,
137             'quote': node, #.deepcopy(),
138             'target': targetnode,
139         })
140
141
142 class Quotes(Directive):
143     """
144     A list of all quote entries.
145     """
146
147     has_content = False
148     required_arguments = 0
149     optional_arguments = 0
150     final_argument_whitespace = False
151         option_spec = {
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'))
156                 }
157
158     def run(self):
159         # Simply insert an empty quotes node which will be replaced later
160         # when process_quote_nodes is called
161                 res = quotes('')
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)
167         return [res]
168
169
170
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
175
176     if not hasattr(env, 'quote_all_quotes'):
177         env.quote_all_quotes = []
178
179         #_info("process_quote_nodes '%s' %i"
180         #         % (fromdocname, len(env.quote_all_quotes)))
181
182     for node in doctree.traverse(quotes):
183
184         content = []
185                 filters = []
186                 loptions = node.options
187
188                 # Filter by tags?
189                 if 'tags' in loptions:
190                         ltags = loptions['tags']
191                         filters.append(
192                                 lambda quote: set.issuperset(
193                                         quote.options['tags'], ltags))
194                 # Filter by group?
195                 if 'group' in loptions:
196                         loptions['group']
197                         filters.append(
198                                 lambda quote:
199                                 quote.options.get('group', '') == loptions['group'])
200
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],
205                                                   True):
206                                 continue
207
208             # Insert into the quotes
209             content.append(quote_entry)
210             ## content.append(para)
211
212                 # Handle additional quotes options
213
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'])
218
219                 # Group items into sections and intersperse the list
220                 # with section nodes
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 ]
233
234                 # Substitute with the content
235         node.replace_self(content)
236
237
238
239 def purge_quotes(app, env, docname):
240     if not hasattr(env, 'quote_all_quotes'):
241         return
242         _info("purge_quotes")
243     env.quote_all_quotes = [quote for quote in env.quote_all_quotes
244                           if quote['docname'] != docname]
245
246
247 def quotes_noop(self, node):
248         pass
249
250 def setup(app):
251     app.add_node(quotes)
252     app.add_node(quote_node,
253                  html=(quotes_noop, quotes_noop),
254                  latex=(quotes_noop, quotes_noop),
255                  text=(quotes_noop, quotes_noop))
256
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)