# 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 # # It also provides a directive to list language extensions: # # .. extension:: # :shortdesc: A short description (REQUIRED) # :noindex: Do not list the extension anywhere (good for duplicates) # # This has the side-effect of an appropriate ghc-flag directive for the `-X` # flag. # # Extensions can be referenced with # # :extension:`extension` # # Language exensions can be listed: # # .. extension-print:: # :type: table/list/summary (REQUIRED) # # 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 from sphinx.errors import SphinxError ### 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 # Functionality to add a flag to the tables, used by both Flag and LanguageExtension class GenericFlag(GenericObject): def register_flag(self, names, category, name_string, shortdesc, flag_type, reverse_string): # 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=[shortdesc], source=self.env.docname, parent=[]) type_vl = ViewList(initlist=[flag_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': names, 'docname': self.env.docname, 'category': category, 'cells': [name_node, shortdesc_node, type_node, reverse_node], }) # 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(GenericFlag): # 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 # Validity checking if 'shortdesc' not in self.options: raise SphinxError('ghc-flag (%s) directive missing :shortdesc: key' % self.names) if 'type' not in self.options: raise SphinxError('ghc-flag (%s) directive missing :type: key' % self.names) # 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'] + '`' self.register_flag( self.names, self.category, name_string, self.options['shortdesc'], self.options['type'], reverse_string) # 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 LanguageExtension(GenericFlag): # The options that can be passed to our directive and their validators option_spec = { 'shortdesc': directives.unchanged_required, 'noindex': directives.flag } # The index directive generated unless :noindex: is specified indextemplate = 'pair: %s; Language Extension' # Invert the flag @staticmethod def _noname(name): if name[:2] == "No": return name[2:] else: return "No%s" % name @staticmethod def _onname(name): if name[:2] == "No": return name[2:] else: return name # Add additional targets def add_target_and_index(self, name, sig, signode): GenericFlag.add_target_and_index(self, name, sig, signode) # Mostly for consistency in URL anchors signode['ids'].append('ghc-flag--X%s' % name) # So that anchors stay valid even if an extension turns to on-by-default signode['ids'].append('extension-%s' % self._noname(name)) targetname = '%s-%s' % (self.objtype, name) # Add index entries for the -XFoo flag self.indexnode['entries'].append(('pair', '-X%s; GHC option' % name, targetname, '', None)) # Make this also addressable using :ghc-flag:-XFoo self.env.domaindata['std']['objects']['ghc-flag', '-X%s' % name] = \ self.env.docname, 'extension-%s' % name # Make this also addressable using :extension:-XNoFoo self.env.domaindata['std']['objects']['extension', self._noname(name)] = \ self.env.docname, 'extension-%s' % name # 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 extension in the table if 'noindex' in self.options: return # Validity checking if len(self.names) < 1: raise SphinxError('extension needs at least one name') primary_name = self.names[0] if 'shortdesc' not in self.options: raise SphinxError('extension (%s) directive missing :shortdesc: key' % primary_name) # Register the corresponding flags for name in self.names: self.register_flag( ['-X%s' % name], 'language', ':extension:`-X%s <%s>`' % (name, primary_name), self.options['shortdesc'], 'dynamic', ':extension:`-X%s <%s>`' % (self._noname(name), primary_name)) # Register the extension for the table, under the "on name" (no No...) onname = self._onname(primary_name) name_node = nodes.paragraph() shortdesc_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=[':extension:`%s`' % onname], source=self.env.docname, parent=[]) shortdesc_vl = ViewList(initlist=[self.options['shortdesc']], 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) # The parsing adds extra layers that we don't need name_node = name_node[0] shortdesc_node = shortdesc_node[0] if not hasattr(self.env, 'all_extensions'): self.env.all_extensions = [] self.env.all_extensions.append({ 'name': onname, 'docname': self.env.docname, 'cells': [name_node, shortdesc_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 flag_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 flag_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 flag_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 = flag_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] ### Extension Printing # Generate a table of flags def generate_extension_table(extensions): # Create column headers for table header = [] for h in ["Extension", "Description"]: inline = nodes.inline(text=h) header.append(inline) extension_list = [header] for extension_info in extensions: extension_list.append(extension_info['cells']) # The column width hints only apply to html, # latex widths are set in file (see flags.rst) table = build_table_from_list(extension_list, [28, 72]) # Flag tables have lots of content, so we need to set 'longtable' # to allow for pagebreaks. (latex specific) table['classes'].append('longtable') return table # Output dispatch table extension_handlers = { 'table': generate_extension_table, } # Generic node for printing extension output class extensionprint(nodes.General, nodes.Element): def __init__(self, output_type='', **kwargs): nodes.Element.__init__(self, rawsource='', **kwargs) # Verify options if output_type not in extension_handlers: error = "extensionprint: Unknown output type: " + output_type raise ValueError(error) # Store the options self.options = { 'type': output_type, } # The man writer has a copy issue, so we explicitly override it here def copy(self): newnode = extensionprint(output_type=self.options['type'], **self.attributes) newnode.source = self.source newnode.line = self.line return newnode def generate_output(self, app, fromdocname): env = app.builder.env extensions = [] for extension_info in sorted(env.all_extensions, key=lambda fi: fi['name'].lower()): # Resolve all references as if they were originated from this node. # This fixes the relative uri. for cell in extension_info['cells']: for ref in cell.traverse(addnodes.pending_xref): ref['refdoc'] = fromdocname env.resolve_references(cell, extension_info['docname'], app.builder) extensions.append(extension_info) handler = extension_handlers[self.options['type']] self.replace_self(handler(extensions)) # A directive to create extensionprint nodes class ExtensionPrintDirective(Directive): option_spec = { 'type': directives.unchanged_required } def run(self): # Create a extensionprint node node = extensionprint(output_type=self.options['type']) 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) for node in doctree.traverse(extensionprint): 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 hasattr(env, 'all_flags'): env.all_flags = [flag for flag in env.all_flags if flag['docname'] != docname] if hasattr(env, 'all_extensions'): env.all_extensions = [ext for ext in env.all_extensions if ext['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 extension directive, and override the class with our own app.add_object_type('extension', 'extension') app.add_directive_to_domain('std', 'extension', LanguageExtension) # NB: language-extension would be misinterpreted by sphinx, and produce # lang="extensions" XML attributes # Add new node and directive app.add_node(flagprint) app.add_directive('flag-print', FlagPrintDirective) # Add new node and directive app.add_node(extensionprint) app.add_directive('extension-print', ExtensionPrintDirective) # 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'}