#!/usr/bin/env python import inspect from pathlib import Path from IPython.terminal.ipapp import TerminalIPythonApp from ipykernel.kernelapp import IPKernelApp from traitlets import Undefined from collections import defaultdict here = (Path(__file__)).parent options = here / "source" / "config" / "options" generated = options / "config-generated.txt" import textwrap indent = lambda text,n: textwrap.indent(text,n*' ') def interesting_default_value(dv): if (dv is None) or (dv is Undefined): return False if isinstance(dv, (str, list, tuple, dict, set)): return bool(dv) return True def format_aliases(aliases): fmted = [] for a in aliases: dashes = '-' if len(a) == 1 else '--' fmted.append('``%s%s``' % (dashes, a)) return ', '.join(fmted) def class_config_rst_doc(cls, trait_aliases): """Generate rST documentation for this class' config options. Excludes traits defined on parent classes. """ lines = [] classname = cls.__name__ for k, trait in sorted(cls.class_traits(config=True).items()): ttype = trait.__class__.__name__ fullname = classname + '.' + trait.name lines += ['.. configtrait:: ' + fullname, '' ] help = trait.help.rstrip() or 'No description' lines.append(indent(inspect.cleandoc(help), 4) + '\n') # Choices or type if 'Enum' in ttype: # include Enum choices lines.append(indent( ':options: ' + ', '.join('``%r``' % x for x in trait.values), 4)) else: lines.append(indent(':trait type: ' + ttype, 4)) # Default value # Ignore boring default values like None, [] or '' if interesting_default_value(trait.default_value): try: dvr = trait.default_value_repr() except Exception: dvr = None # ignore defaults we can't construct if dvr is not None: if len(dvr) > 64: dvr = dvr[:61] + '...' # Double up backslashes, so they get to the rendered docs dvr = dvr.replace('\\n', '\\\\n') lines.append(indent(':default: ``%s``' % dvr, 4)) # Command line aliases if trait_aliases[fullname]: fmt_aliases = format_aliases(trait_aliases[fullname]) lines.append(indent(':CLI option: ' + fmt_aliases, 4)) # Blank line lines.append('') return '\n'.join(lines) def reverse_aliases(app): """Produce a mapping of trait names to lists of command line aliases. """ res = defaultdict(list) for alias, trait in app.aliases.items(): res[trait].append(alias) # Flags also often act as aliases for a boolean trait. # Treat flags which set one trait to True as aliases. for flag, (cfg, _) in app.flags.items(): if len(cfg) == 1: classname = list(cfg)[0] cls_cfg = cfg[classname] if len(cls_cfg) == 1: traitname = list(cls_cfg)[0] if cls_cfg[traitname] is True: res[classname+'.'+traitname].append(flag) return res def write_doc(name, title, app, preamble=None): trait_aliases = reverse_aliases(app) filename = options / (name + ".rst") with open(filename, "w") as f: f.write(title + "\n") f.write(("=" * len(title)) + "\n") f.write("\n") if preamble is not None: f.write(preamble + '\n\n') #f.write(app.document_config_options()) for c in app._classes_inc_parents(): f.write(class_config_rst_doc(c, trait_aliases)) f.write('\n') if __name__ == '__main__': # Touch this file for the make target Path(generated).write_text("") write_doc('terminal', 'Terminal IPython options', TerminalIPythonApp()) write_doc('kernel', 'IPython kernel options', IPKernelApp(), preamble=("These options can be used in :file:`ipython_kernel_config.py`. " "The kernel also respects any options in `ipython_config.py`"), )