diff --git a/IPython/config/application.py b/IPython/config/application.py index 04dbe86..05022e0 100644 --- a/IPython/config/application.py +++ b/IPython/config/application.py @@ -33,7 +33,11 @@ from IPython.utils.traitlets import ( Unicode, List, Int, Enum, Dict, Instance ) from IPython.utils.importstring import import_item -from IPython.utils.text import indent +from IPython.utils.text import indent, wrap_paragraphs, dedent + +#----------------------------------------------------------------------------- +# function for re-wrapping a helpstring +#----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Descriptions for the various sections @@ -44,6 +48,8 @@ Flags are command-line arguments passed as '--'. These take no parameters, unlike regular key-value arguments. They are typically used for setting boolean flags, or enabling modes that involve setting multiple options together. + +Flags *always* begin with '--', never just one '-'. """.strip() # trim newlines of front and back alias_description = """ @@ -104,10 +110,18 @@ class Application(SingletonConfigurable): # this must be a dict of two-tuples, the first element being the Config/dict # and the second being the help string for the flag flags = Dict() + def _flags_changed(self, name, old, new): + """ensure flags dict is valid""" + for key,value in new.iteritems(): + assert len(value) == 2, "Bad flag: %r:%s"%(key,value) + assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value) + assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value) + # subcommands for launching other applications # if this is not empty, this will be a parent Application - # this must be a dict of two-tuples, the first element being the application class/import string + # this must be a dict of two-tuples, + # the first element being the application class/import string # and the second being the help string for the subcommand subcommands = Dict() # parse_command_line will initialize a subapp, if requested @@ -123,11 +137,6 @@ class Application(SingletonConfigurable): # options. self.classes.insert(0, self.__class__) - # ensure self.flags dict is valid - for key,value in self.flags.iteritems(): - assert len(value) == 2, "Bad flag: %r:%s"%(key,value) - assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value) - assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value) self.init_logging() def _config_changed(self, name, old, new): @@ -170,14 +179,16 @@ class Application(SingletonConfigurable): self.log.setLevel(new) def print_alias_help(self): - """print the alias part of the help""" + """Print the alias part of the help.""" if not self.aliases: return lines = ['Aliases'] lines.append('-'*len(lines[0])) - lines.append(self.alias_description) lines.append('') + for p in wrap_paragraphs(self.alias_description): + lines.append(p) + lines.append('') classdict = {} for cls in self.classes: @@ -197,23 +208,25 @@ class Application(SingletonConfigurable): print '\n'.join(lines) def print_flag_help(self): - """print the flag part of the help""" + """Print the flag part of the help.""" if not self.flags: return lines = ['Flags'] lines.append('-'*len(lines[0])) - lines.append(self.flag_description) lines.append('') + for p in wrap_paragraphs(self.flag_description): + lines.append(p) + lines.append('') for m, (cfg,help) in self.flags.iteritems(): lines.append('--'+m) - lines.append(indent(help.strip(), flatten=True)) + lines.append(indent(dedent(help.strip()))) lines.append('') print '\n'.join(lines) def print_subcommands(self): - """print the subcommand part of the help""" + """Print the subcommand part of the help.""" if not self.subcommands: return @@ -222,14 +235,14 @@ class Application(SingletonConfigurable): for subc, (cls,help) in self.subcommands.iteritems(): lines.append("%s : %s"%(subc, cls)) if help: - lines.append(indent(help.strip(), flatten=True)) + lines.append(indent(dedent(help.strip()))) lines.append('') print '\n'.join(lines) def print_help(self, classes=False): """Print the help for each Configurable class in self.classes. - If classes=False (the default), only flags and aliases are printed + If classes=False (the default), only flags and aliases are printed. """ self.print_subcommands() self.print_flag_help() @@ -239,8 +252,10 @@ class Application(SingletonConfigurable): if self.classes: print "Class parameters" print "----------------" - print self.keyvalue_description print + for p in wrap_paragraphs(self.keyvalue_description): + print p + print for cls in self.classes: cls.class_print_help() @@ -251,8 +266,9 @@ class Application(SingletonConfigurable): def print_description(self): """Print the application description.""" - print self.description - print + for p in wrap_paragraphs(self.description): + print p + print def print_version(self): """Print the version string.""" @@ -269,7 +285,7 @@ class Application(SingletonConfigurable): self.config = newconfig def initialize_subcommand(self, subc, argv=None): - """Initialize a subcommand with argv""" + """Initialize a subcommand with argv.""" subapp,help = self.subcommands.get(subc) if isinstance(subapp, basestring): @@ -330,7 +346,7 @@ class Application(SingletonConfigurable): #----------------------------------------------------------------------------- def boolean_flag(name, configurable, set_help='', unset_help=''): - """helper for building basic --trait, --no-trait flags + """Helper for building basic --trait, --no-trait flags. Parameters ---------- @@ -360,3 +376,4 @@ def boolean_flag(name, configurable, set_help='', unset_help=''): setter = {cls : {trait : True}} unsetter = {cls : {trait : False}} return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)} + diff --git a/IPython/config/configurable.py b/IPython/config/configurable.py index 433195f..94a7595 100755 --- a/IPython/config/configurable.py +++ b/IPython/config/configurable.py @@ -21,12 +21,12 @@ Authors: # Imports #----------------------------------------------------------------------------- -from copy import deepcopy import datetime +from copy import deepcopy from loader import Config from IPython.utils.traitlets import HasTraits, Instance -from IPython.utils.text import indent +from IPython.utils.text import indent, wrap_paragraphs #----------------------------------------------------------------------------- @@ -51,7 +51,7 @@ class Configurable(HasTraits): created = None def __init__(self, **kwargs): - """Create a conigurable given a config config. + """Create a configurable given a config config. Parameters ---------- @@ -153,27 +153,31 @@ class Configurable(HasTraits): @classmethod def class_get_trait_help(cls, trait): - """Get the help string for a single """ + """Get the help string for a single trait.""" lines = [] header = "%s.%s : %s" % (cls.__name__, trait.name, trait.__class__.__name__) + lines.append(header) try: dvr = repr(trait.get_default_value()) except Exception: dvr = None # ignore defaults we can't construct if dvr is not None: - header += ' [default: %s]'%dvr - lines.append(header) + if len(dvr) > 64: + dvr = dvr[:61]+'...' + lines.append(indent('Default: %s'%dvr, 4)) + if 'Enum' in trait.__class__.__name__: + # include Enum choices + lines.append(indent('Choices: %r'%(trait.values,))) help = trait.get_metadata('help') if help is not None: - lines.append(indent(help.strip(), flatten=True)) - if 'Enum' in trait.__class__.__name__: - # include Enum choices - lines.append(indent('Choices: %r'%(trait.values,), flatten=True)) + help = '\n'.join(wrap_paragraphs(help, 76)) + lines.append(indent(help, 4)) return '\n'.join(lines) @classmethod def class_print_help(cls): + """Get the help string for a single trait and print it.""" print cls.class_get_help() diff --git a/IPython/utils/text.py b/IPython/utils/text.py index 9370da8..d0e3387 100644 --- a/IPython/utils/text.py +++ b/IPython/utils/text.py @@ -19,6 +19,7 @@ import __main__ import os import re import shutil +import textwrap from string import Formatter from IPython.external.path import path @@ -520,6 +521,58 @@ def format_screen(strng): strng = par_re.sub('',strng) return strng +def dedent(text): + """Equivalent of textwrap.dedent that ignores unindented first line. + + This means it will still dedent strings like: + '''foo + is a bar + ''' + + For use in wrap_paragraphs. + """ + + if text.startswith('\n'): + # text starts with blank line, don't ignore the first line + return textwrap.dedent(text) + + # split first line + splits = text.split('\n',1) + if len(splits) == 1: + # only one line + return textwrap.dedent(text) + + first, rest = splits + # dedent everything but the first line + rest = textwrap.dedent(rest) + return '\n'.join([first, rest]) + +def wrap_paragraphs(text, ncols=80): + """Wrap multiple paragraphs to fit a specified width. + + This is equivalent to textwrap.wrap, but with support for multiple + paragraphs, as separated by empty lines. + + Returns + ------- + + list of complete paragraphs, wrapped to fill `ncols` columns. + """ + paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE) + text = dedent(text).strip() + paragraphs = paragraph_re.split(text)[::2] # every other entry is space + out_ps = [] + indent_re = re.compile(r'\n\s+', re.MULTILINE) + for p in paragraphs: + # presume indentation that survives dedent is meaningful formatting, + # so don't fill unless text is flush. + if indent_re.search(p) is None: + # wrap paragraph + p = textwrap.fill(p, ncols) + out_ps.append(p) + return out_ps + + class EvalFormatter(Formatter): """A String Formatter that allows evaluation of simple expressions.