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