Make language extensions their own category in the documentation
[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 # It also provides a directive to list language extensions:
24 #
25 # .. extension::
26 # :shortdesc: A short description (REQUIRED)
27 # :noindex: Do not list the extension anywhere (good for duplicates)
28 #
29 # This has the side-effect of an appropriate ghc-flag directive for the `-X`
30 # flag.
31 #
32 # Extensions can be referenced with
33 #
34 # :extension:`extension`
35 #
36 # Language exensions can be listed:
37 #
38 # .. extension-print::
39 # :type: table/list/summary (REQUIRED)
40 #
41 # The two main functions in this extension are Flag.after_content() which adds
42 # flag metadata into the environment, and flagprint.generate_output(), which
43 # reads the metadata back out and formats it as desired.
44 #
45 #
46
47 from docutils import nodes
48 from docutils.parsers.rst import Directive, directives
49 from sphinx import addnodes
50 from sphinx.domains.std import GenericObject
51 from sphinx.errors import SphinxError
52
53 ### Settings
54
55 # Categories to titles as well as a canonical list of categories
56 categories = {
57 '': 'All flags',
58 'codegen': 'Code generation',
59 'coverage': 'Program coverage',
60 'cpp': 'C pre-processor',
61 'debugging': 'Debugging the compiler',
62 'interactive': 'Interactive mode',
63 'interface-files': 'Interface files',
64 'keep-intermediates': 'Keeping intermediate files',
65 'language': 'Language options',
66 'linking': 'Linking options',
67 'misc': 'Miscellaneous options',
68 'modes': 'Modes of operation',
69 'optimization': 'Individual optimizations',
70 'optimization-levels': 'Optimization levels',
71 'packages': 'Package options',
72 'phases': 'Phases of compilation',
73 'phase-programs': 'Overriding external programs',
74 'phase-options': 'Phase-specific options',
75 'platform-options': 'Platform-specific options',
76 'plugins': 'Compiler plugins',
77 'profiling': 'Profiling',
78 'recompilation': 'Recompilation checking',
79 'redirect-output': 'Redirecting output',
80 'search-path': 'Finding imports',
81 'temp-files': 'Temporary files',
82 'verbosity': 'Verbosity options',
83 'warnings': 'Warnings',
84 }
85
86 # Map file names to default flag categories
87 file_defaults = {
88 'debugging': 'debugging',
89 'ghci': 'interactive',
90 'glasgow_exts': 'language',
91 'packages': 'packages',
92 'profiling': 'profiling',
93 'safe_haskell': 'language',
94 'separate_compilation': 'redirect-output',
95 'using-warnings': 'warnings',
96 'using-optimisation': 'optimization'
97 }
98
99
100 ### Flag declaration
101
102 # Functionality to add a flag to the tables, used by both Flag and LanguageExtension
103 class GenericFlag(GenericObject):
104 def register_flag(self, names, category, name_string, shortdesc, flag_type, reverse_string):
105 # Create nodes for each cell of the table
106 name_node = nodes.paragraph()
107 shortdesc_node = nodes.paragraph()
108 type_node = nodes.paragraph()
109 reverse_node = nodes.paragraph()
110
111
112 # Nodes expect an internal ViewList type for the content,
113 # we are just spoofing it here
114 from docutils.statemachine import ViewList
115 name_vl = ViewList(initlist=[name_string],
116 source=self.env.docname, parent=[])
117 shortdesc_vl = ViewList(initlist=[shortdesc],
118 source=self.env.docname, parent=[])
119 type_vl = ViewList(initlist=[flag_type],
120 source=self.env.docname, parent=[])
121 reverse_vl = ViewList(initlist=[reverse_string],
122 source=self.env.docname, parent=[])
123
124
125 # Parse the content into the nodes
126 self.state.nested_parse(name_vl, 0, name_node)
127 self.state.nested_parse(shortdesc_vl, 0, shortdesc_node)
128 self.state.nested_parse(type_vl, 0, type_node)
129 self.state.nested_parse(reverse_vl, 0, reverse_node)
130
131
132 # The parsing adds extra layers that we don't need
133 name_node = name_node[0]
134 shortdesc_node = shortdesc_node[0]
135
136 # Append this flag to the environment, initializing if necessary
137 if not hasattr(self.env, 'all_flags'):
138 self.env.all_flags = []
139 self.env.all_flags.append({
140 'names': names,
141 'docname': self.env.docname,
142 'category': category,
143 'cells': [name_node, shortdesc_node, type_node, reverse_node],
144 })
145
146 # This class inherits from Sphinx's internal GenericObject, which drives
147 # the add_object_type() utility function. We want to keep that tooling,
148 # but need to override some of the functionality.
149 class Flag(GenericFlag):
150
151 # The options that can be passed to our directive and their validators
152 option_spec = {
153 'shortdesc': directives.unchanged_required,
154 'type': directives.unchanged_required,
155 'reverse': directives.unchanged,
156 'category': directives.unchanged,
157 'noindex': directives.flag
158 }
159
160 # The index directive generated unless :noindex: is specified
161 indextemplate = 'pair: %s; GHC option'
162
163
164 # Generate docutils node from directive arguments
165 @staticmethod
166 def _parse_flag(env, sig, signode):
167
168 # Break flag into name and args
169 import re
170 parts = re.split(r'( |=|\[)', sig, 1)
171 flag = parts[0]
172 # Bold printed name
173 signode += addnodes.desc_name(flag, flag)
174 if len(parts) > 1:
175 args = ''.join(parts[1:])
176 # Smaller arguments
177 signode += addnodes.desc_addname(args, args)
178
179 # Reference name left unchanged
180 return sig
181
182 # Used in the GenericObject class
183 parse_node = _parse_flag
184
185 # Override the (empty) function that is called at the end of run()
186 # to append metadata about this flag into the environment
187 def after_content(self):
188
189 # If noindex, then do not include this flag in the table
190 if 'noindex' in self.options:
191 return
192
193 # Validity checking
194 if 'shortdesc' not in self.options:
195 raise SphinxError('ghc-flag (%s) directive missing :shortdesc: key' % self.names)
196 if 'type' not in self.options:
197 raise SphinxError('ghc-flag (%s) directive missing :type: key' % self.names)
198
199 # Set the flag category (default: misc)
200 self.category = 'misc'
201 if not 'category' in self.options or self.options['category'] == '':
202 if self.env.docname in file_defaults:
203 self.category = file_defaults[self.env.docname]
204 else:
205 self.category = self.options['category']
206
207 # Manually create references
208 name_string = ", ".join([':ghc-flag:`'+n+'`' for n in self.names])
209 reverse_string = ''
210 if 'reverse' in self.options and self.options['reverse'] != '':
211 reverse_string = ':ghc-flag:`' + self.options['reverse'] + '`'
212
213 self.register_flag(
214 self.names,
215 self.category,
216 name_string,
217 self.options['shortdesc'],
218 self.options['type'],
219 reverse_string)
220
221 # This class inherits from Sphinx's internal GenericObject, which drives
222 # the add_object_type() utility function. We want to keep that tooling,
223 # but need to override some of the functionality.
224 class LanguageExtension(GenericFlag):
225
226 # The options that can be passed to our directive and their validators
227 option_spec = {
228 'shortdesc': directives.unchanged_required,
229 'noindex': directives.flag
230 }
231
232 # The index directive generated unless :noindex: is specified
233 indextemplate = 'pair: %s; Language Extension'
234
235 # Invert the flag
236 @staticmethod
237 def _noname(name):
238 if name[:2] == "No":
239 return name[2:]
240 else:
241 return "No%s" % name
242
243 @staticmethod
244 def _onname(name):
245 if name[:2] == "No":
246 return name[2:]
247 else:
248 return name
249
250 # Add additional targets
251 def add_target_and_index(self, name, sig, signode):
252
253 GenericFlag.add_target_and_index(self, name, sig, signode)
254
255 # Mostly for consistency in URL anchors
256 signode['ids'].append('ghc-flag--X%s' % name)
257 # So that anchors stay valid even if an extension turns to on-by-default
258 signode['ids'].append('extension-%s' % self._noname(name))
259
260 targetname = '%s-%s' % (self.objtype, name)
261
262 # Add index entries for the -XFoo flag
263 self.indexnode['entries'].append(('pair', '-X%s; GHC option' % name,
264 targetname, '', None))
265
266 # Make this also addressable using :ghc-flag:-XFoo
267 self.env.domaindata['std']['objects']['ghc-flag', '-X%s' % name] = \
268 self.env.docname, 'extension-%s' % name
269 # Make this also addressable using :extension:-XNoFoo
270 self.env.domaindata['std']['objects']['extension', self._noname(name)] = \
271 self.env.docname, 'extension-%s' % name
272
273
274 # Override the (empty) function that is called at the end of run()
275 # to append metadata about this flag into the environment
276 def after_content(self):
277
278 # If noindex, then do not include this extension in the table
279 if 'noindex' in self.options:
280 return
281
282 # Validity checking
283 if len(self.names) < 1:
284 raise SphinxError('extension needs at least one name')
285 primary_name = self.names[0]
286 if 'shortdesc' not in self.options:
287 raise SphinxError('extension (%s) directive missing :shortdesc: key' % primary_name)
288
289 # Register the corresponding flags
290 for name in self.names:
291 self.register_flag(
292 ['-X%s' % name],
293 'language',
294 ':extension:`-X%s <%s>`' % (name, primary_name),
295 self.options['shortdesc'],
296 'dynamic',
297 ':extension:`-X%s <%s>`' % (self._noname(name), primary_name))
298
299 # Register the extension for the table, under the "on name" (no No...)
300 onname = self._onname(primary_name)
301
302 name_node = nodes.paragraph()
303 shortdesc_node = nodes.paragraph()
304 # Nodes expect an internal ViewList type for the content,
305 # we are just spoofing it here
306 from docutils.statemachine import ViewList
307 name_vl = ViewList(initlist=[':extension:`%s`' % onname],
308 source=self.env.docname, parent=[])
309 shortdesc_vl = ViewList(initlist=[self.options['shortdesc']],
310 source=self.env.docname, parent=[])
311 # Parse the content into the nodes
312 self.state.nested_parse(name_vl, 0, name_node)
313 self.state.nested_parse(shortdesc_vl, 0, shortdesc_node)
314 # The parsing adds extra layers that we don't need
315 name_node = name_node[0]
316 shortdesc_node = shortdesc_node[0]
317
318 if not hasattr(self.env, 'all_extensions'):
319 self.env.all_extensions = []
320 self.env.all_extensions.append({
321 'name': onname,
322 'docname': self.env.docname,
323 'cells': [name_node, shortdesc_node]
324 })
325
326 ### Flag Printing
327
328 # Taken from Docutils source inside the ListTable class. We must bypass
329 # using the class itself, but this function comes in handy.
330 def build_table_from_list(table_data, col_widths):
331 table = nodes.table()
332 tgroup = nodes.tgroup(cols=len(col_widths))
333 table += tgroup
334 for col_width in col_widths:
335 colspec = nodes.colspec(colwidth=col_width)
336 tgroup += colspec
337 rows = []
338 for row in table_data:
339 row_node = nodes.row()
340 for cell in row:
341 entry = nodes.entry()
342 entry += cell
343 row_node += entry
344 rows.append(row_node)
345 thead = nodes.thead()
346 thead.extend(rows[:1])
347 tgroup += thead
348 tbody = nodes.tbody()
349 tbody.extend(rows[1:])
350 tgroup += tbody
351 return table
352
353
354 # Generate a table of flags
355 def generate_flag_table(flags, category):
356
357 # Create column headers for table
358 header = []
359 for h in ["Flag", "Description", "Type", "Reverse"]:
360 inline = nodes.inline(text=h)
361 header.append(inline)
362
363 flags_list = [header]
364
365 for flag_info in flags:
366
367 flags_list.append(flag_info['cells'])
368
369 # The column width hints only apply to html,
370 # latex widths are set in file (see flags.rst)
371 table = build_table_from_list(flags_list, [28, 34, 10, 28])
372
373 # Flag tables have lots of content, so we need to set 'longtable'
374 # to allow for pagebreaks. (latex specific)
375 table['classes'].append('longtable')
376
377 return table
378
379
380 # Generate a list of flags and their short descriptions
381 def generate_flag_list(flags, category):
382
383 list_node = nodes.definition_list()
384
385 for flag_info in flags:
386
387 dl_item_node = nodes.definition_list_item()
388 term_node = nodes.term()
389 # The man writer is picky, so we have to remove the outer
390 # paragraph node to get just the flag name
391 term_node += flag_info['cells'][0][0]
392 dl_item_node += term_node
393 def_node = nodes.definition()
394 def_node += flag_info['cells'][1]
395 dl_item_node += def_node
396
397 list_node += dl_item_node
398
399 return list_node
400
401
402 # Generate a block of flag names under a category
403 def generate_flag_summary(flags, category):
404
405 summary_node = nodes.definition_list_item()
406 term_node = nodes.term(text=categories[category])
407 summary_node += term_node
408 block = nodes.definition()
409 summary_node += block
410
411 # Fill block with flags
412 for flag_info in flags:
413
414 for name in flag_info['names']:
415 block += nodes.literal(text=name)
416 block += nodes.inline(text=' ')
417
418 block += nodes.inline(text='\n')
419
420 return summary_node
421
422 # Output dispatch table
423 flag_handlers = {
424 'table': generate_flag_table,
425 'list': generate_flag_list,
426 'summary': generate_flag_summary
427 }
428
429
430 # Generic node for printing flag output
431 class flagprint(nodes.General, nodes.Element):
432
433 def __init__(self, output_type='', category='', **kwargs):
434
435 nodes.Element.__init__(self, rawsource='', **kwargs)
436
437 # Verify options
438 if category not in categories:
439 error = "flagprint: Unknown category: " + category
440 raise ValueError(error)
441 if output_type not in flag_handlers:
442 error = "flagprint: Unknown output type: " + output_type
443 raise ValueError(error)
444
445 # Store the options
446 self.options = {
447 'type': output_type,
448 'category': category
449 }
450
451
452 # The man writer has a copy issue, so we explicitly override it here
453 def copy(self):
454 newnode = flagprint(output_type=self.options['type'],
455 category=self.options['category'], **self.attributes)
456 newnode.source = self.source
457 newnode.line = self.line
458 return newnode
459
460
461 def generate_output(self, app, fromdocname):
462 env = app.builder.env
463
464 # Filter flags before passing to flag_handlers
465 flags = []
466
467 for flag_info in sorted(env.all_flags,
468 key=lambda fi: fi['names'][0].lower()):
469
470 if not (self.options['category'] == '' or
471 self.options['category'] == flag_info['category']):
472 continue
473
474 # Resolve all references as if they were originated from this node.
475 # This fixes the relative uri.
476 for cell in flag_info['cells']:
477 for ref in cell.traverse(addnodes.pending_xref):
478 ref['refdoc'] = fromdocname
479 env.resolve_references(cell, flag_info['docname'], app.builder)
480
481 flags.append(flag_info)
482
483 handler = flag_handlers[self.options['type']]
484 self.replace_self(handler(flags, self.options['category']))
485
486 # A directive to create flagprint nodes
487 class FlagPrintDirective(Directive):
488
489 option_spec = {
490 'type': directives.unchanged_required,
491 'category': directives.unchanged
492 }
493
494 def run(self):
495
496 # Process options
497 category = ''
498 if 'category' in self.options:
499 category = self.options['category']
500
501 # Create a flagprint node
502 node = flagprint(output_type=self.options['type'], category=category)
503 return [node]
504
505 ### Extension Printing
506
507
508 # Generate a table of flags
509 def generate_extension_table(extensions):
510
511 # Create column headers for table
512 header = []
513 for h in ["Extension", "Description"]:
514 inline = nodes.inline(text=h)
515 header.append(inline)
516
517 extension_list = [header]
518
519 for extension_info in extensions:
520 extension_list.append(extension_info['cells'])
521
522 # The column width hints only apply to html,
523 # latex widths are set in file (see flags.rst)
524 table = build_table_from_list(extension_list, [28, 72])
525
526 # Flag tables have lots of content, so we need to set 'longtable'
527 # to allow for pagebreaks. (latex specific)
528 table['classes'].append('longtable')
529
530 return table
531
532
533 # Output dispatch table
534 extension_handlers = {
535 'table': generate_extension_table,
536 }
537
538 # Generic node for printing extension output
539 class extensionprint(nodes.General, nodes.Element):
540
541 def __init__(self, output_type='', **kwargs):
542
543 nodes.Element.__init__(self, rawsource='', **kwargs)
544
545 # Verify options
546 if output_type not in extension_handlers:
547 error = "extensionprint: Unknown output type: " + output_type
548 raise ValueError(error)
549
550 # Store the options
551 self.options = {
552 'type': output_type,
553 }
554
555
556 # The man writer has a copy issue, so we explicitly override it here
557 def copy(self):
558 newnode = extensionprint(output_type=self.options['type'], **self.attributes)
559 newnode.source = self.source
560 newnode.line = self.line
561 return newnode
562
563
564 def generate_output(self, app, fromdocname):
565 env = app.builder.env
566
567 extensions = []
568
569 for extension_info in sorted(env.all_extensions,
570 key=lambda fi: fi['name'].lower()):
571
572 # Resolve all references as if they were originated from this node.
573 # This fixes the relative uri.
574 for cell in extension_info['cells']:
575 for ref in cell.traverse(addnodes.pending_xref):
576 ref['refdoc'] = fromdocname
577 env.resolve_references(cell, extension_info['docname'], app.builder)
578
579 extensions.append(extension_info)
580
581 handler = extension_handlers[self.options['type']]
582 self.replace_self(handler(extensions))
583
584 # A directive to create extensionprint nodes
585 class ExtensionPrintDirective(Directive):
586
587 option_spec = {
588 'type': directives.unchanged_required
589 }
590
591 def run(self):
592 # Create a extensionprint node
593 node = extensionprint(output_type=self.options['type'])
594 return [node]
595
596
597 ### Additional processing
598
599 # Convert every flagprint node into its output format
600 def process_print_nodes(app, doctree, fromdocname):
601
602 for node in doctree.traverse(flagprint):
603 node.generate_output(app, fromdocname)
604
605 for node in doctree.traverse(extensionprint):
606 node.generate_output(app, fromdocname)
607
608
609 # To avoid creating duplicates in the serialized environment, clear all
610 # flags originating from a file before re-reading it.
611 def purge_flags(app, env, docname):
612
613 if hasattr(env, 'all_flags'):
614 env.all_flags = [flag for flag in env.all_flags
615 if flag['docname'] != docname]
616 if hasattr(env, 'all_extensions'):
617 env.all_extensions = [ext for ext in env.all_extensions
618 if ext['docname'] != docname]
619
620 ### Initialization
621
622 def setup(app):
623
624 # Add ghc-flag directive, and override the class with our own
625 app.add_object_type('ghc-flag', 'ghc-flag')
626 app.add_directive_to_domain('std', 'ghc-flag', Flag)
627
628 # Add extension directive, and override the class with our own
629 app.add_object_type('extension', 'extension')
630 app.add_directive_to_domain('std', 'extension', LanguageExtension)
631 # NB: language-extension would be misinterpreted by sphinx, and produce
632 # lang="extensions" XML attributes
633
634 # Add new node and directive
635 app.add_node(flagprint)
636 app.add_directive('flag-print', FlagPrintDirective)
637
638 # Add new node and directive
639 app.add_node(extensionprint)
640 app.add_directive('extension-print', ExtensionPrintDirective)
641
642 # Add our generator and cleanup functions as callbacks
643 app.connect('doctree-resolved', process_print_nodes)
644 app.connect('env-purge-doc', purge_flags)
645
646 return {'version': '1.0'}