diff --git a/IPython/config/configurable.py b/IPython/config/configurable.py index ed8fc2a..12abd1c 100755 --- a/IPython/config/configurable.py +++ b/IPython/config/configurable.py @@ -27,6 +27,7 @@ from weakref import WeakValueDictionary from IPython.utils.importstring import import_item from loader import Config from IPython.utils.traitlets import HasTraits, Instance +from IPython.utils.text import indent #----------------------------------------------------------------------------- @@ -137,3 +138,33 @@ class Configurable(HasTraits): # shared by all instances, effectively making it a class attribute. setattr(self, k, deepcopy(config_value)) + @classmethod + def class_get_shortnames(cls): + """Return the shortname to fullname dict for config=True traits.""" + cls_traits = cls.class_traits(config=True) + shortnames = {} + for k, v in cls_traits.items(): + shortname = v.get_metadata('shortname') + if shortname is not None: + longname = cls.__name__ + '.' + k + shortnames[shortname] = longname + return shortnames + + @classmethod + def class_get_help(cls): + cls_traits = cls.class_traits(config=True) + final_help = [] + final_help.append('%s options' % cls.__name__) + final_help.append(len(final_help[0])*'-') + for k, v in cls_traits.items(): + help = v.get_metadata('help') + final_help.append(k + " : " + v.__class__.__name__) + if help is not None: + final_help.append(indent(help)) + return '\n'.join(final_help) + + @classmethod + def class_print_help(cls): + print cls.class_get_help() + + \ No newline at end of file diff --git a/IPython/config/loader.py b/IPython/config/loader.py index 6a0b3e6..720aef5 100644 --- a/IPython/config/loader.py +++ b/IPython/config/loader.py @@ -315,7 +315,7 @@ class KeyValueConfigLoader(CommandLineConfigLoader): ipython Global.profile="foo" InteractiveShell.autocall=False """ - def __init__(self, argv=None): + def __init__(self, argv=None, classes=None): """Create a key value pair config loader. Parameters @@ -324,6 +324,9 @@ class KeyValueConfigLoader(CommandLineConfigLoader): A list that has the form of sys.argv[1:] which has unicode elements of the form u"key=value". If this is None (default), then sys.argv[1:] will be used. + class : (list, tuple) of Configurables + A sequence of Configurable classes that will be used to map + shortnames to longnames. Returns ------- @@ -338,11 +341,14 @@ class KeyValueConfigLoader(CommandLineConfigLoader): >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"]) {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'} """ - if argv == None: + if argv is None: argv = sys.argv[1:] + if classes is None: + classes = () self.argv = argv + self.classes = classes - def load_config(self, argv=None): + def load_config(self, argv=None, classes=None): """Parse the configuration and generate the Config object. Parameters @@ -350,16 +356,47 @@ class KeyValueConfigLoader(CommandLineConfigLoader): argv : list, optional A list that has the form of sys.argv[1:] which has unicode elements of the form u"key=value". If this is None (default), - then self.argv will be used. + then self.argv will be used. + class : (list, tuple) of Configurables + A sequence of Configurable classes that will be used to map + shortnames to longnames. """ self.clear() if argv is None: argv = self.argv + if classes is None: + classes = self.classes + + # print argv + + shortnames = {} + for cls in classes: + sn = cls.class_get_shortnames() + # Check for duplicate shortnames and raise if found. + for k, v in sn.items(): + if k in shortnames: + raise KeyError( + "duplicate shortname: %s and %s both use the shortname: %s" %\ + (v, shortnames[k], k) + ) + shortnames.update(sn) + + # print shortnames + for item in argv: pair = tuple(item.split("=")) if len(pair) == 2: - exec_str = 'self.config.' + pair[0] + '=' + pair[1] - exec exec_str in locals(), globals() + lhs = pair[0] + rhs = pair[1] + # Substitute longnames for shortnames. + if lhs in shortnames: + lhs = shortnames[lhs] + exec_str = 'self.config.' + lhs + '=' + rhs + try: + exec exec_str in locals(), globals() + except (NameError, SyntaxError): + exec_str = 'self.config.' + lhs + '="' + rhs + '"' + exec exec_str in locals(), globals() return self.config diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 4f4e5f6..3ba7de4 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -214,7 +214,7 @@ class InteractiveShell(Configurable, Magic): # We can't do this yet because even runlines uses the autoindent. autoindent = CBool(True, config=True, help= """ - Should IPython indent code entered interactively. + Autoindent IPython code entered interactively. """ ) automagic = CBool(True, config=True, help= @@ -222,12 +222,37 @@ class InteractiveShell(Configurable, Magic): Enable magic commands to be called without the leading %. """ ) - cache_size = Int(1000, config=True) - color_info = CBool(True, config=True) + cache_size = Int(1000, config=True, help= + """ + Set the size of the output cache. The default is 1000, you can + change it permanently in your config file. Setting it to 0 completely + disables the caching system, and the minimum value accepted is 20 (if + you provide a value less than 20, it is reset to 0 and a warning is + issued). This limit is defined because otherwise you'll spend more + time re-flushing a too small cache than working + """ + ) + color_info = CBool(True, config=True, help= + """ + Use colors for displaying information about objects. Because this + information is passed through a pager (like 'less'), and some pagers + get confused with color codes, this capability can be turned off. + """ + ) colors = CaselessStrEnum(('NoColor','LightBG','Linux'), default_value=get_default_colors(), config=True) debug = CBool(False, config=True) - deep_reload = CBool(False, config=True) + deep_reload = CBool(False, config=True, help= + """ + Enable deep (recursive) reloading by default. IPython can use the + deep_reload module which reloads changes in modules recursively (it + replaces the reload() function, so you don't need to change anything to + use it). deep_reload() forces a full reload of modules whose code may + have changed, which the default reload() function does not. When + deep_reload is off, IPython will use the normal reload(), but + deep_reload will still be available as dreload(). + """ + ) display_formatter = Instance(DisplayFormatter) displayhook_class = Type(DisplayHook) display_pub_class = Type(DisplayPublisher) @@ -245,12 +270,28 @@ class InteractiveShell(Configurable, Magic): # interactive statements or whole blocks. input_splitter = Instance('IPython.core.inputsplitter.IPythonInputSplitter', (), {}) - logstart = CBool(False, config=True) - logfile = Unicode('', config=True) - logappend = Unicode('', config=True) + logstart = CBool(False, config=True, help= + """ + Start logging to the default log file. + """ + ) + logfile = Unicode('', config=True, help= + """ + The name of the logfile to use. + """ + ) + logappend = Unicode('', config=True, help= + """ + Start logging to the given file in append mode. + """ + ) object_info_string_level = Enum((0,1,2), default_value=0, config=True) - pdb = CBool(False, config=True) + pdb = CBool(False, config=True, help= + """ + Automatically call the pdb debugger after every exception. + """ + ) profile = Unicode('', config=True) prompt_in1 = Str('In [\\#]: ', config=True) diff --git a/IPython/utils/tests/test_traitlets.py b/IPython/utils/tests/test_traitlets.py index 724d631..ca30c84 100755 --- a/IPython/utils/tests/test_traitlets.py +++ b/IPython/utils/tests/test_traitlets.py @@ -366,6 +366,7 @@ class TestHasTraits(TestCase): f = Float a = A() self.assertEquals(a.trait_names(),['i','f']) + self.assertEquals(A.class_trait_names(),['i','f']) def test_trait_metadata(self): class A(HasTraits): @@ -379,6 +380,7 @@ class TestHasTraits(TestCase): f = Float a = A() self.assertEquals(a.traits(), dict(i=A.i, f=A.f)) + self.assertEquals(A.class_traits(), dict(i=A.i, f=A.f)) def test_traits_metadata(self): class A(HasTraits): diff --git a/IPython/utils/traitlets.py b/IPython/utils/traitlets.py index f570c62..21187cc 100644 --- a/IPython/utils/traitlets.py +++ b/IPython/utils/traitlets.py @@ -515,6 +515,48 @@ class HasTraits(object): for n in names: self._add_notifiers(handler, n) + @classmethod + def class_trait_names(cls, **metadata): + """Get a list of all the names of this classes traits. + + This method is just like the :meth:`trait_names` method, but is unbound. + """ + return cls.class_traits(**metadata).keys() + + @classmethod + def class_traits(cls, **metadata): + """Get a list of all the traits of this class. + + This method is just like the :meth:`traits` method, but is unbound. + + The TraitTypes returned don't know anything about the values + that the various HasTrait's instances are holding. + + This follows the same algorithm as traits does and does not allow + for any simple way of specifying merely that a metadata name + exists, but has any value. This is because get_metadata returns + None if a metadata key doesn't exist. + """ + traits = dict([memb for memb in getmembers(cls) if \ + isinstance(memb[1], TraitType)]) + + if len(metadata) == 0: + return traits + + for meta_name, meta_eval in metadata.items(): + if type(meta_eval) is not FunctionType: + metadata[meta_name] = _SimpleTest(meta_eval) + + result = {} + for name, trait in traits.items(): + for meta_name, meta_eval in metadata.items(): + if not meta_eval(trait.get_metadata(meta_name)): + break + else: + result[name] = trait + + return result + def trait_names(self, **metadata): """Get a list of all the names of this classes traits.""" return self.traits(**metadata).keys() diff --git a/docs/examples/core/appconfig.py b/docs/examples/core/appconfig.py new file mode 100644 index 0000000..28644ce --- /dev/null +++ b/docs/examples/core/appconfig.py @@ -0,0 +1,55 @@ +import sys + +from IPython.config.configurable import Configurable +from IPython.utils.traitlets import ( + Bool, Unicode, Int, Float, List +) +from IPython.config.loader import KeyValueConfigLoader + +class Foo(Configurable): + + i = Int(0, config=True, shortname='i', help="The integer i.") + j = Int(1, config=True, shortname='j', help="The integer j.") + name = Unicode(u'Brian', config=True, shortname='name', help="First name.") + + +class Bar(Configurable): + + enabled = Bool(True, config=True, shortname="bar-enabled", help="Enable bar.") + + +class MyApp(Configurable): + + app_name = Unicode(u'myapp', config=True, shortname="myapp", help="The app name.") + running = Bool(False, config=True, shortname="running", help="Is the app running?") + classes = List([Bar, Foo]) + + def __init__(self, **kwargs): + Configurable.__init__(self, **kwargs) + self.classes.insert(0, self.__class__) + + def print_help(self): + for cls in self.classes: + cls.class_print_help() + print + + def parse_command_line(self, argv=None): + if argv is None: + argv = sys.argv[1:] + if '-h' in argv or '--h' in argv: + self.print_help() + sys.exit(1) + loader = KeyValueConfigLoader(argv=argv, classes=self.classes) + config = loader.load_config() + self.config = config + + +def main(): + app = MyApp() + app.parse_command_line() + print "app.config:" + print app.config + + +if __name__ == "__main__": + main()