summaryrefslogtreecommitdiff
path: root/docs/users_guide/flags.py
diff options
context:
space:
mode:
Diffstat (limited to 'docs/users_guide/flags.py')
-rw-r--r--docs/users_guide/flags.py400
1 files changed, 400 insertions, 0 deletions
diff --git a/docs/users_guide/flags.py b/docs/users_guide/flags.py
new file mode 100644
index 0000000000..06223b599c
--- /dev/null
+++ b/docs/users_guide/flags.py
@@ -0,0 +1,400 @@
+# GHC User's Guide flag extension
+#
+# This file defines a Sphinx extension to document GHC flags.
+# It introduces a directive:
+#
+# .. ghc-flag::
+# :shortdesc: A short description (REQUIRED)
+# :type: dynamic, mode, dynamix/ ``:set`` (REQUIRED)
+# :reverse: The reverse of the flag
+# :category: The category to list the flag under (default: 'misc')
+# :noindex: Do not list the flag anywhere (good for duplicates)
+#
+# That can be referenced using:
+#
+# :ghc-flag:`flag`
+#
+# As well as a directive to generate a display of flags:
+#
+# .. flag-print::
+# :type: table/list/summary (REQUIRED)
+# :category: Limit the output to a single category
+#
+# The two main functions in this extension are Flag.after_content() which adds
+# flag metadata into the environment, and flagprint.generate_output(), which
+# reads the metadata back out and formats it as desired.
+
+from docutils import nodes
+from docutils.parsers.rst import Directive, directives
+from sphinx import addnodes
+from sphinx.domains.std import GenericObject
+
+### Settings
+
+# Categories to titles as well as a canonical list of categories
+categories = {
+ '': 'All flags',
+ 'codegen': 'Code generation',
+ 'coverage': 'Program coverage',
+ 'cpp': 'C pre-processor',
+ 'debugging': 'Debugging the compiler',
+ 'interactive': 'Interactive mode',
+ 'interface-files': 'Interface files',
+ 'keep-intermediates': 'Keeping intermediate files',
+ 'language': 'Language options',
+ 'linking': 'Linking options',
+ 'misc': 'Miscellaneous options',
+ 'modes': 'Modes of operation',
+ 'optimization': 'Individual optimizations',
+ 'optimization-levels': 'Optimization levels',
+ 'packages': 'Package options',
+ 'phases': 'Phases of compilation',
+ 'phase-programs': 'Overriding external programs',
+ 'phase-options': 'Phase-specific options',
+ 'platform-options': 'Platform-specific options',
+ 'plugins': 'Compiler plugins',
+ 'profiling': 'Profiling',
+ 'recompilation': 'Recompilation checking',
+ 'redirect-output': 'Redirecting output',
+ 'search-path': 'Finding imports',
+ 'temp-files': 'Temporary files',
+ 'verbosity': 'Verbosity options',
+ 'warnings': 'Warnings',
+}
+
+# Map file names to default flag categories
+file_defaults = {
+ 'debugging': 'debugging',
+ 'ghci': 'interactive',
+ 'glasgow_exts': 'language',
+ 'packages': 'packages',
+ 'profiling': 'profiling',
+ 'safe_haskell': 'language',
+ 'separate_compilation': 'redirect-output',
+ 'using-warnings': 'warnings',
+ 'using-optimisation': 'optimization'
+}
+
+
+### Flag declaration
+
+# This class inherits from Sphinx's internal GenericObject, which drives
+# the add_object_type() utility function. We want to keep that tooling,
+# but need to override some of the functionality.
+class Flag(GenericObject):
+
+ # The options that can be passed to our directive and their validators
+ option_spec = {
+ 'shortdesc': directives.unchanged_required,
+ 'type': directives.unchanged_required,
+ 'reverse': directives.unchanged,
+ 'category': directives.unchanged,
+ 'noindex': directives.flag
+ }
+
+ # The index directive generated unless :noindex: is specified
+ indextemplate = 'pair: %s; GHC option'
+
+
+ # Generate docutils node from directive arguments
+ @staticmethod
+ def _parse_flag(env, sig, signode):
+
+ # Break flag into name and args
+ import re
+ parts = re.split(r'( |=|\[)', sig, 1)
+ flag = parts[0]
+ # Bold printed name
+ signode += addnodes.desc_name(flag, flag)
+ if len(parts) > 1:
+ args = ''.join(parts[1:])
+ # Smaller arguments
+ signode += addnodes.desc_addname(args, args)
+
+ # Reference name left unchanged
+ return sig
+
+ # Used in the GenericObject class
+ parse_node = _parse_flag
+
+ # Override the (empty) function that is called at the end of run()
+ # to append metadata about this flag into the environment
+ def after_content(self):
+
+ # If noindex, then do not include this flag in the table
+ if 'noindex' in self.options:
+ return
+
+ # Set the flag category (default: misc)
+ self.category = 'misc'
+ if not 'category' in self.options or self.options['category'] == '':
+ if self.env.docname in file_defaults:
+ self.category = file_defaults[self.env.docname]
+ else:
+ self.category = self.options['category']
+
+ # Manually create references
+ name_string = ", ".join([':ghc-flag:`'+n+'`' for n in self.names])
+ reverse_string = ''
+ if 'reverse' in self.options and self.options['reverse'] != '':
+ reverse_string = ':ghc-flag:`' + self.options['reverse'] + '`'
+
+ # Create nodes for each cell of the table
+ name_node = nodes.paragraph()
+ shortdesc_node = nodes.paragraph()
+ type_node = nodes.paragraph()
+ reverse_node = nodes.paragraph()
+
+
+ # Nodes expect an internal ViewList type for the content,
+ # we are just spoofing it here
+ from docutils.statemachine import ViewList
+ name_vl = ViewList(initlist=[name_string],
+ source=self.env.docname, parent=[])
+ shortdesc_vl = ViewList(initlist=[self.options['shortdesc']],
+ source=self.env.docname, parent=[])
+ type_vl = ViewList(initlist=[self.options['type']],
+ source=self.env.docname, parent=[])
+ reverse_vl = ViewList(initlist=[reverse_string],
+ source=self.env.docname, parent=[])
+
+
+ # Parse the content into the nodes
+ self.state.nested_parse(name_vl, 0, name_node)
+ self.state.nested_parse(shortdesc_vl, 0, shortdesc_node)
+ self.state.nested_parse(type_vl, 0, type_node)
+ self.state.nested_parse(reverse_vl, 0, reverse_node)
+
+
+ # The parsing adds extra layers that we don't need
+ name_node = name_node[0]
+ shortdesc_node = shortdesc_node[0]
+
+ # Append this flag to the environment, initializing if necessary
+ if not hasattr(self.env, 'all_flags'):
+ self.env.all_flags = []
+ self.env.all_flags.append({
+ 'names': self.names,
+ 'docname': self.env.docname,
+ 'category': self.category,
+ 'cells': [name_node, shortdesc_node, type_node, reverse_node],
+ })
+
+
+### Flag Printing
+
+# Taken from Docutils source inside the ListTable class. We must bypass
+# using the class itself, but this function comes in handy.
+def build_table_from_list(table_data, col_widths):
+ table = nodes.table()
+ tgroup = nodes.tgroup(cols=len(col_widths))
+ table += tgroup
+ for col_width in col_widths:
+ colspec = nodes.colspec(colwidth=col_width)
+ tgroup += colspec
+ rows = []
+ for row in table_data:
+ row_node = nodes.row()
+ for cell in row:
+ entry = nodes.entry()
+ entry += cell
+ row_node += entry
+ rows.append(row_node)
+ thead = nodes.thead()
+ thead.extend(rows[:1])
+ tgroup += thead
+ tbody = nodes.tbody()
+ tbody.extend(rows[1:])
+ tgroup += tbody
+ return table
+
+
+# Generate a table of flags
+def generate_flag_table(flags, category):
+
+ # Create column headers for table
+ header = []
+ for h in ["Flag", "Description", "Type", "Reverse"]:
+ inline = nodes.inline(text=h)
+ header.append(inline)
+
+ flags_list = [header]
+
+ for flag_info in flags:
+
+ flags_list.append(flag_info['cells'])
+
+ # The column width hints only apply to html,
+ # latex widths are set in file (see flags.rst)
+ table = build_table_from_list(flags_list, [28, 34, 10, 28])
+
+ # Flag tables have lots of content, so we need to set 'longtable'
+ # to allow for pagebreaks. (latex specific)
+ table['classes'].append('longtable')
+
+ return table
+
+
+# Generate a list of flags and their short descriptions
+def generate_flag_list(flags, category):
+
+ list_node = nodes.definition_list()
+
+ for flag_info in flags:
+
+ dl_item_node = nodes.definition_list_item()
+ term_node = nodes.term()
+ # The man writer is picky, so we have to remove the outer
+ # paragraph node to get just the flag name
+ term_node += flag_info['cells'][0][0]
+ dl_item_node += term_node
+ def_node = nodes.definition()
+ def_node += flag_info['cells'][1]
+ dl_item_node += def_node
+
+ list_node += dl_item_node
+
+ return list_node
+
+
+# Generate a block of flag names under a category
+def generate_flag_summary(flags, category):
+
+ summary_node = nodes.definition_list_item()
+ term_node = nodes.term(text=categories[category])
+ summary_node += term_node
+ block = nodes.definition()
+ summary_node += block
+
+ # Fill block with flags
+ for flag_info in flags:
+
+ for name in flag_info['names']:
+ block += nodes.literal(text=name)
+ block += nodes.inline(text=' ')
+
+ block += nodes.inline(text='\n')
+
+ return summary_node
+
+# Output dispatch table
+handlers = {
+ 'table': generate_flag_table,
+ 'list': generate_flag_list,
+ 'summary': generate_flag_summary
+}
+
+
+# Generic node for printing flag output
+class flagprint(nodes.General, nodes.Element):
+
+ def __init__(self, output_type='', category='', **kwargs):
+
+ nodes.Element.__init__(self, rawsource='', **kwargs)
+
+ # Verify options
+ if category not in categories:
+ error = "flagprint: Unknown category: " + category
+ raise ValueError(error)
+ if output_type not in handlers:
+ error = "flagprint: Unknown output type: " + output_type
+ raise ValueError(error)
+
+ # Store the options
+ self.options = {
+ 'type': output_type,
+ 'category': category
+ }
+
+
+ # The man writer has a copy issue, so we explicitly override it here
+ def copy(self):
+ newnode = flagprint(output_type=self.options['type'],
+ category=self.options['category'], **self.attributes)
+ newnode.source = self.source
+ newnode.line = self.line
+ return newnode
+
+
+ def generate_output(self, app, fromdocname):
+ env = app.builder.env
+
+ # Filter flags before passing to handlers
+ flags = []
+
+ for flag_info in sorted(env.all_flags,
+ key=lambda fi: fi['names'][0].lower()):
+
+ if not (self.options['category'] == '' or
+ self.options['category'] == flag_info['category']):
+ continue
+
+ # Resolve all references as if they were originated from this node.
+ # This fixes the relative uri.
+ for cell in flag_info['cells']:
+ for ref in cell.traverse(addnodes.pending_xref):
+ ref['refdoc'] = fromdocname
+ env.resolve_references(cell, flag_info['docname'], app.builder)
+
+ flags.append(flag_info)
+
+ handler = handlers[self.options['type']]
+ self.replace_self(handler(flags, self.options['category']))
+
+
+# A directive to create flagprint nodes
+class FlagPrintDirective(Directive):
+
+ option_spec = {
+ 'type': directives.unchanged_required,
+ 'category': directives.unchanged
+ }
+
+ def run(self):
+
+ # Process options
+ category = ''
+ if 'category' in self.options:
+ category = self.options['category']
+
+ # Create a flagprint node
+ node = flagprint(output_type=self.options['type'], category=category)
+ return [node]
+
+
+### Additional processing
+
+# Convert every flagprint node into its output format
+def process_print_nodes(app, doctree, fromdocname):
+
+ for node in doctree.traverse(flagprint):
+ node.generate_output(app, fromdocname)
+
+
+# To avoid creating duplicates in the serialized environment, clear all
+# flags originating from a file before re-reading it.
+def purge_flags(app, env, docname):
+
+ if not hasattr(env, 'all_flags'):
+ return
+
+ env.all_flags = [flag for flag in env.all_flags
+ if flag['docname'] != docname]
+
+### Initialization
+
+def setup(app):
+
+ # Add ghc-flag directive, and override the class with our own
+ app.add_object_type('ghc-flag', 'ghc-flag')
+ app.add_directive_to_domain('std', 'ghc-flag', Flag)
+
+ # Add new node and directive
+ app.add_node(flagprint)
+ app.add_directive('flag-print', FlagPrintDirective)
+
+ # Add our generator and cleanup functions as callbacks
+ app.connect('doctree-resolved', process_print_nodes)
+ app.connect('env-purge-doc', purge_flags)
+
+ return {'version': '1.0'}