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