5a1ff51bab5d13abb9d941df29b6a1659ce3e31b
[ghc.git] / docs / users_guide / flags.py
1 # GHC User's Guide flag extension
2 #
3 # This file defines a Sphinx extension to document GHC flags.
4 # It introduces a directive:
5 #
6 # .. ghc-flag::
7 # :shortdesc: A short description (REQUIRED)
8 # :type: dynamic, mode, dynamix/ ``:set`` (REQUIRED)
9 # :reverse: The reverse of the flag
10 # :category: The category to list the flag under (default: 'misc')
11 # :noindex: Do not list the flag anywhere (good for duplicates)
12 #
13 # That can be referenced using:
14 #
15 # :ghc-flag:`flag`
16 #
17 # As well as a directive to generate a display of flags:
18 #
19 # .. flag-print::
20 # :type: table/list/summary (REQUIRED)
21 # :category: Limit the output to a single category
22 #
23 # The two main functions in this extension are Flag.after_content() which adds
24 # flag metadata into the environment, and flagprint.generate_output(), which
25 # reads the metadata back out and formats it as desired.
26
27 from docutils import nodes
28 from docutils.parsers.rst import Directive, directives
29 from sphinx import addnodes
30 from sphinx.domains.std import GenericObject
31 from sphinx.errors import SphinxError
32
33 ### Settings
34
35 # Categories to titles as well as a canonical list of categories
36 categories = {
37 '': 'All flags',
38 'codegen': 'Code generation',
39 'coverage': 'Program coverage',
40 'cpp': 'C pre-processor',
41 'debugging': 'Debugging the compiler',
42 'interactive': 'Interactive mode',
43 'interface-files': 'Interface files',
44 'keep-intermediates': 'Keeping intermediate files',
45 'language': 'Language options',
46 'linking': 'Linking options',
47 'misc': 'Miscellaneous options',
48 'modes': 'Modes of operation',
49 'optimization': 'Individual optimizations',
50 'optimization-levels': 'Optimization levels',
51 'packages': 'Package options',
52 'phases': 'Phases of compilation',
53 'phase-programs': 'Overriding external programs',
54 'phase-options': 'Phase-specific options',
55 'platform-options': 'Platform-specific options',
56 'plugins': 'Compiler plugins',
57 'profiling': 'Profiling',
58 'recompilation': 'Recompilation checking',
59 'redirect-output': 'Redirecting output',
60 'search-path': 'Finding imports',
61 'temp-files': 'Temporary files',
62 'verbosity': 'Verbosity options',
63 'warnings': 'Warnings',
64 }
65
66 # Map file names to default flag categories
67 file_defaults = {
68 'debugging': 'debugging',
69 'ghci': 'interactive',
70 'glasgow_exts': 'language',
71 'packages': 'packages',
72 'profiling': 'profiling',
73 'safe_haskell': 'language',
74 'separate_compilation': 'redirect-output',
75 'using-warnings': 'warnings',
76 'using-optimisation': 'optimization'
77 }
78
79
80 ### Flag declaration
81
82 # This class inherits from Sphinx's internal GenericObject, which drives
83 # the add_object_type() utility function. We want to keep that tooling,
84 # but need to override some of the functionality.
85 class Flag(GenericObject):
86
87 # The options that can be passed to our directive and their validators
88 option_spec = {
89 'shortdesc': directives.unchanged_required,
90 'type': directives.unchanged_required,
91 'reverse': directives.unchanged,
92 'category': directives.unchanged,
93 'noindex': directives.flag
94 }
95
96 # The index directive generated unless :noindex: is specified
97 indextemplate = 'pair: %s; GHC option'
98
99
100 # Generate docutils node from directive arguments
101 @staticmethod
102 def _parse_flag(env, sig, signode):
103
104 # Break flag into name and args
105 import re
106 parts = re.split(r'( |=|\[)', sig, 1)
107 flag = parts[0]
108 # Bold printed name
109 signode += addnodes.desc_name(flag, flag)
110 if len(parts) > 1:
111 args = ''.join(parts[1:])
112 # Smaller arguments
113 signode += addnodes.desc_addname(args, args)
114
115 # Reference name left unchanged
116 return sig
117
118 # Used in the GenericObject class
119 parse_node = _parse_flag
120
121 # Override the (empty) function that is called at the end of run()
122 # to append metadata about this flag into the environment
123 def after_content(self):
124
125 # If noindex, then do not include this flag in the table
126 if 'noindex' in self.options:
127 return
128
129 # Validity checking
130 if 'shortdesc' not in self.options:
131 raise SphinxError('ghc-flag (%s) directive missing :shortdesc: key' % self.names)
132 if 'type' not in self.options:
133 raise SphinxError('ghc-flag (%s) directive missing :type: key' % self.names)
134
135 # Set the flag category (default: misc)
136 self.category = 'misc'
137 if not 'category' in self.options or self.options['category'] == '':
138 if self.env.docname in file_defaults:
139 self.category = file_defaults[self.env.docname]
140 else:
141 self.category = self.options['category']
142
143 # Manually create references
144 name_string = ", ".join([':ghc-flag:`'+n+'`' for n in self.names])
145 reverse_string = ''
146 if 'reverse' in self.options and self.options['reverse'] != '':
147 reverse_string = ':ghc-flag:`' + self.options['reverse'] + '`'
148
149 # Create nodes for each cell of the table
150 name_node = nodes.paragraph()
151 shortdesc_node = nodes.paragraph()
152 type_node = nodes.paragraph()
153 reverse_node = nodes.paragraph()
154
155
156 # Nodes expect an internal ViewList type for the content,
157 # we are just spoofing it here
158 from docutils.statemachine import ViewList
159 name_vl = ViewList(initlist=[name_string],
160 source=self.env.docname, parent=[])
161 shortdesc_vl = ViewList(initlist=[self.options['shortdesc']],
162 source=self.env.docname, parent=[])
163 type_vl = ViewList(initlist=[self.options['type']],
164 source=self.env.docname, parent=[])
165 reverse_vl = ViewList(initlist=[reverse_string],
166 source=self.env.docname, parent=[])
167
168
169 # Parse the content into the nodes
170 self.state.nested_parse(name_vl, 0, name_node)
171 self.state.nested_parse(shortdesc_vl, 0, shortdesc_node)
172 self.state.nested_parse(type_vl, 0, type_node)
173 self.state.nested_parse(reverse_vl, 0, reverse_node)
174
175
176 # The parsing adds extra layers that we don't need
177 name_node = name_node[0]
178 shortdesc_node = shortdesc_node[0]
179
180 # Append this flag to the environment, initializing if necessary
181 if not hasattr(self.env, 'all_flags'):
182 self.env.all_flags = []
183 self.env.all_flags.append({
184 'names': self.names,
185 'docname': self.env.docname,
186 'category': self.category,
187 'cells': [name_node, shortdesc_node, type_node, reverse_node],
188 })
189
190
191 ### Flag Printing
192
193 # Taken from Docutils source inside the ListTable class. We must bypass
194 # using the class itself, but this function comes in handy.
195 def build_table_from_list(table_data, col_widths):
196 table = nodes.table()
197 tgroup = nodes.tgroup(cols=len(col_widths))
198 table += tgroup
199 for col_width in col_widths:
200 colspec = nodes.colspec(colwidth=col_width)
201 tgroup += colspec
202 rows = []
203 for row in table_data:
204 row_node = nodes.row()
205 for cell in row:
206 entry = nodes.entry()
207 entry += cell
208 row_node += entry
209 rows.append(row_node)
210 thead = nodes.thead()
211 thead.extend(rows[:1])
212 tgroup += thead
213 tbody = nodes.tbody()
214 tbody.extend(rows[1:])
215 tgroup += tbody
216 return table
217
218
219 # Generate a table of flags
220 def generate_flag_table(flags, category):
221
222 # Create column headers for table
223 header = []
224 for h in ["Flag", "Description", "Type", "Reverse"]:
225 inline = nodes.inline(text=h)
226 header.append(inline)
227
228 flags_list = [header]
229
230 for flag_info in flags:
231
232 flags_list.append(flag_info['cells'])
233
234 # The column width hints only apply to html,
235 # latex widths are set in file (see flags.rst)
236 table = build_table_from_list(flags_list, [28, 34, 10, 28])
237
238 # Flag tables have lots of content, so we need to set 'longtable'
239 # to allow for pagebreaks. (latex specific)
240 table['classes'].append('longtable')
241
242 return table
243
244
245 # Generate a list of flags and their short descriptions
246 def generate_flag_list(flags, category):
247
248 list_node = nodes.definition_list()
249
250 for flag_info in flags:
251
252 dl_item_node = nodes.definition_list_item()
253 term_node = nodes.term()
254 # The man writer is picky, so we have to remove the outer
255 # paragraph node to get just the flag name
256 term_node += flag_info['cells'][0][0]
257 dl_item_node += term_node
258 def_node = nodes.definition()
259 def_node += flag_info['cells'][1]
260 dl_item_node += def_node
261
262 list_node += dl_item_node
263
264 return list_node
265
266
267 # Generate a block of flag names under a category
268 def generate_flag_summary(flags, category):
269
270 summary_node = nodes.definition_list_item()
271 term_node = nodes.term(text=categories[category])
272 summary_node += term_node
273 block = nodes.definition()
274 summary_node += block
275
276 # Fill block with flags
277 for flag_info in flags:
278
279 for name in flag_info['names']:
280 block += nodes.literal(text=name)
281 block += nodes.inline(text=' ')
282
283 block += nodes.inline(text='\n')
284
285 return summary_node
286
287 # Output dispatch table
288 handlers = {
289 'table': generate_flag_table,
290 'list': generate_flag_list,
291 'summary': generate_flag_summary
292 }
293
294
295 # Generic node for printing flag output
296 class flagprint(nodes.General, nodes.Element):
297
298 def __init__(self, output_type='', category='', **kwargs):
299
300 nodes.Element.__init__(self, rawsource='', **kwargs)
301
302 # Verify options
303 if category not in categories:
304 error = "flagprint: Unknown category: " + category
305 raise ValueError(error)
306 if output_type not in handlers:
307 error = "flagprint: Unknown output type: " + output_type
308 raise ValueError(error)
309
310 # Store the options
311 self.options = {
312 'type': output_type,
313 'category': category
314 }
315
316
317 # The man writer has a copy issue, so we explicitly override it here
318 def copy(self):
319 newnode = flagprint(output_type=self.options['type'],
320 category=self.options['category'], **self.attributes)
321 newnode.source = self.source
322 newnode.line = self.line
323 return newnode
324
325
326 def generate_output(self, app, fromdocname):
327 env = app.builder.env
328
329 # Filter flags before passing to handlers
330 flags = []
331
332 for flag_info in sorted(env.all_flags,
333 key=lambda fi: fi['names'][0].lower()):
334
335 if not (self.options['category'] == '' or
336 self.options['category'] == flag_info['category']):
337 continue
338
339 # Resolve all references as if they were originated from this node.
340 # This fixes the relative uri.
341 for cell in flag_info['cells']:
342 for ref in cell.traverse(addnodes.pending_xref):
343 ref['refdoc'] = fromdocname
344 env.resolve_references(cell, flag_info['docname'], app.builder)
345
346 flags.append(flag_info)
347
348 handler = handlers[self.options['type']]
349 self.replace_self(handler(flags, self.options['category']))
350
351
352 # A directive to create flagprint nodes
353 class FlagPrintDirective(Directive):
354
355 option_spec = {
356 'type': directives.unchanged_required,
357 'category': directives.unchanged
358 }
359
360 def run(self):
361
362 # Process options
363 category = ''
364 if 'category' in self.options:
365 category = self.options['category']
366
367 # Create a flagprint node
368 node = flagprint(output_type=self.options['type'], category=category)
369 return [node]
370
371
372 ### Additional processing
373
374 # Convert every flagprint node into its output format
375 def process_print_nodes(app, doctree, fromdocname):
376
377 for node in doctree.traverse(flagprint):
378 node.generate_output(app, fromdocname)
379
380
381 # To avoid creating duplicates in the serialized environment, clear all
382 # flags originating from a file before re-reading it.
383 def purge_flags(app, env, docname):
384
385 if not hasattr(env, 'all_flags'):
386 return
387
388 env.all_flags = [flag for flag in env.all_flags
389 if flag['docname'] != docname]
390
391 ### Initialization
392
393 def setup(app):
394
395 # Add ghc-flag directive, and override the class with our own
396 app.add_object_type('ghc-flag', 'ghc-flag')
397 app.add_directive_to_domain('std', 'ghc-flag', Flag)
398
399 # Add new node and directive
400 app.add_node(flagprint)
401 app.add_directive('flag-print', FlagPrintDirective)
402
403 # Add our generator and cleanup functions as callbacks
404 app.connect('doctree-resolved', process_print_nodes)
405 app.connect('env-purge-doc', purge_flags)
406
407 return {'version': '1.0'}